© Bauke Scholtz, Arjan Tijms 2018

Bauke Scholtz and Arjan Tijms, The Definitive Guide to JSF in Java EE 8, https://doi.org/10.1007/978-1-4842-3387-0_9

9. Exception Handling

Bauke Scholtz and Arjan Tijms2

(1)Willemstad, Curaçao

(2)Amsterdam, Noord-Holland, The Netherlands

Sometimes things can unexpectedly go wrong. In Java, that usually manifests as an Exception being thrown. In Java EE, it’s basically no different. The issue is how and when to properly handle them. By default, any uncaught exception during an HTTP request will end up in a default error page provided by the application server. Figure 9-1 shows what WildFly’s default HTTP 500 error page looks like:

A454457_1_En_9_Fig1_HTML.jpg
Figure 9-1 The default HTTP 500 error page of WildFly 11.0.0

The JSF (JavaServer Faces) implementation being used may even provide its own default error page. Both Mojarra and MyFaces provide an internal default implementation based on the javax.faces.context.ExceptionHandler API,1 which is only shown when the JSF project stage is set to Development. Figure 9-2 shows how that page looks for Mojarra.

A454457_1_En_9_Fig2_HTML.jpg
Figure 9-2 The default HTTP 500 error page of Mojarra 2.3.3 in development stage

It not only includes the stack trace but also the textual representation of the JSF component tree and any scoped variables, which might be helpful in nailing down the root cause, although, in reality, the stack trace alone and re-executing the use case with a debugger is much more helpful than that.

Custom Error Pages

While useful to the average web developer , those default error pages are, quite frankly, scary to the average end user. The “look ’n’ feel” is completely off from the rest of the site and the text is like abracadabra to the average end user. Such an error page doesn’t even provide escape points for the end user. The disgruntled end user cannot quickly find its way to the home or contact page. Fortunately for the end user, you can override those default error pages by including an error page in the web application and registering its location in an <error-page> entry in web.xml.

<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/errorpages/500.xhtml</location>
</error-page>

The custom error page is purposefully being placed in a /WEB-INF folder so that end users can’t access or bookmark them directly. By default, the servlet container will set error page related attributes in the request scope whose keys are defined as javax.servlet.RequestDispatcher.ERROR_XXX constants.2 This way you can, if necessary, include them in the custom error page. Following is an example:

<dl>
    <dt>Request URI</dt>
    <dd>#{requestScope['javax.servlet.error.request_uri']}</dd>
    <dt>Exception type</dt>
    <dd>#{requestScope['javax.servlet.error.exception']['class']}</dd>
    <dt>Exception message</dt>
    <dd>#{requestScope['javax.servlet.error.exception'].message}</dd>
    <dt>Stack trace</dt>
    <dd><pre>#{
        facesContext.externalContext.response.writer.flush()
     }#{
        requestScope['javax.servlet.error.exception'].printStackTrace
            (facesContext.externalContext.response.writer)
     }</pre></dd>
</dl>

Note the trick to print the stack trace. It’s important that the response writer is flushed before printing the stack trace, and that there’s no whitespace in template text outside the EL (Expression Language) expressions within the <pre> element, otherwise it would be appended to the stack trace.

Coming back to the scariness of such an error page, you’d better hide away all the error detail behind a condition that evaluates only true when the JSF project stage equals Development. First set an application-scoped shortcut variable in some master template:

<c:set var="DEV" scope="application"
    value="#{facesContext.application.projectStage eq 'Development'}">
</c:set>

Now you can conditionally display technical information in the error page when the JSF project stage equals Development.

<c:if test="#{DEV}">
    <h3>Error detail for developer</h3>
    <dl>
        ...
    </dl>
</c:if>

In any case, it’s very important that the error page is entirely stateless. In other words, a decent error page may not contain any JSF forms, not even a logout form. Not only will you avoid the risk that the form submit fails because the initial exception was actually caused by a corrupted JSF state, but those forms will actually submit to the wrong URL (uniform resource locator), namely, the one of the error page itself. As the error page is hidden in the /WEB-INF folder, the form submit would only result in a 404 error. Instead of moving the error page outside the /WEB-INF folder, you could work around the logout case by using a plain HTML form which submits to a plain Servlet. The SecurityContext is also just injectable over there, as are session-scoped managed beans , if any.

Ajax Exception Handling

By default, when an exception occurs during a JSF Ajax request, the end user would not get any form of feedback whether or not the action was successfully performed. In Mojarra , only when the JSF project stage is set to Development, the end user would see a bare JavaScript alert with only the exception type and message (see Figure 9-3).

A454457_1_En_9_Fig3_HTML.jpg
Figure 9-3 The JavaScript alert when an exception is thrown during an Ajax request in Mojarra 2.3.3 in Development stage

This isn’t terribly useful. And in the Production stage the end user wouldn’t even get any feedback. The web application would fail silently, leaving the end user behind as if nothing had happened, which is just confusing and bad for user experience. It would make more sense if exceptions during JSF Ajax requests are handled the same way as exceptions during synchronous requests, which reuse exactly the same error page as the one declared as <error-page> in web.xml. In other words, the end user should be able to see the error page in full glory. This can be achieved by creating a custom ExceptionHandler implementation which basically instructs JSF to create a new UIViewRoot based on the error page and then build and render it. It’s only quite a bit of code . At its simplest it can look as follows:

public class AjaxExceptionHandler extends ExceptionHandlerWrapper {

    public AjaxExceptionHandler(ExceptionHandler wrapped) {
        super(wrapped);
    }


    @Override
    public void handle() {
        handleAjaxException(FacesContext.getCurrentInstance());
        getWrapped().handle();
    }


    protected void handleAjaxException(FacesContext context) {
        Iterator<ExceptionQueuedEvent> unhandledExceptionQueuedEvents =
            getUnhandledExceptionQueuedEvents().iterator();


        if (context == null
            || context.getExternalContext().isResponseCommitted()
            || !context.getPartialViewContext().isAjaxRequest()
            || !unhandledExceptionQueuedEvents.hasNext()
        ) {
            return;
        }


        Throwable exception = unhandledExceptionQueuedEvents
            .next().getContext().getException();


        while (exception.getCause() != null
            && (exception instanceof FacesException
                || exception instanceof ELException)
        ) {
            exception = exception.getCause();
        }


        ExternalContext external = context.getExternalContext();
        String uri = external.getRequestContextPath()
            + external.getRequestServletPath();
        Map<String, Object> requestScope = external.getRequestMap();
        requestScope.put(RequestDispatcher.ERROR_REQUEST_URI, uri);
        requestScope.put(RequestDispatcher.ERROR_EXCEPTION, exception);


        String viewId = "/WEB-INF/errorpages/500.xhtml";
        Application application = context.getApplication();
        ViewHandler viewHandler = application.getViewHandler();
        UIViewRoot viewRoot = viewHandler.createView(context, viewId);
        context.setViewRoot(viewRoot);


        try {
            external.responseReset();
            ViewDeclarationLanguage viewDeclarationLanguage =
                viewHandler.getViewDeclarationLanguage(context, viewId);
            viewDeclarationLanguage.buildView(context, viewRoot);
            context.getPartialViewContext().setRenderAll(true);
            viewDeclarationLanguage.renderView(context, viewRoot);
            context.responseComplete();
        }
        catch (IOException e) {
            throw new FacesException(e);
        }
        finally {
            requestScope.remove(RequestDispatcher.ERROR_EXCEPTION);
        }


        unhandledExceptionQueuedEvents.remove();

        while (unhandledExceptionQueuedEvents.hasNext()) {
            unhandledExceptionQueuedEvents.next();
            unhandledExceptionQueuedEvents.remove();
        }
    }


    public static class Factory extends ExceptionHandlerFactory {

        public Factory(ExceptionHandlerFactory wrapped) {
            super(wrapped);
        }


        @Override
        public ExceptionHandler getExceptionHandler() {
            return new AjaxExceptionHandler
                (getWrapped().getExceptionHandler());
        }
    }
}

The handleAjaxException() will first check if there is a faces context, if the response isn’t yet committed, if the request is an Ajax request, and if there’s any unhandled exception event in the queue. If none of those conditions matches, it will return and let JSF continue as usual.

The HTTP response is considered committed when the response or a part thereof has physically already been sent to the client. This is a point of no return. You can’t take the already sent bytes back. This may happen when the exception occurs halfway during the render response phase. The first half of the HTTP response may already have been sent to the client. Also the default exception handler of JSF and the server can’t deal with it. Effectively, the client gets a half-baked HTML page. Best what you could do to avoid this is to make sure that you aren’t performing business logic in getter methods of backing beans, which on its own is always a bad idea, and that backing beans are initialized as soon as possible. That could be achieved by executing exception-sensitive business logic in <f:viewAction> instead of @PostConstruct. Another option is to increase the HTTP response buffer size to match the size of the generated HTML response of the largest exception-sensitive page. Assuming that it’s 100 kB, the following web.xml context parameter can be used.

<context-param>
    <param-name>javax.faces.FACELETS_BUFFER_SIZE</param-name>
    <param-value>102400</param-value> <!-- 100 kB. -->
</context-param>

The next step in handleAjaxException() is extracting the root cause of interest from the unhandled exception events queue. Any exception that occurs during the processing of the JSF life cycle will be wrapped in javax.faces.FacesException. Any exception that occurs during the evaluation of an EL expression will be wrapped in javax.el.ELException. Those are not our interest.

Next, the #{requestScope['javax.servlet.error.request_uri']} and #{requestScope['javax.servlet.error.exception']} variables will be set so that the error page can access them. Also the UIViewRoot instance representing the error page will be created with help of the ViewHandler and set in the JSF context. You could, if necessary, conditionally prepare the view ID of the error page based on the actual root cause of the exception. For example:

String viewId;

if (exception instanceof ViewExpiredException) {
    viewId = "/WEB-INF/errorpages/expired.xhtml";
}
else {
    viewId = "/WEB-INF/errorpages/500.xhtml";
}

Coming back to the handleAjaxException(), in the try block, the HTTP response buffer will be cleared, the UIViewRoot will be populated with components, the Ajax context will be instructed to render the entire view, the UIViewRoot will be rendered, the JSF context will be instructed that the response is already manually taken care of so that it won’t perform any navigation, and finally the exception will be removed from the request scope. The removal of the exception in the finally block is not mandatory, but servlet containers exist which consider this a trigger to write an internal error page to the response, such as Tomcat.

Finally, the queue of unhandled exception events will be drained. This is also not mandatory but done on purpose so that it matches the default behavior of web.xml-configured error pages, and also to prevent any next ExceptionHandler in the chain from handling any remaining exception events.

In order to get it to run, you actually need to create a factory as well. As many other application-wide customizations in JSF, custom exception handlers can only be registered through a factory. It might look verbose, but that’s just part of the design. In this specific case, it allows you to return a different exception handler implementation depending on some global configuration setting. You can find the Factory as a nested class in the bottom of the previously shown AjaxExceptionHandler class. It extends from javax.faces.context.ExceptionHandlerFactory 3 and can be registered in faces-config.xml as follows:

<factory>
    <exception-handler-factory>
        com.example.project.exceptionhandler.AjaxExceptionHandler$Factory
    </exception-handler-factory>
</factory>

By the way, it must be said that such an exception handler is also suitable on non-Ajax requests. You just have to remove the PartialViewContext#isAjaxRequest() check. You only need to keep in mind to manually set the HTTP response status code to 500 depending on whether or not it’s an Ajax request. Do this after the ExternalContext#responseReset() line.

if (!context.getPartialViewContext().isAjaxRequest()) {
    external.setResponseStatus
        (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}

When you do so on an Ajax request , the JSF Ajax script won’t process the Ajax response as you’d expect. Instead of displaying the error page, it will trigger the onerror handler.

ViewExpiredException Handling

If you’ve worked with JSF before, then the chance is great that you’ve seen or heard about a ViewExpiredException . To the point, it will be thrown when the JSF view state associated with the postback cannot be found in the HTTP session any more—in other words, when the javax.faces.STATE_SAVING_METHOD context parameter is set to its default value of “server”, and the end user submits a JSF form whose view state cannot be found any more on the server side. Note that when the context parameter is set to “client”, you still need a “jsf/ClientSideSecretKey” environment entry in order to avoid expired views when the server restarts. This all is elaborated in the section “View State” in Chapter 3. Also, in the section “@ViewScoped” in Chapter 8, you can find how to configure the amount of views and managed beans JSF will save in the HTTP session.

There are several circumstances where a ViewExpiredException may unexpectedly occur. All of them are related to page navigation and the browser cache while the end user has recently logged out from the web application. Normally, the HTTP session is, during logout, invalidated as a security measure, and the end user is redirected to some landing page. When the previously visited web page is cacheable, and the end user presses the browser’s back button after logout, then the end user may successfully get to see the previously visited web page from the browser cache. If it contains any JSF form, then its javax.faces.ViewState hidden input field will actually refer a view state which does not exist in the current session any more. When the end user submits such JSF form, then JSF will inevitably throw a ViewExpiredException.

Although this is an excellent security measure, the end user may get confused as the previously visited web page actually got successfully loaded in end user’s experience. This is not good for user experience. You’d therefore best make sure that stateful JSF pages are not cacheable, so that the web browser is forced to actually hit the web server when the end user presses the back button and thus load a fresh new page with a view state which is actually valid in the current HTTP session. This can be achieved with a servlet filter which sets specific response headers which instruct the client not to cache the HTTP response. Following is an example of such a servlet filter:

@WebFilter(servletNames="facesServlet")
public class NoCacheFilter implements Filter {


    @Override
    public void doFilter
        (ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException
    {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String resourcePath = request.getContextPath()
            + ResourceHandler.RESOURCE_IDENTIFIER;


        if (!request.getRequestURI().startsWith(resourcePath)) {
            response.setHeader
                ("Cache-Control", "no-store, must-revalidate");
        }


        chain.doFilter(request, response);
    }
}

Basically, it hooks on all requests which are going to hit the FacesServlet, provided that it’s configured on a servlet name of “facesServlet” in web.xml as follows:

<servlet>
    <servlet-name>facesServlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>

Alternatively, if you have divided your web application into a public area with only stateless JSF pages and a restricted area with only stateful JSF pages, then you could also map this filter on a specific URL pattern which matches only the stateful section, such as /admin/*.

@WebFilter("/admin/*")

The filter example checks first if the HTTP request doesn’t represent a JSF resource request before setting the cache control header. Those requests are identified by the /javax.faces.resource path after the context path, which is available by the ResourceHandler#RESOURCE_IDENTIFIER constant. This is automatically used when you use a JSF resource component, such as <h:graphicImage>, <h:outputScript>, or <h:outputstyleSheet>. (See also the section “Resource Components” in Chapter 6.) You don’t want to disable the browser cache on them as that would otherwise impact the page loading performance.

The cache control header being set is actually only recognized by HTTP 1.1-capable clients. HTTP 1.1 was introduced in 1997. In case you would like to cover HTTP 1.0 clients as well, which these days are generally only ancient proxies or poor users who can’t get something better than Internet Explorer 6.0, then you’d best add two more response headers.

response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.

Now, with this filter in place, the end user won’t any more get to see any JSF page from the browser cache and thus can no longer get a ViewExpiredException on them. However, there are still cases where a ViewExpiredException is unavoidable. One such case is an end user who has several JSF pages open in different browser tabs and logs out in one of them and then performs an action in another tab without refreshing the page beforehand. For such a case, you’d really need a “Sorry, your session has timed out” error page. This can easily be configured as another error page in web.xml as follows:

<error-page>    
    <exception-type>
        javax.faces.application.ViewExpiredException
    </exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>

Do not let it point to a public JSF page, such as a login page. In case you really need it to be a public JSF page, use a meta refresh header in the error page which redirects the end user further to a public JSF page .

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Session expired</title>
        <meta http-equiv="refresh"
            content="0;url=#{request.contextPath}/login.xhtml" />
    </head>
    <body>
        <h1>Sorry, your session has timed out</h1>
        <h3>You will be redirected to login page</h3>
        <p>
             <a href="#{request.contextPath}/login.xhtml">
                 Click here if the redirect didn't work,
                 or when you’re impatient.
             </a>
         </p>
    </body>
</html>

Note that the “0” in the meta refresh content represents the amount of seconds before redirect. Thus, “0” means “redirect immediately.” You can use, for example, “3” to let the browser wait 3 seconds before the redirect.

Keep in mind also to configure this location in any custom exception handler such as the AjaxExceptionHandler demonstrated in the previous section. You also need to make sure that your “general” error page is mapped on an error code of 500 instead of an exception type of, e.g., java.lang.Exception or java.lang.Throwable; otherwise all exceptions wrapped in ServletException would still end up in the general error page. JSF will wrap any exception in a ServletException when it’s thrown during a synchronous (non-Ajax) postback instead of an asynchronous (Ajax) postback. The web.xml error page mechanism will only extract the root cause from the ServletException for a second pass through error pages by exception type when no matching error page by exception type is found.

A completely different alternative to avoid ViewExpiredException is to use stateless JSF views. This way nothing of the JSF view state will be saved and the JSF views will never expire but will just be rebuilt from scratch on every request. You can turn on stateless views by setting the transient attribute of <f:view> to true. This is elaborated in the section “Stateless Forms” in Chapter 4.

Regardless of the solution, make sure you do not use the Mojarra-specific com.sun.faces.enableRestoreView11Compatibility context parameter. This will basically turn on JSF 1.0/1.1 behavior with regard to expired views. It won’t throw the ViewExpiredException any more, but neither does it actually restore the original view state at all. It basically recreates the view and all associated view scoped beans from scratch and hereby thus loses all of original state. As the application will behave in a confusing way (“Hey, where are my input values..??”), this is bad for user experience. Better use stateless JSF views instead so that you can manage the application in specific views only instead of for all views.

IOException Handling

Some methods of the underlying HttpServletRequest and HttpServletResponse objects may throw an IOException . Usually, that only happens when the network connection unexpectedly breaks: for example, when the end user abruptly stops the HTTP request by pressing the Esc button in the web browser, or the end user abruptly navigates to a different web page while the current page is still loading, or even when the end user’s computer or network cable catches fire. Those circumstances are really unavoidable. For you, as web application developer, it’s best to let any IOException bubble up into the servlet container. In other words, there’s absolutely no need to catch it as follows:

public void someAjaxListener() {
    try {
        FacesContext.getCurrentInstance()
            .getExternalContext().redirect(url);
    }
    catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}

Instead, just let it go.

public void someAjaxListener() throws IOException {                                                                                      
    FacesContext.getCurrentInstance()
        .getExternalContext().redirect(url);
}

EJBException Handling

Sometimes, invoking service methods may also cause an exception. Generally, those are on purpose, such as “entity not found,” “unique constraint violation,” “invalid user credentials”, “entity is in meanwhile modified by another user,” etc. By default, any non-application-specific RuntimeException, such as NullPointerException and even JPA’s PersistenceException which is thrown from an EJB service method, is wrapped in an EJBException . This makes it clumsy to nail down the actual root cause in the JSF action method.

public void addProduct() {
    FacesMessage message;


    try {
        Long id = productService.create(product);
        message = new FacesMessage(FacesMessage.SEVERITY_INFO,
            "Product successfully saved", "ID is " + id);
    }
    catch (EJBException e) {
        if (e.getCause() instanceof ConstraintViolationException) {
            message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                "Duplicate product!", e.getMessage());
            context.validationFailed();


        }
        else {
            throw e;
        }
    }


    context.addMessage(null, message);
}

This is not the best practice . Not only would you need to determine the root cause of the EJB exception by inspecting Exception#getCause(), the web.xml error page mechanism would also not be able to show a specific error page for, for example, a ConstraintViolationException, because it’s wrapped in an EJBException. In order to get EJB to throw it unwrapped, you need to create a custom exception superclass first which you then annotate with @javax.ejb.ApplicationException.4

@ApplicationException(rollback=true)
public abstract class BusinessException extends RuntimeException {
    public BusinessException() {
        super();
    }
    public BusinessException(Exception cause) {
        super(cause);
    }
}

Note the rollback=true attribute on the annotation. This is very important in case you’d like the EJB container to roll back any active transaction from where this exception is being thrown. Following are some examples of subclasses of this custom business exception.

public abstract class QueryException extends BusinessException {}
public class EntityNotFoundException extends QueryException {}
public class DuplicateEntityException extends QueryException {}
public abstract class CredentialsException extends BusinessException {}
public class InvalidUsernameException extends CredentialsException {}
public class InvalidPasswordException extends CredentialsException {}

Note that you don’t necessarily need to repeat the @ApplicationException over all subclasses as it’s already @Inherited. Following are some concrete use cases on which those exceptions could be thrown:

public User getById(Long id) {
    try {
        return entityManager
            .createQuery("FROM User u WHERE u.id = :id", User.class)
            .setParameter("id", id)
            .getSingleResult();
    }
    catch (NoResultException e) {
        throw new EntityNotFoundException(e);
    }
}


public Optional<User> findByEmail(String email) {
    try {
        return Optional.of(entityManager
            .createQuery("FROM User u"
                + " WHERE u.email = :email", User.class)
            .setParameter("email", email)
            .getSingleResult());
    }
    catch (NoResultException e) {
        return Optional.empty();
    }
}


public User getByEmailAndPassword(String email, String password) {
    User user = findByEmail(email)
        .orElseThrow(InvalidUsernameException::new);
    Credentials credentials = user.getCredentials();
    byte[] passwordHash = digest(password, credentials.getSalt());


    if (!Arrays.equals(passwordHash, credentials.getPasswordHash())) {
        throw new InvalidPasswordException();
    }


    return user;
}


public Long create(User user) {
    if (findByEmail(user.getEmail()).isPresent()) {
        throw new DuplicateEntityException();
    }
    entityManager.persist(user);
    return user.getId();
}

In the JSF backing bean action methods, you could then handle them accordingly.

public void signup() {
    FacesMessage message;


    try {
        userService.create(product);
        message = new FacesMessage("You are successfully signed up!");
    }
    catch (DuplicateEntityException e) {
        message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
            "Sorry, username already taken!", e.getMessage());
        context.validationFailed();
    }


    context.addMessage(null, message);
}

In order to further reduce the boilerplate code , you could even let all business exceptions go and have a custom exception handler to handle them.

public class BusinessExceptionHandler extends ExceptionHandlerWrapper {

    public BusinessExceptionHandler(ExceptionHandler wrapped) {
        super(wrapped);
    }


    @Override
    public void handle() {
        handleBusinessException(FacesContext.getCurrentInstance());
        getWrapped().handle();
    }


    protected void handleBusinessException(FacesContext context) {
        Iterator<ExceptionQueuedEvent> unhandledExceptionQueuedEvents =
            getUnhandledExceptionQueuedEvents().iterator();


        if (context == null
            || !unhandledExceptionQueuedEvents.hasNext()
        ) {
            return;
        }


        Throwable exception = unhandledExceptionQueuedEvents
            .next().getContext().getException();


        while (exception.getCause() != null
            && (exception instanceof FacesException
                || exception instanceof ELException)
        ) {
            exception = exception.getCause();
        }


        if (!(exception instanceof BusinessException)) {
            return;
        }


        context.addMessage(null, new FacesMessage(
            FacesMessage.SEVERITY_FATAL,
            exception.toString(),
            exception.getMessage()));
        context.validationFailed();
        context.getPartialViewContext()
            .getRenderIds().add("globalMessages");


        unhandledExceptionQueuedEvents.remove();

        while (unhandledExceptionQueuedEvents.hasNext()) {
            unhandledExceptionQueuedEvents.next();
            unhandledExceptionQueuedEvents.remove();
        }
    }


    public static class Factory extends ExceptionHandlerFactory {

        public Factory(ExceptionHandlerFactory wrapped) {
            super(wrapped);
        }


        @Override
        public ExceptionHandler getExceptionHandler() {
            return new BusinessExceptionHandler
                (getWrapped().getExceptionHandler());
        }
    }


}

Yes, it’s indeed similar to the AjaxExceptionHandler as shown in the earlier section “Ajax Exception Handler .” However, the first difference is that it doesn’t skip handling the exception when the response is already committed or when it’s not an Ajax request. The second difference is the logic between extracting the root cause of the exception and draining the remaining unhandled exception events. This BusinessExceptionHandler will instead check if the root cause is an instance of BusinessException and if so, it will add a faces message and instruct JSF to explicitly update the component identified by “globalMessages”, which should refer to a global messages component in your master template something like the following:

<h:messages id="globalMessages" globalOnly="true" />

Ultimately, all business exception-related faces messages will end up there. You might have noticed that the faces context is explicitly marked as “validation failed” via the FacesContext#validationFailed() call. This is generally useful in case any code in the Facelet template is relying on it. If you would like to run it together with the AjaxExceptionHandler for non-business exceptions, then you need to register it in the faces-config.xml after the AjaxExceptionHandler. Anything that is declared later in the faces-config.xml will effectively wrap the previously declared one. This also applies to custom exception handler factories. When the BusinessExceptionHandler confirms that the exception is not an instance of BusinessException, then it will leave the unhandled exception in the queue and return from the method and finally delegate to the handle() method of the wrapped ExceptionHandler, which shall be the AjaxExceptionHandler.

<factory>
    <exception-handler-factory>
        com.example.project.AjaxExceptionHandler$Factory
    </exception-handler-factory>
    <exception-handler-factory>
        com.example.project.BusinessExceptionHandler$Factory
    </exception-handler-factory>
</factory>

With the BusinessExceptionHandler in place, you could further reduce the backing bean action method as follows:

public void signup() {
    userService.create(product);
    context.addMessage(null,
        new FacesMessage("You are successfully signed up!");
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.248.69