1 2 Previous Next 15 Replies Latest reply on May 24, 2010 1:18 PM by nickarls

    Persist and pass FacesMessages over multiple page redirects

      Based 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 :



      1. It isn't type safe I rely on a small utility to resolve my request scoped from the ELContext

      2. 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 :



      1. creation of an extension

      2. control the lifecycle of the PhaseListener



      What do you think about that, could it be better, more type safe?


      Thank you.

        • 1. Re: Persist and pass FacesMessages over multiple page redirects

          In fact I can implement this without a ServletFilter (sorry for the noise) :


          The Phase Listener :


          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())) {
                       MessagesRevival msgRevival = injectMessagesRevival();
                    msgRevival.pumpMessagesFromFacesContext();
                  }
               }
          
               @Override
               public void beforePhase(PhaseEvent event) {
                    if(PhaseId.RESTORE_VIEW.equals(event.getPhaseId())) {
                               MessagesRevival msgRevival = injectMessagesRevival();
                               msgRevival.pumpFromSession();
                    }
                    
                    if(PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) {
                         if(!event.getFacesContext().getResponseComplete()) {
                              MessagesRevival msgRevival = injectMessagesRevival();
                              msgRevival.pushMessagesToFacesContext();
                         }
                    }
               }
               
               public MessagesRevival injectMessagesRevival() {
                    return JsfUtil.getObjectByELExpr("#{messagesRevival}", MessagesRevival.class);
               }
          
               @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 @Inject MessagesRevivalSessionScope sessionScopedMessages;
               
               private List<FacesMessage> revivals;
               
               public void pumpMessagesFromFacesContext() {
                    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 pushMessagesToFacesContext() {
                    if(revivals == null) return;
                    FacesContext fctx = FacesContext.getCurrentInstance();
                    for(FacesMessage fms : revivals) {
                         fctx.addMessage(null, fms);
                    }
                    revivals = null;
               }
               
               public void pumpFromSession() {
                    sessionScopedMessages.pushToRequestScope();
               }
               
               @PreDestroy
               public void pushToSession() {
                    sessionScopedMessages.pumpFromRequestScope();
               }
               
               public List<FacesMessage> getRevivals() {
                    return revivals;
               }
               
               public void setRevivals(List<FacesMessage> revivals) {
                    this.revivals = revivals;
               }
          }
          



          And 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 pumpFromRequestScope() {
                    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 pushToRequestScope() {
                    requestRevivals.setRevivals(revivals);
                    revivals = null;
               }
          }
          



          Now the only problem is type safety isolated inside : MessagesRevivalPhaseListener.injectMessagesRevival()

          • 2. Re: Persist and pass FacesMessages over multiple page redirects
            lincolnthree
            Hi David :)

            I'm glad my tutorial might have helped a little. I do also feel your frustration with the lack of @Inject support in JSF artifacts such as PhaseListener. I have several issues opened to try to address it; your support may help push things along :)

            I know more folks than you and I are needing this type of functionality.

            https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1578
            https://issues.apache.org/jira/browse/MYFACES-2590
            https://glassfish.dev.java.net/issues/show_bug.cgi?id=11655
            https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=763

            Also, if this doesn't happen fast enough, Seam3 will be providing this functionality in the Faces module, so you can always add that to your project if you can't wait :) when it becomes available, of course - it's currently under development.

            If you want to comment on any of those issues and explain why you think it's important to be albe to @Inject into PhaseListeners and other JSF artifacts, I'm sure that it wouldn't hurt in getting the ball rolling. The more people who speak up, the faster things get fixed!

            Otherwise, you could get a reference to the BeanManager and do the typesafe lookup manually if you so choose. EL may be simpler, however:

            http://docs.jboss.org/weld/reference/1.0.0/en-US/html/extend.html#d0e4698

            "Java EE components may obtain an instance of BeanManager from JNDI by looking up the name java:comp/BeanManager. Any operation of BeanManager may be called at any time during the execution of the application."

            I hope this is helpful!
            --Lincoln
            • 3. Re: Persist and pass FacesMessages over multiple page redirects
              hirowla.ian.rowlands.three.com.au

              Hi Lincoln, good to see you here. Just read the blog about you joining JBoss - should I be bowing down in your presence :-) :-) :-) Good to see somebody with that passion working with JSF and other things, because I know when I mention JSF to people they remember verson 1.x and roll their eyes!


              Anyway, back to topic. I agree with you about the PhaseListener. In fact, I'd go further than that - I would suggest that any class that a JEE6 container instantiates (rather than the developer) should have @Inject support. I haven't used a PhaseListener but have used a ServletContextListener (which works), an extended JSF ExceptionHandlerFactory (which doesn't), and an EJB Interceptor (which doesn't, but is being fixed I think). You can get around some of these things - for instance, on the constructor have a class which uses the BeanManager to inject itself - but it is a bit of a hack.


              Am I casting too wide a net about this? Or am I contradicting the CDI spec by saying this.


              Ian


              • 4. Re: Persist and pass FacesMessages over multiple page redirects
                pmuir

                Ian R wrote on Mar 10, 2010 01:35:


                Anyway, back to topic. I agree with you about the PhaseListener. In fact, I'd go further than that - I would suggest that any class that a JEE6 container instantiates (rather than the developer) should have @Inject support.



                We agree with this too. But unfortunately Java EE is a large group of specs, and a large group of people. We pushed injection into as many places as we could without delaying the release of EE6 forever!

                • 5. Re: Persist and pass FacesMessages over multiple page redirects
                  pmuir

                  So please, send feedback to the EE expert group that you want the places can inject stuff increased. Specific examples will help them!

                  • 6. Re: Persist and pass FacesMessages over multiple page redirects
                    nickarls

                    The ultimate target is to have über-injection everywhere in the platform but until then, there is nothing that prevents JBoss AS to do it on a server level, right?

                    • 7. Re: Persist and pass FacesMessages over multiple page redirects
                      nickarls

                      It would, of course be even nicer if all the PhaseListeners etc would be acquired by CDI resolves themselves...

                      • 8. Re: Persist and pass FacesMessages over multiple page redirects

                        Hi,


                        Thank you for the comments, I didn't know that this issue was so demanded but in fact it is quite understandable.
                        As ServletFilters are eligible for injection there is no reasons why PhaseListeners are not. ServletFilters were the best swiss knife to do some sort of pseudo-AOP (security, concurrent access protection and so on...) with good old servlet/jsp applications. But with JSF they are not useable (FacesContext is not available in Filters) and the JSF way to do this appears to be PhaseListeners.


                        As it is not CDI responsability to control everything but rather every EE components to declare their beans to the CDI container it is not the right place to ask such features as Pete said.
                        But I'm pretty sure this will evolve rapidly, we just discovered and used  CDI (thanks to Weld) on a small JSF project and I must admit that I've never seen such revolutionary words as @Scoped and @Inject (except maybe extends and implements :).


                        Now back to my problem : I'll look at java:comp/BeanManager, it seems to be a portable name (I saw it in the jsr-299 pdf document) so I'll go for a CDIUtils toolbox class.


                        Many thanks.

                        • 9. Re: Persist and pass FacesMessages over multiple page redirects
                          pmuir

                          Nicklas Karlsson wrote on Mar 10, 2010 14:12:


                          The ultimate target is to have über-injection everywhere in the platform but until then, there is nothing that prevents JBoss AS to do it on a server level, right?


                          Right. Lincoln is working on getting this available in both Mojarra and MyFaces so then it will work with the common JSF implementations (and hence ootb in GlassFish and JBoss AS at least).

                          • 10. Re: Persist and pass FacesMessages over multiple page redirects
                            henk53

                            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.


                            Is storing messages in the HTTP session really a good idea? Wouldn't you run into problems now when the user has the same page open in multiple tabs or windows?

                            • 11. Re: Persist and pass FacesMessages over multiple page redirects
                              henk53

                              Anyone cares to answer the last question? ;)

                              • 12. Re: Persist and pass FacesMessages over multiple page redirects
                                nickarls

                                Henk de Boer wrote on May 08, 2010 19:35:


                                Anyone cares to answer the last question? ;)


                                Yes.


                                Oh, you mean the one second to last? ;-)


                                Yes. Session scoped stuff tend to be indistinguishable between tabs unless there is some more information added.

                                • 13. Re: Persist and pass FacesMessages over multiple page redirects
                                  pmuir

                                  Nicklas Karlsson wrote on May 08, 2010 22:01:



                                  Henk de Boer wrote on May 08, 2010 19:35:


                                  Anyone cares to answer the last question? ;)


                                  Yes.

                                  Oh, you mean the one second to last? ;-)

                                  Yes. Session scoped stuff tend to be indistinguishable between tabs unless there is some more information added.


                                  Exactly, read about conversations :-)

                                  • 14. Re: Persist and pass FacesMessages over multiple page redirects
                                    henk53

                                    Pete Muir wrote on May 19, 2010 13:16:



                                    Nicklas Karlsson wrote on May 08, 2010 22:01:

                                    Yes. Session scoped stuff tend to be indistinguishable between tabs unless there is some more information added.


                                    Exactly, read about conversations :-)


                                    That seems to be the better solution indeed, especially when using Seam/CDI I don't think we should fake some kind of pseudo conversation when the Real Thing is actually there ;)

                                    1 2 Previous Next