10 Replies Latest reply on Apr 7, 2010 11:16 AM by adamw

    Using events instead of a PhaseListener

    adamw

      I have a PhaseListener, which executes some code before the RENDER_RESPONSE phase. Sometimes it does a redirect, using facesCtx.getExternalContext().redirect(url). And this works fine.


      However, if I try to execute the same code, but observe the before render response events, it stops working.


      The signature for my method is:


      public void beforePhase(@Observes @Before @RenderResponse PhaseEvent event)



      And the exception I get:


      java.lang.IllegalStateException
           org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:436)
           javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:170)
           javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:170)
           org.jboss.weld.servlet.ConversationPropagationFilter$1.sendRedirect(ConversationPropagationFilter.java:91)
           com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:572)
           util.SecurityPhaseListener.beforePhase(SecurityPhaseListener.java:38)



      Any ideas why this happens only when I observe the phase events? Or where to start debugging? :)


      Adam

        • 1. Re: Using events instead of a PhaseListener
          dan.j.allen

          I tried this scenario, but could not reproduce the problem. Here's the observer I am using:


          public class GoogleRedirector {
             public void redirect(@Observes @Before @RenderResponse PhaseEvent e, FacesContext ctx) throws IOException {
                if (ctx.isPostback()) {
                   ctx.getExternalContext().redirect("http://google.com");
                }
             }
          }



          I tried the redirect on both an initial request and postback. In both cases, the redirect happened successfully.


          I do know that the error is caused when content is written to the output stream before attempting to redirect. You must do any redirect, cookie writing, and any other HTTP header setting before you start writing other content. Once you have written even 1 byte to the output stream you cannot do any of those other things.


          Perhaps you have two things trying to redirect. I was able to duplicate the problem if I called redirect() twice.


          If you can reproduce the problem consistently with the latest SVN, then you can create a JIRA with the details so we can be sure to follow up on it ;)

          • 2. Re: Using events instead of a PhaseListener
            dan.j.allen

            Btw, I noticed that if the conversation is long-running, the redirect will also fail.

            • 3. Re: Using events instead of a PhaseListener
              nickarls

              This can of course have something to do with listener orderings, too. Can you breakpoint in the ConversationPropagationFilter to verify that the cause of the IllegalStateException is that content has been written to the response?

              • 4. Re: Using events instead of a PhaseListener
                adamw

                Did you try redirecting to a view in the application? (not to an external site)


                Adam

                • 5. Re: Using events instead of a PhaseListener
                  adamw

                  I did some debugging, and it seems that if I use the observer for the render response event, the method is called twice, hence the redirect is called twice, and the second time it fails, as there's already content written (headers from the previous calls).


                  If I use a normal phase listener it's called once.


                  In Weld, there are only two event listeners registered (mine and TransactionPL, which is empty right now), so no idea why it's called twice.


                  I'll try to create a small app later which reproduces the problem.


                  Adam

                  • 6. Re: Using events instead of a PhaseListener
                    dan.j.allen

                    Great Adam. Thanks. We'll be sure to create a test from it. Lincoln and Nik, this would be a good case for a JSFUnit test, I think.

                    • 7. Re: Using events instead of a PhaseListener
                      adamw

                      Heh, of course my small test works (no exception). I'm giving up for now.


                      Only in my application, which is pretty simple, the DelegatingPhaseListener is called for some unknown reason twice, for each event.


                      Adam

                      • 8. Re: Using events instead of a PhaseListener
                        adamw

                        Ok, this was as stupid as it could be. Idea has been deploying duplicate artifacts. I've been cleaning the build before, but using a command from the IDE (menu). But that didn't do anything; only deleting the target from command line worked. And now events work fine. Sorry to bug you and waste your time :).


                        And now comes another problem: is there a way to order the events? In faces-config.xml I have control on which phase listener comes first.


                        As my phase listeners implement transactions and security, the first must be exexcuted before the second.


                        Or maybe I should just stick to xml ;)


                        Adam

                        • 9. Re: Using events instead of a PhaseListener
                          nickarls

                          The PhaseListener (and other JSF artifacts) handling is currently being investigated


                          One option would of course to have some sort of


                          @PhaseListener(id="security") and @PhaseListener(id="transactions" after="security")
                          



                          and then register one coordinating listener that makes sure stuff is fired in the correct order. Perhaps.

                          • 10. Re: Using events instead of a PhaseListener
                            adamw

                            Hmm ... I guess the CDI-way of ordering things is through beans.xml? So maybe this also should be kept in xml. But then you can just specify the listeners in faces-config.xml :).


                            Adam