9 Replies Latest reply on Mar 20, 2008 11:44 AM by lixin meng

    NullPointer at FacesContextBridge

    lixin meng Newbie

      I got NPE in the following code (FacesContextBridge.java) where 'facesContext' is null, when I tried to create a JSFClientSession.

       public static FacesContext getCurrentInstance()
       {
       HttpSession session = WebConversationFactory.getSessionFromThreadLocal();
       JSFUnitFacesContext facesContext = (JSFUnitFacesContext)session.getAttribute(JSFUnitFacesContext.SESSION_KEY);
       facesContext.setInstanceToJSFUnitThread();
       return facesContext;
       }
      


      What I found is that the FacesContextBridge's 'session' (above) is different from the 'session' (below) that JSFUnitFacesContext refered (my targeted JSF request is using).

       public JSFUnitFacesContext(FacesContext delegate, Object request)
       {
      ...
       req.getSession().setAttribute(SESSION_KEY, this);
      ...
       }
      


      In my test, I have to login the application via a SSO portal, as below. Will this cause the problem?

       public void testInitialPage() throws IOException, SAXException {
      
       WebConversation webConversation = WebConversationFactory
       .makeWebConversation();
      
       // Initial JSF request
       WebRequest req = new GetMethodWebRequest(WebConversationFactory.getWARURL()
       + "/main.jsf");
       this.mWebResponse = webConversation.getResponse(req);
       if (this.mWebResponse.getURL().toString().endsWith("startPortal.do")) {
       WebForm[] forms = this.mWebResponse.getForms();
       WebForm loginForm = forms[0];
      
       assertEquals("loginForm", loginForm.getName());
      
       loginForm.setParameter("username", "PA");
       loginForm.setParameter("password", "PA");
       this.mWebResponse = loginForm.submit();
       // get the session established
       this.mWebResponse = webConversation.getResponse(req);
       }
       mClient = new JSFClientSession(webConversation, "/main.jsf"); }
      
      


        • 1. Re: NullPointer at FacesContextBridge
          Stan Silvert Master

          Are you running in a cluster? JSFUnit/Cactus doesn't work with round-robin load balancing. If you are running in a cluster then every client request must go to the same server. See http://jakarta.apache.org/cactus/faq.html#faq_load_balancing

          To help debug, you can always get the session id like this:

          String jsessionid = webConversation.getCookieValue("JSESSIONID");


          I've never tried mixing JSF and Struts requests in a JSFUnit test, but it looks like what you have should work. So if you can figure out exactly where it is losing the session, maybe we can pin it down.

          Stan

          • 2. Re: NullPointer at FacesContextBridge
            lixin meng Newbie

            Thanks for the quick reply.

            I am not using cluster, but I am running on Weblogic 9.

            I modified my code by injecting those checks on session id. The observation is that the session id at 'point B' is different from 'point A'.

            My deployment is that we have two instance of Weblogic 9 (different ports). One is a Portal server for SSO, the other one is my target instance. The unit test code is on the target instance. To access JSF pages on my target instance, I need to be authenticated by the Portal server.

            Is there a way to handle situation like this?

            Regards,
            mlx

             public void testInitialPage() throws IOException, SAXException {
            
             WebConversation webConversation = WebConversationFactory
             .makeWebConversation();
            
             // Initial JSF request
             WebRequest req = new GetMethodWebRequest(WebConversationFactory.getWARURL()
             + "/main.jsf");
            
             String jsessionid = webConversation.getCookieValue("JSESSIONID");
             System.out.println("--- point A --- " + jsessionid);
            
             this.mWebResponse = webConversation.getResponse(req);
             if (this.mWebResponse.getURL().toString().endsWith("startPortal.do")) {
             WebForm[] forms = this.mWebResponse.getForms();
             WebForm loginForm = forms[0];
            
             assertEquals("loginForm", loginForm.getName());
            
             loginForm.setParameter("username", "PA");
             loginForm.setParameter("password", "PA");
             this.mWebResponse = loginForm.submit();
             // get the session established
             this.mWebResponse = webConversation.getResponse(req);
             }
            
             jsessionid = webConversation.getCookieValue("JSESSIONID");
             System.out.println("--- point B --- " + jsessionid);
            
             mClient = new JSFClientSession(webConversation, "/main.jsf");
            
             jsessionid = webConversation.getCookieValue("JSESSIONID");
             System.out.println("--- point C --- " + jsessionid);
            
            


            • 3. Re: NullPointer at FacesContextBridge
              Stan Silvert Master

              You might want to take a look at this to see in more detail how JSFUnit works: http://wiki.jboss.org/wiki/JSFUnitSessionAndThreads

              When FacesContextBridge.getCurrentInstance() gets the session, it will always use the one referenced in your ThreadLocal. This is the reference set up by the JSFUnitFilter.

              So when you log in to your SSO server, the JSESSIONID is reset and you do your initial JSF request. But it does it against a different session. So when the FacesContextBridge tries to find the FacesContext, it's sitting in a different session.

              I'm pretty sure the solution is to just reset the cookie on the WebConversation so that you get back to the original JSESSIONID. Or it should work if you just use the single-arg constructor of JSFClientSession. That will create a new WebConversation with the correct JSESSIONID.

              Stan

              • 4. Re: NullPointer at FacesContextBridge
                lixin meng Newbie

                 

                Based on the link you posted. 'a multi-threaded test where two JSFClientSession instances are making JSF requests at the same time is not supported.'. If that's the case, why not change the use of ThreadLocal to a global static variable for holding the session.

                Is there a better solution?

                mlx


                While a multi-threaded test is not supported, multi-client testing IS supported. In other words, you might have more than one developer hitting a single server instance. Each developer should be able to run a JSFUnit test against that server at the same time. A ThreadLocal works for that but a global static variable does not.

                I think I have a cleaner solution for you. It sounds like the way your SSO server works is that you log in and it resets the JSESSIONID to what it considers to be an "authenticated" session. That is the one you need to use for all future requests.

                So instead of kicking of JSFUnit by a call to the ServletTestRunner, call your own FooServlet that logs into your SSO server using plain HttpUnit. Then take that same WebConversation with the newly authenticated JSESSIONID and call the ServletTestRunner. Get the result from that call and write it to the response of the FooServlet.

                If you are not running JSFUnit from the browser then you will need to change your mappings in web.xml so that Cactus hits your FooServlet instead of the ServletRedirector. If that is the case, I can walk you through it. But try my suggestion running from the browser first. That should work.

                Stan

                • 5. Re: NullPointer at FacesContextBridge
                  lixin meng Newbie

                  Thanks for the message. I'll try it later and keep you updated.

                  One more thought. If you provide another 'makeWebConversation()', which allows user to decide whether to call 'clearSession()' or not, people in similar situation won't have to build the extra 'FooServlet'.

                  We can just use browser to login our application first, then start the testing with the same browser. It is the same concept as your solution, I guess.

                  Is there a reason that the session must be cleared?

                  mlx

                  • 6. Re: NullPointer at FacesContextBridge
                    Stan Silvert Master

                     

                    "lmeng" wrote:


                    Is there a reason that the session must be cleared?

                    mlx


                    If you didn't clear the session you wouldn't be able to run more than one test. When you run your second test you would have junk left over from the last one. Each time you call "new JSFClientSession()" you should be assured that, indeed, that's what you are getting.

                    I don't think clearing the session is the issue in your case. The problem is that you need to run Cactus under an authenticated session. Instead of authenticating the session that hit the SSO server, your SSO server is resetting that session id. So there is no getting around the fact that you need some sort of pre-handler to log into your SSO server before Cactus will work.

                    Of course, this all assumes that I understand how your SSO server works.

                    Stan

                    • 7. Re: NullPointer at FacesContextBridge
                      lixin meng Newbie

                      I tried the solution of providing another FooServlet. It still failed.

                      I examed the session ID of the wc after the login() (highlighted below). It is different from the one I got in JSFUnitFilter later.

                      Did I get your idea wrong?

                      The FooServlet looks like:

                      ...
                       public void doGet(HttpServletRequest theRequest,
                       HttpServletResponse theResponse) throws ServletException,
                       IOException {
                      ...
                       WebResponse response;
                       try {
                       String warUrl = makeWARURL(theRequest);
                       response = run(warUrl, suiteClassName, xslParam, encoding, transformParam);
                       theResponse.setContentType(response.getContentType());
                       PrintWriter pw = theResponse.getWriter();
                       pw.println(response.getText());
                       } catch (SAXException e) {
                       throw new ServletException(e);
                       }
                      
                       }
                      
                       protected WebResponse run(String warUrl, String suiteClassName, String xslParam, String encoding, String transformParam )
                       throws ServletException, MalformedURLException, IOException, SAXException {
                       WebConversation wc = login(warUrl);
                      
                       // exam the session id here
                       StringBuilder realCactusURL = new StringBuilder();
                      ...
                       // Initial JSF request
                       WebRequest req = new GetMethodWebRequest(warUrl + realCactusURL.toString());
                       WebResponse response = wc.getResponse(req);
                      
                       return (response);
                       }
                      
                       protected WebConversation login(String warUrl) throws MalformedURLException, IOException, SAXException, ServletException {
                      
                       WebConversation webConversation = new WebConversation();
                      
                       // Initial JSF request
                       WebRequest req = new GetMethodWebRequest(warUrl+ "/main.jsf");
                      
                       WebResponse webResponse = webConversation.getResponse(req);
                       if (webResponse.getURL().toString().endsWith("startPortal.do")) {
                       WebForm[] forms = webResponse.getForms();
                       WebForm loginForm = forms[0];
                      
                      ...
                       webResponse = loginForm.submit();
                      
                       }
                       return (webConversation);
                       }
                      
                      ...
                      


                      • 8. Re: NullPointer at FacesContextBridge
                        Stan Silvert Master

                        That looks right except I'm not sure about this:

                        // Initial JSF request
                        WebRequest req = new GetMethodWebRequest(warUrl + realCactusURL.toString());


                        This is not a JSF request. It should be a request to the ServletTestRunner. But the fact that you say your request hit the JSFUnitFilter tells me that realCactusURL is a URL to hit ServletTestRunner?

                        What I don't understand is how the WebConversation can have the jsessionid set correctly by the SSO server and then when it hits the JSFUnitFilter via above line of code, jsessionid is changed?

                        Stan

                        • 9. Re: NullPointer at FacesContextBridge
                          lixin meng Newbie

                          It is a request to the ServletTestRunner, which in turn calls 'testInitialPage()'. From there, a JSF request is sent out. My comment in the code is wrong. It was copy-and-paste.

                          I have to look into how the session got changed later.

                          Thanks for all the help.