Persist and pass FacesMessages over multiple page redirects
dcausse Mar 5, 2010 11:49 AMBased on the idea http://ocpsoft.com/java/persist-and-pass-facesmessages-over-page-redirects/
I tried to implement such feature with CDI:
Messages revival is implemented within a PhaseListener according to the Lincoln Baxter implementation.
Messages are saved in RequestScoped bean within the scope of the PhaseListener
Messages are then saved in a SessionScoped bean to live over multiple request
A servlet filter is needed to pump messages from the RequestScoped bean to the SessionScoped bean and push them back from the session to the request scope.
The PhaseListener :
public class MessagesRevivalPhaseListener implements javax.faces.event.PhaseListener {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase(PhaseEvent event) {
if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) {
// XXX: My best attempt to access a managed bean from a PhaseListener...
MessagesRevival msgRevival = JsfUtil.getObjectByELExpr("#{messagesRevival}", MessagesRevival.class);
msgRevival.pumpMessages();
}
}
@Override
public void beforePhase(PhaseEvent event) {
if(PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) {
if(!event.getFacesContext().getResponseComplete()) {
MessagesRevival msgRevival = JsfUtil.getObjectByELExpr("#{messagesRevival}", MessagesRevival.class);
msgRevival.pushMessages();
}
}
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}
The request scoped bean :
@Named
@RequestScoped
public class MessagesRevival implements Serializable {
private static final long serialVersionUID = 1L;
private List<FacesMessage> revivals;
public void pumpMessages() {
FacesContext fctx = FacesContext.getCurrentInstance();
Iterator<FacesMessage> iter = fctx.getMessages();
if(revivals == null && iter.hasNext())
revivals = new ArrayList<FacesMessage>();
while(iter.hasNext()) {
revivals.add(iter.next());
iter.remove();
}
}
public void pushMessages() {
if(revivals == null) return;
FacesContext fctx = FacesContext.getCurrentInstance();
for(FacesMessage fms : revivals) {
fctx.addMessage(null, fms);
}
}
public List<FacesMessage> getRevivals() {
return revivals;
}
public void setRevivals(List<FacesMessage> revivals) {
this.revivals = revivals;
}
}
The session scoped bean
@SessionScoped
public class MessagesRevivalSessionScope implements Serializable {
private static final long serialVersionUID = 1L;
private @Inject MessagesRevival requestRevivals;
private List<FacesMessage> revivals = null;
public synchronized void pump() {
if(revivals == null)
revivals = requestRevivals.getRevivals();
else // In case of concurrent access try to not loose a message
revivals.addAll(requestRevivals.getRevivals());
}
public synchronized void push() {
requestRevivals.setRevivals(revivals);
revivals = null;
}
}
And the servlet filter (we reused our AuthFilter) :
public class AuthFilter implements Filter {
private @Inject Login login;
private @Inject Logger log;
private @Inject MessagesRevivalSessionScope msgRevival;
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
* The first things to do : push the saved messages
* to the request Scoped MessagesRevival
*/
msgRevival.push();
if(login.isActive())
chain.doFilter(request, response);
else {
HttpServletResponse resp = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
resp.sendRedirect(req.getContextPath());
}
if(((HttpServletRequest)request).getSession(false) != null) {
/*
* If the session hasn't been invalidated by a logout
* we read messages from MessagesRevival request Scoped
*/
msgRevival.pump();
}
}
@Override
public void init(FilterConfig fConfig) throws ServletException {
log.debug("Filter {} started", this.getClass().getSimpleName());
}
}
It works but I'm a bit frustated because :
- It isn't type safe I rely on a small utility to resolve my request scoped from the ELContext
- It could have been very simple if PhaseListener was able to receive injection, we could have avoided the ServletFilter and use @PostConstruct et @PreDestroy on the request scoped bean to handle the push and pump.
I tried to find a way to do programmatic injection but it involves :
- creation of an extension
- control the lifecycle of the PhaseListener
What do you think about that, could it be better, more type safe?
Thank you.