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.