ICEfaces
  1. ICEfaces
  2. ICE-9354

Portlets showing Network Connected Interrupt Dialog when session expires

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 3.3, EE-3.3.0.GA
    • Fix Version/s: EE-3.3.0.GA_P01, 4.0.BETA, 4.0
    • Component/s: Framework
    • Labels:
      None
    • Environment:
      Portlets
    • Assignee Priority:
      P1

      Description

      It only seems to be an issue with portlets but when the session expires, I'm seeing the Network Connection Interrupted popup appear. Logging on the server indicates that session expiry has occurred. I have not been able to see the same issue in non-portlet environment.

        Activity

        Hide
        Deryk Sinotte added a comment -

        Assigning to Mircea. I'll try and add more detail on how to reproduce this.

        Show
        Deryk Sinotte added a comment - Assigning to Mircea. I'll try and add more detail on how to reproduce this.
        Hide
        Ken Fyten added a comment -

        Datapoint: I tried testing EE 3.3 build 11 stand-alone (no portlets):

        1. The correct Session Expired message appears in none-push-scenarios.

        2. With push active (and strictSessionTimeout=true) it also presents the User Session Expired dialog correctly (and asynchronously).

        Show
        Ken Fyten added a comment - Datapoint: I tried testing EE 3.3 build 11 stand-alone (no portlets): 1. The correct Session Expired message appears in none-push-scenarios. 2. With push active (and strictSessionTimeout=true) it also presents the User Session Expired dialog correctly (and asynchronously).
        Hide
        Deryk Sinotte added a comment -

        Assigning back to me and adding more information.

        During testing of portlets for the 3.3 EE release, I've noticed that SessionExpiredExceptions are being logged even when the session should still be valid:

        04:38:37,361 ERROR [ExceptionHandlerAjaxImpl:62] Session has expired
        org.icefaces.application.SessionExpiredException: Session has expired
        at org.icefaces.impl.application.ExtendedExceptionHandler.handle(ExtendedExceptionHandler.java:106)
        at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:119)
        at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116)
        at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
        at com.liferay.faces.bridge.BridgePhaseResourceImpl.execute(BridgePhaseResourceImpl.java:100)
        at com.liferay.faces.bridge.BridgeImpl.doFacesRequest(BridgeImpl.java:128)
        at javax.portlet.faces.GenericFacesPortlet.serveResource(GenericFacesPortlet.java:178)
        at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(FilterChainImpl.java:177)
        at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:76)
        at com.liferay.portal.kernel.servlet.PortletServlet.service(PortletServlet.java:100)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        These exceptions are always triggered by ViewExpiredExceptions:

        WARNING: queued exception
        javax.faces.application.ViewExpiredException: viewId:/portlet-view.xhtml - View /portlet-view.xhtml could not be restored.
        at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:205)
        at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
        at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116)
        at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
        at com.liferay.faces.bridge.BridgePhaseResourceImpl.execute(BridgePhaseResourceImpl.java:100)
        at com.liferay.faces.bridge.BridgeImpl.doFacesRequest(BridgeImpl.java:128)
        at javax.portlet.faces.GenericFacesPortlet.serveResource(GenericFacesPortlet.java:178)
        at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(FilterChainImpl.java:177)
        at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:76)
        at com.liferay.portal.kernel.servlet.PortletServlet.service(PortletServlet.java:100)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        We detect ViewExpiredExceptions in our customer error handler and then attempt to detect if the session is still valid before removing the ViewExpiredException and inserting our own custom SessionExpiredException. So it appears that views are expiring but we are incorrectly detecting that the session is no longer valid.

        Additionally, when these exceptions occur, the client sometimes displays a Network Connection Interrupted dialog

        Show
        Deryk Sinotte added a comment - Assigning back to me and adding more information. During testing of portlets for the 3.3 EE release, I've noticed that SessionExpiredExceptions are being logged even when the session should still be valid: 04:38:37,361 ERROR [ExceptionHandlerAjaxImpl:62] Session has expired org.icefaces.application.SessionExpiredException: Session has expired at org.icefaces.impl.application.ExtendedExceptionHandler.handle(ExtendedExceptionHandler.java:106) at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:119) at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116) at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118) at com.liferay.faces.bridge.BridgePhaseResourceImpl.execute(BridgePhaseResourceImpl.java:100) at com.liferay.faces.bridge.BridgeImpl.doFacesRequest(BridgeImpl.java:128) at javax.portlet.faces.GenericFacesPortlet.serveResource(GenericFacesPortlet.java:178) at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(FilterChainImpl.java:177) at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:76) at com.liferay.portal.kernel.servlet.PortletServlet.service(PortletServlet.java:100) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) These exceptions are always triggered by ViewExpiredExceptions: WARNING: queued exception javax.faces.application.ViewExpiredException: viewId:/portlet-view.xhtml - View /portlet-view.xhtml could not be restored. at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:205) at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:116) at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118) at com.liferay.faces.bridge.BridgePhaseResourceImpl.execute(BridgePhaseResourceImpl.java:100) at com.liferay.faces.bridge.BridgeImpl.doFacesRequest(BridgeImpl.java:128) at javax.portlet.faces.GenericFacesPortlet.serveResource(GenericFacesPortlet.java:178) at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(FilterChainImpl.java:177) at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:76) at com.liferay.portal.kernel.servlet.PortletServlet.service(PortletServlet.java:100) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) We detect ViewExpiredExceptions in our customer error handler and then attempt to detect if the session is still valid before removing the ViewExpiredException and inserting our own custom SessionExpiredException. So it appears that views are expiring but we are incorrectly detecting that the session is no longer valid. Additionally, when these exceptions occur, the client sometimes displays a Network Connection Interrupted dialog
        Hide
        Deryk Sinotte added a comment -

        There are a number of variables involved with session expiration:

        • The configuration of the sessionTimeout parameter
          • Unlike non-portlet examples, setting the sessionTimeout parameter doesn't have the same impact as the setting is taken from Liferay's .war file
        • The logging in the LiferayFaces Bridge
          • It appears that a lot of the logging is extra verbose output from the bridge which is logging every queued exception as ERROR.
        • Being logged into Liferay as a valid user vs not being logged in.
          • Being logged in activates the Liferay session expiration warning system. As well, if you are not logged in your session automatically renews itself - you'll never see it expire. If you don't see the Liferay session warning banner 1 minute before the session should expire, you are likely not logged in.
        • Push is active vs not being active.
          • If Push is active then it relies on strictSessionTimeout being set. Otherwise the session remains active.
        • Setting strictSessionTimeout to true/false
          • "Ignores" push requests as touching the session
        • Number of portlets on the page and which .war they come from.
          • Not sure of the exact impact of this yet as portlets from separate .war files don't share a session
        • Network Connection Interrupted
          • We look specifically for our own SessionExpiredMessage. Other exceptions (like ViewExpiredExceptions) coming back from a "Push" appear to simply fall back to this error reporting strategy.
        Show
        Deryk Sinotte added a comment - There are a number of variables involved with session expiration: The configuration of the sessionTimeout parameter Unlike non-portlet examples, setting the sessionTimeout parameter doesn't have the same impact as the setting is taken from Liferay's .war file The logging in the LiferayFaces Bridge It appears that a lot of the logging is extra verbose output from the bridge which is logging every queued exception as ERROR. Being logged into Liferay as a valid user vs not being logged in. Being logged in activates the Liferay session expiration warning system. As well, if you are not logged in your session automatically renews itself - you'll never see it expire. If you don't see the Liferay session warning banner 1 minute before the session should expire, you are likely not logged in. Push is active vs not being active. If Push is active then it relies on strictSessionTimeout being set. Otherwise the session remains active. Setting strictSessionTimeout to true/false "Ignores" push requests as touching the session Number of portlets on the page and which .war they come from. Not sure of the exact impact of this yet as portlets from separate .war files don't share a session Network Connection Interrupted We look specifically for our own SessionExpiredMessage. Other exceptions (like ViewExpiredExceptions) coming back from a "Push" appear to simply fall back to this error reporting strategy.
        Hide
        Deryk Sinotte added a comment -

        Looks as if both exceptions are coming in - first SessionExpired and, since push hasn't stopped for some reason, ViewExpired, which explains why there is an NCI. Looks like there are actually two NCIs (one for each exception).

        Digging a bit deeper, when the first exception arrives (SessionExpiredException), the handler we registered with JSF to deal with incoming errors is invoked. The relevant piece is:

        if (e.status == 'serverError') {
            var xmlContent = e.responseXML;
            if (containsXMLData(xmlContent) && sessionExpiredListener) {
                var errorName = xmlContent.getElementsByTagName("error-name")[0].firstChild.nodeValue;
                if (errorName && contains(errorName, 'org.icefaces.application.SessionExpiredException')) {
                    info(logger, 'received session expired message');
                    sessionExpiredListener();
                    return;
                }
            }
        
            info(logger, 'received error message [code: ' + e.responseCode + ']: ' + e.responseText);
            broadcast(perRequestServerErrorListeners, [ e.responseCode, e.responseText, containsXMLData(xmlContent) ? xmlContent : null]);
        } else if (e.status == 'httpError') {
            warn(logger, 'HTTP error [code: ' + e.responseCode + ']: ' + e.description);
            broadcast(perRequestNetworkErrorListeners, [ e.responseCode, e.description]);
        } else {
            //If the error falls through the other conditions, just log it.
            error(logger, 'Error [status: ' + e.status + ' code: ' + e.responseCode + ']: ' + e.description);
        }
        

        The problem seems to be that the details of the exception are slightly different than what we the code is looking for. Note that the status is httpError rather than serverError:

        e: Object
        description: "There was an error communicating with the server, status: 400"
        responseCode: 400
        responseText: "<partial-response><error><error-name>class org.icefaces.application.SessionExpiredException</error-name><error-message><![CDATA[Session has expired]]></error-message></error><changes><extension aceCallbackParam="validationFailed">{"validationFailed":false}</extension></changes></partial-response>
        responseXML: null
        source: form#v3q3duy4-retrieve-update
        status: "httpError"
        type: "error"
        __proto__: Object
        errorName: undefined
        this: Window
        xmlContent: undefined

        However I can't find anything recently changed that would cause this to happen in portlets. I ran against the 3.3 open source release and it seems to have the same behaviour so it may not be that recent.

        In jsf.js, errors are sent with a status of httpError in onComplete:

        req.onComplete = function onComplete() {
            if (req.xmlReq.status && (req.xmlReq.status >= 200 && req.xmlReq.status < 300)) {
                sendEvent(req.xmlReq, req.context, "complete");
                jsf.ajax.response(req.xmlReq, req.context);
            } else {
                sendEvent(req.xmlReq, req.context, "complete");
                sendError(req.xmlReq, req.context, "httpError");
            }
        

        Whereas they are sent as serverError in response:

        if (responseType.nodeName === "error") { // it's an error
            var errorName = responseType.firstChild.firstChild.nodeValue;
            var errorMessage = responseType.firstChild.nextSibling.firstChild.nodeValue;
            sendError(request, context, "serverError", null, errorName, errorMessage);
            sendEvent(request, context, "success");
            return;
        }
        
        Show
        Deryk Sinotte added a comment - Looks as if both exceptions are coming in - first SessionExpired and, since push hasn't stopped for some reason, ViewExpired, which explains why there is an NCI. Looks like there are actually two NCIs (one for each exception). Digging a bit deeper, when the first exception arrives (SessionExpiredException), the handler we registered with JSF to deal with incoming errors is invoked. The relevant piece is: if (e.status == 'serverError') { var xmlContent = e.responseXML; if (containsXMLData(xmlContent) && sessionExpiredListener) { var errorName = xmlContent.getElementsByTagName("error-name")[0].firstChild.nodeValue; if (errorName && contains(errorName, 'org.icefaces.application.SessionExpiredException')) { info(logger, 'received session expired message'); sessionExpiredListener(); return ; } } info(logger, 'received error message [code: ' + e.responseCode + ']: ' + e.responseText); broadcast(perRequestServerErrorListeners, [ e.responseCode, e.responseText, containsXMLData(xmlContent) ? xmlContent : null ]); } else if (e.status == 'httpError') { warn(logger, 'HTTP error [code: ' + e.responseCode + ']: ' + e.description); broadcast(perRequestNetworkErrorListeners, [ e.responseCode, e.description]); } else { //If the error falls through the other conditions, just log it. error(logger, 'Error [status: ' + e.status + ' code: ' + e.responseCode + ']: ' + e.description); } The problem seems to be that the details of the exception are slightly different than what we the code is looking for. Note that the status is httpError rather than serverError: e: Object description: "There was an error communicating with the server, status: 400" responseCode: 400 responseText: "<partial-response><error><error-name>class org.icefaces.application.SessionExpiredException</error-name><error-message><![CDATA[Session has expired]]></error-message></error><changes><extension aceCallbackParam="validationFailed">{"validationFailed":false}</extension></changes></partial-response> responseXML: null source: form#v3q3duy4-retrieve-update status: "httpError" type: "error" __proto__: Object errorName: undefined this: Window xmlContent: undefined However I can't find anything recently changed that would cause this to happen in portlets. I ran against the 3.3 open source release and it seems to have the same behaviour so it may not be that recent. In jsf.js, errors are sent with a status of httpError in onComplete: req.onComplete = function onComplete() { if (req.xmlReq.status && (req.xmlReq.status >= 200 && req.xmlReq.status < 300)) { sendEvent(req.xmlReq, req.context, "complete"); jsf.ajax.response(req.xmlReq, req.context); } else { sendEvent(req.xmlReq, req.context, "complete"); sendError(req.xmlReq, req.context, "httpError"); } Whereas they are sent as serverError in response: if (responseType.nodeName === "error") { // it's an error var errorName = responseType.firstChild.firstChild.nodeValue; var errorMessage = responseType.firstChild.nextSibling.firstChild.nodeValue; sendError(request, context, "serverError", null , errorName, errorMessage); sendEvent(request, context, "success"); return ; }
        Hide
        Deryk Sinotte added a comment -

        To reproduce the problem:

        • Set the Liferay ROOT/WEB-INF/web.xml to 2 minute session duration to speed things up. Set the session timeout for any apps you are testing with (i.e. showcase-portlet) to 2 minutes as well.
        • As you will be testing with push, set "strictSessionTimeout" to true as well.

        Once everything is configured.

        1. Build and deploy the showcase-portlet.war.
        2. Sign in to Liferay.
        3. Create a new portal page (e.g. NoPushEE).
        4. Add a non-push example (ICEfaces3 -> ACE -> Button -> Checkbox Button) to the page.
        5. Idle and wait for the session to expire. Liferay banner should give 1 minute countdown and then the warning message.
        6. Once the session expires, interact with the portlet (click the checkbox). If you have a portlet with Push (e.g. Progress Bar Push), it basically does the same thing but it just does it "automatically" as a Push request will trigger the behaviour rather than user interaction.
        7. Instead of showing our "Session Expired" dialog it shows the Network Connection Interrupt.

        Using the browser dev console (I use Chrome) to look at the network traffic - specifically the request that triggered the SessionExpiredException. It should show a 400 error and has the following response content exactly as typed:

        "
        <partial-response><error><error-name>class org.icefaces.application.SessionExpiredException</error-name><error-message><![CDATA[Session has expired]]></error-message></error><changes><extension aceCallbackParam="validationFailed">{"validationFailed":false}</extension></changes></partial-response>
        … 
        [about 95 empty lines]
        …
        		<!DOCTYPE html>
        …
        [about 120 empty lines]
        … 
        <html class="ltr" dir="ltr" lang="en-US">
        
        
        
        <head>
        
        	<title>Status - Liferay</title>
        … 
        [the entire contents of this page]
        … 
        </html>
        
        "

        So the issue is that something is wrapping the Ajax response in quotes and appending some sort of Liferay page or document as well. This means that the response can't be parsed properly as XML which results in the issue we are seeing (NCI instead of Session Expired dialog).

        Show
        Deryk Sinotte added a comment - To reproduce the problem: Set the Liferay ROOT/WEB-INF/web.xml to 2 minute session duration to speed things up. Set the session timeout for any apps you are testing with (i.e. showcase-portlet) to 2 minutes as well. As you will be testing with push, set "strictSessionTimeout" to true as well. Once everything is configured. Build and deploy the showcase-portlet.war. Sign in to Liferay. Create a new portal page (e.g. NoPushEE). Add a non-push example (ICEfaces3 -> ACE -> Button -> Checkbox Button) to the page. Idle and wait for the session to expire. Liferay banner should give 1 minute countdown and then the warning message. Once the session expires, interact with the portlet (click the checkbox). If you have a portlet with Push (e.g. Progress Bar Push), it basically does the same thing but it just does it "automatically" as a Push request will trigger the behaviour rather than user interaction. Instead of showing our "Session Expired" dialog it shows the Network Connection Interrupt. Using the browser dev console (I use Chrome) to look at the network traffic - specifically the request that triggered the SessionExpiredException. It should show a 400 error and has the following response content exactly as typed: " <partial-response><error><error-name>class org.icefaces.application.SessionExpiredException</error-name><error-message><![CDATA[Session has expired]]></error-message></error><changes><extension aceCallbackParam="validationFailed">{"validationFailed":false}</extension></changes></partial-response> … [about 95 empty lines] … <!DOCTYPE html> … [about 120 empty lines] … <html class="ltr" dir="ltr" lang="en-US"> <head> <title>Status - Liferay</title> … [the entire contents of this page] … </html> " So the issue is that something is wrapping the Ajax response in quotes and appending some sort of Liferay page or document as well. This means that the response can't be parsed properly as XML which results in the issue we are seeing (NCI instead of Session Expired dialog).
        Hide
        Deryk Sinotte added a comment -

        I tried an older version of the bridge (the 3.1.1 we shipped previously) and the session expired worked without Push as expected but failed (in a different way) when Push was used.

        The newer bridge has the same problem in both cases where the response containing the SessionExpiredException also includes the whole document appended to the end of the response.

        Show
        Deryk Sinotte added a comment - I tried an older version of the bridge (the 3.1.1 we shipped previously) and the session expired worked without Push as expected but failed (in a different way) when Push was used. The newer bridge has the same problem in both cases where the response containing the SessionExpiredException also includes the whole document appended to the end of the response.
        Hide
        Deryk Sinotte added a comment -

        I checked in a fix that allows the "strictSessionTimeout" parameter to be properly respected when running in a portal. This involved changing from using ExternalContext.getSessionMap() to using EnvUtils.getSafeSession() which returns a ProxySession instance. The end result is that we used the application-scoped PortletSession rather than the portlet-scoped session.

        Show
        Deryk Sinotte added a comment - I checked in a fix that allows the "strictSessionTimeout" parameter to be properly respected when running in a portal. This involved changing from using ExternalContext.getSessionMap() to using EnvUtils.getSafeSession() which returns a ProxySession instance. The end result is that we used the application-scoped PortletSession rather than the portlet-scoped session.
        Hide
        Deryk Sinotte added a comment -

        Screen shot showing Ajax response with SessionExpiredException along with the page that Liferay renders when it detects an exception.

        Show
        Deryk Sinotte added a comment - Screen shot showing Ajax response with SessionExpiredException along with the page that Liferay renders when it detects an exception.
        Hide
        Deryk Sinotte added a comment -

        It seems that the exception is being detected by the Liferay container itself which ends up writing out the original Ajax update as well as the full page of the status portlet response (see the attached screen shot). I wouldn't have thought that Liferay would even see these exceptions as part of the Ajax request/response so I decided to concentrate on that and found a couple of things:

        • The LiferayFaces Bridge has the ExceptionHandlerAjaxImpl logging all the Ajax exceptions as a troubleshooting tool. We have something similar in our own custom exception handler when we found that Mojarra wasn't logging anything and certain problems related to Ajax were difficult to pin down.

        However, we switched to only doing it when ProjectStage = Development to avoid logging what amounts to normal behaviour (like ViewExpiredExceptions). This is not part of the current issue we're seeing but it does tend to generate some extra noise that you may not want in a production environment. So I modified the bridge's ExceptionHandlerAjaxImpl to do something similar:

                public void handle() throws FacesException {
         
        +        FacesContext fc = FacesContext.getCurrentInstance();
        +        boolean isDevelopment = fc.isProjectStage(ProjectStage.Development);
        +
                        // Before delegating, log all exceptions to the console.
                        Iterable<ExceptionQueuedEvent> unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents();
                        Iterator<ExceptionQueuedEvent> itr = unhandledExceptionQueuedEvents.iterator();
        @@ -58,7 +63,7 @@ public class ExceptionHandlerAjaxImpl extends ExceptionHandlerWrapper {
                                if (exceptionQueuedEventContext != null) {
                                        Throwable throwable = exceptionQueuedEventContext.getException();
         
        -                               if (throwable != null) {
        +                               if (throwable != null && isDevelopment) {
                                                logger.error(throwable);
                                        }
        
        • The real problem seems to be in BridgePhaseResourceImpl (again, a class in the LiferayFaces Bridge) where exceptions are detected, wrapped, and rethrown as BridgeExceptions. This causes them to trickle up to the container (Liferay) which does it's own error handling and sticks the "status" page onto the end of the Ajax response. To verify this, I made a slight alteration so that the extra wrapping is only done if this is not currently an Ajax request:
        +                //Only wrap/throw BridgeExceptions if it's not Ajax
        +                boolean isAjax = facesContext.getPartialViewContext().isAjaxRequest();
        +                logger.info("is Ajax: " + isAjax);
        +
        -                               if (handledException != null) {
        +                               if (handledException != null && !isAjax) {
                                                throw new BridgeException(handledException);
                                        }
         
                                        // Otherwise, if there were any "unhandled" exceptions queued, then throw a BridgeException.
                                        Throwable unhandledException = getJSF2UnhandledException(facesContext);
         
        -                               if (unhandledException != null) {
        +                               if (unhandledException != null && !isAjax) {
                                                throw new BridgeException(unhandledException);
                                        }
        

        I'll follow up with Neil about what we will do about these two issues and create/link additional JIRAs as required.

        Show
        Deryk Sinotte added a comment - It seems that the exception is being detected by the Liferay container itself which ends up writing out the original Ajax update as well as the full page of the status portlet response (see the attached screen shot). I wouldn't have thought that Liferay would even see these exceptions as part of the Ajax request/response so I decided to concentrate on that and found a couple of things: The LiferayFaces Bridge has the ExceptionHandlerAjaxImpl logging all the Ajax exceptions as a troubleshooting tool. We have something similar in our own custom exception handler when we found that Mojarra wasn't logging anything and certain problems related to Ajax were difficult to pin down. However, we switched to only doing it when ProjectStage = Development to avoid logging what amounts to normal behaviour (like ViewExpiredExceptions). This is not part of the current issue we're seeing but it does tend to generate some extra noise that you may not want in a production environment. So I modified the bridge's ExceptionHandlerAjaxImpl to do something similar: public void handle() throws FacesException { + FacesContext fc = FacesContext.getCurrentInstance(); + boolean isDevelopment = fc.isProjectStage(ProjectStage.Development); + // Before delegating, log all exceptions to the console. Iterable<ExceptionQueuedEvent> unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents(); Iterator<ExceptionQueuedEvent> itr = unhandledExceptionQueuedEvents.iterator(); @@ -58,7 +63,7 @@ public class ExceptionHandlerAjaxImpl extends ExceptionHandlerWrapper { if (exceptionQueuedEventContext != null) { Throwable throwable = exceptionQueuedEventContext.getException(); - if (throwable != null) { + if (throwable != null && isDevelopment) { logger.error(throwable); } The real problem seems to be in BridgePhaseResourceImpl (again, a class in the LiferayFaces Bridge) where exceptions are detected, wrapped, and rethrown as BridgeExceptions. This causes them to trickle up to the container (Liferay) which does it's own error handling and sticks the "status" page onto the end of the Ajax response. To verify this, I made a slight alteration so that the extra wrapping is only done if this is not currently an Ajax request: + //Only wrap/throw BridgeExceptions if it's not Ajax + boolean isAjax = facesContext.getPartialViewContext().isAjaxRequest(); + logger.info("is Ajax: " + isAjax); + - if (handledException != null) { + if (handledException != null && !isAjax) { throw new BridgeException(handledException); } // Otherwise, if there were any "unhandled" exceptions queued, then throw a BridgeException. Throwable unhandledException = getJSF2UnhandledException(facesContext); - if (unhandledException != null) { + if (unhandledException != null && !isAjax) { throw new BridgeException(unhandledException); } I'll follow up with Neil about what we will do about these two issues and create/link additional JIRAs as required.
        Hide
        Deryk Sinotte added a comment -

        Passed the information on to Neil who opened up a ticket in the Liferay system for this issue:

        http://issues.liferay.com/browse/FACES-1639

        Show
        Deryk Sinotte added a comment - Passed the information on to Neil who opened up a ticket in the Liferay system for this issue: http://issues.liferay.com/browse/FACES-1639
        Hide
        Deryk Sinotte added a comment -

        This has been verified to be fixed using a SNAPSHOT of LiferayFaces 3.1.3-ga4 (specifically 3.1.3-ga4-20130810.205323-5). Will leave this open until we have a production version that includes this fix.

        Show
        Deryk Sinotte added a comment - This has been verified to be fixed using a SNAPSHOT of LiferayFaces 3.1.3-ga4 (specifically 3.1.3-ga4-20130810.205323-5). Will leave this open until we have a production version that includes this fix.
        Hide
        Ken Fyten added a comment -

        Marking fixed pending LiferayFaces 3.1.3-ga4 final release.

        Show
        Ken Fyten added a comment - Marking fixed pending LiferayFaces 3.1.3-ga4 final release.

          People

          • Assignee:
            Deryk Sinotte
            Reporter:
            Deryk Sinotte
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: