Search code examples
ajaxjsfexceptionhandle

JSF Handle Exception in Ajax events


I followed this solution and works perfectlly when a ViewExpiredException happen, but when i inspect (firefox utility) the view error redered, i see that its replacing it, but just inside the body tag of the normal view, i mean, the view which was the cause of the Exception. The view error had a css class declared in his own body tag, but i don't know, why doesn't replace the whole view error, instead just take all the content (after his body tag) of the view error and inserted inside the body tag of the normal view?

To get this behavior, i have a login view (normal view that i refer above) and just have to wait until session expire, then i try to login (submit the form of the view) and this trigger a ExceptionHandler to render de view error.

Here are some snippet:

login.xhtml

    <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://primefaces.org/ui">

  <h:head>
    <f:facet name="first">
      <meta http-equiv="X-UA-Compatible" content="IE=edge" />
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
      <meta name="apple-mobile-web-app-capable" content="yes" />
    </f:facet>
    <title>PrimeFaces</title>
  </h:head>

  <h:body styleClass="login-body">
    <div class="login-panel ui-fluid">
      <div class="ui-g">
    <div class="ui-g-12 logo-container">
      <p:graphicImage name="images/logo-colored.png" library="theme-layout" />
      <h1>Login to Your Account</h1>
      <h2>WELCOME</h2>
    </div>
    <div class="ui-g-12">
      <p:inputText placeholder="User" />
    </div>
    <div class="ui-g-12">
      <p:password placeholder="Password" feedback="false"/>
    </div>
    <div class="ui-g-12 chkbox-container">
      <p:selectBooleanCheckbox id="remember-me" />
      <p:outputLabel for="remember-me" value="Remember Me"/>
    </div>
    <div class="ui-g-12 button-container">
      <p:commandButton type="submit" value="Log in" icon="fa fa-user" styleClass="orange-btn" action="#{menu.login}" update="frmLoginPromo">
    </div>
      </div>
    </div>

    <h:outputStylesheet name="css/layout-#{guestPreferences.layout}.css" library="theme-layout" />
  </h:body>

</html>

error.xhtml

    <!DOCTYPE html>
      <html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://primefaces.org/ui">

    <h:head>
        <f:facet name="first">
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
            <meta name="apple-mobile-web-app-capable" content="yes" />
        </f:facet>
        <title>PrimeFaces - Error </title>
    </h:head>

    <h:body styleClass="exception-body">
        <div class="exception-panel">
            <p:graphicImage name="images/icon-error.png" library="theme-layout" />

            <h1>Error Occured</h1>
            <p>An error occured, please try again later.</p>
        </div>

        <h:outputStylesheet name="css/layout-blue.css" library="theme-layout" />
    </h:body>

</html>

CustomExceptionHandler.java

    @Override
    public void handle() throws FacesException{
    final Iterator<ExceptionQueuedEvent> lclExceptionQueue = getUnhandledExceptionQueuedEvents().iterator();
    final FacesContext lclFacesContext = FacesContext.getCurrentInstance();
    final Map<String, Object> requestMap = lclFacesContext.getExternalContext().getSessionMap();
    while (lclExceptionQueue.hasNext()){
    ExceptionQueuedEvent event = lclExceptionQueue.next();
    ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
    Throwable lclThrowable = context.getException();


    try{
            if (lclThrowable instanceof ViewExpiredException){
 lclFacesContext.setViewRoot(lclFacesContext.getApplication().getViewHandler().createView(lclFacesContext, "/error.xhtml"));
                lclFacesContext.getPartialViewContext().setRenderAll(true);
                lclFacesContext.renderResponse();
            }
        }finally{
            lclExceptionQueue.remove();
        }
        }
        getWrapped().handle();
    }

Here is how looks like, after error view is rendered

Inspecte view error Please, tell me what am doing wrong?


Solution

  • Since you are already using PrimeFaces, I'd opt for not developing your own exceptionhandlers. PrimeFaces already has one that can handle both ajax and non-ajax requests.

    For the people not using PrimeFaces, I'd suggest to use the OmniFaces exceptionhandling

    For PrimeFaces 6.2 the documentation contains the information to configure this in chapter 11.3 (it is the same chapter btw for PF 6.1)

    In summary (ALL quotes are from the PF documentation)

    Configure an el resolver and the exceptionhandler

    <application>
         <el-resolver>
            org.primefaces.application.exceptionhandler.PrimeExceptionHandlerELResolver
        </el-resolver>
    </application>
    <factory>
        <exception-handler-factory>
            org.primefaces.application.exceptionhandler.PrimeExceptionHandlerFactory
        </exception-handler-factory>
    </factory>
    

    Configure errorpages if you want to in web.xml

    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/ui/error/error.jsf</location>
    </error-page>
    <error-page>
        <exception-type>javax.faces.application.ViewExpiredException</exception-type>
        <location>/ui/error/viewExpired.jsf</location>
    </error-page>
    

    You can then use information about the exceptions in EL in the error pages

    <h:outputText value="Message:#{pfExceptionHandler.message}" />
    <h:outputText value="#{pfExceptionHandler.formattedStackTrace}" escape="false" />
    

    There is more info, for which I'd suggest to consult the documentation.

    And for ajax exceptions you can do

    <p:ajaxExceptionHandler type="javax.faces.application.ViewExpiredException"
        update="exceptionDialog" onexception="PF('exceptionDialog').show();" />
    <p:dialog id="exceptionDialog" header="Exception: #{pfExceptionHandler.type} 
        occured!" widgetVar="exceptionDialog" height="500px">
        Message: #{pfExceptionHandler.message} <br/>
        StackTrace: <h:outputText value="#{pfExceptionHandler.formattedStackTrace}" escape="false" />
        <p:button onclick="document.location.href = document.location.href;"
            value="Reload!"/>
    </p:dialog>
    

    The configuration for OmniFaces is fairly similar.

    See also: