2

Simplified, my error handler looks like this:

@Override
public void handle() throws FacesException {
    Iterator<ExceptionQueuedEvent> unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents().iterator();
    FacesContext context = FacesContext.getCurrentInstance();

    if (unhandledExceptionQueuedEvents.hasNext()) {
        Throwable exception = unhandledExceptionQueuedEvents.next().getContext().getException();
        unhandledExceptionQueuedEvents.remove();
        context.getExternalContext().dispatch("/error.jsf");
    }

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

simplified template.xhtml

<html>
    <f:view>
    <h:head>
    <!-- some stuff here -->
    </h:head>
    <h:body>
        <div id="menu"><!-- menu content --></div>
        <div id="content">
            <ui:insert name="body"></ui:insert>
        </div>
    </h:body>
    </f:view>
</html>

error.xhtml

<html>
<ui:composition template="/template.xhtml">
    <ui:define name="body">
        ERROR
    </ui:define>
</ui:composition>
</html>

But when rendering error.jsf, templating with ui:composition goes very wrong. JSF renders template.xhtml up until around the , where it starts rendering the template all over again. The result is a page where menus are rendered twice, and all resources are included once again in the middle of the page. The start of it looks something like this:

<div id="menu></div>
<div<?xml version="1.0" encoding="UTF-8" ?="">
    <!-- Page included once more -->

This is followed by an infinite loop on the server, causing millions of log rows containing the following bit over and over again

at com.xdin.competence.web.error.ErrorHandler.handleException(ErrorHandler.java:123) [:]
at com.xdin.competence.web.error.ErrorHandler.handle(ErrorHandler.java:108) [:]
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:115) [:2.0.3-]
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:107) [:2.0.3-]
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:114) [:2.0.3-]
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:308) [:2.0.3-]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:324) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:734) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:541) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:479) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:407) [:6.0.0.Final]
at com.sun.faces.context.ExternalContextImpl.dispatch(ExternalContextImpl.java:542) [:2.0.3-]
at javax.faces.context.ExternalContextWrapper.dispatch(ExternalContextWrapper.java:89) [:2.0.3-]
at org.jboss.seam.faces.environment.org$jboss$weld$bean-jboss$classloader:id="vfs:$$$C:$JBoss$jboss-6$0$0$Final$server$career$deploy$career-portal$war"-ManagedBean-class_org$jboss$seam$faces$environment$SeamExternalContext_$$_WeldClientProxy.dispatch(org$jboss$weld$bean-jboss$classloader:id="vfs:$$$C:$JBoss$jboss-6$0$0$Final$server$career$deploy$career-portal$war"-ManagedBean-class_org$jboss$seam$faces$environment$SeamExternalContext_$$_WeldClientProxy.java) [:3.0.1.Final]

Any idea of what the cause could be?

Rasmus Franke
  • 4,434
  • 8
  • 45
  • 62

1 Answers1

9

You should not use ExternalContext#dispatch() to render a JSF view. It should only be used to forward to a non-JSF resource. You should instead set the desired JSF view by FacesContext#setViewRoot() and let JSF render it.

ViewHandler viewHandler = context.getApplication().getViewHandler();
UIViewRoot viewRoot = viewHandler.createView(context, viewId);
context.setViewRoot(viewRoot);
context.renderResponse();

Or, if you're currently already sitting in the render response, then it's too late to let JSF re-render it. You'd need to build and render the new view manually. In that case, replace the last line by:

ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(context, viewId);
vdl.buildView(context, viewRoot);
context.getApplication().publishEvent(context, PreRenderViewEvent.class, viewRoot);
vdl.renderView(context, viewRoot);
context.responseComplete();

Note that this is in turn too late if the response is already committed. You may want to check that beforehand as well.

You may find the source code of the OmniFaces FullAjaxExceptionHandler helpful to get some insight.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • My code is actually a modification of FullAjaxExceptionHandler, modified to use my own error mapping instead of fetching it from web.xml, since you cannot use jsf views as web.xml error pages. Is it "random" if the response is commited or not? FullAjaxExceptionHandler seems to check for it and take different actions. – Rasmus Franke Mar 18 '13 at 12:39
  • A response is committed when a `flush()` is been performed during rendering of the response and thus the headers and a part of content is already sent to the client. This is a point of no return. This may in case of exceptions happen when an exception is thrown in midst of rendering of the response, e.g. due to some exception-sensitive business logic in a getter method. – BalusC Mar 18 '13 at 12:40
  • Could I use the code in handleAjaxException for non-ajax exceptions aswell? Since it seems to handle most scenarios. – Rasmus Franke Mar 18 '13 at 12:41
  • By the way, how so "cannot use JSF views as web.xml error pages"? The `FullAjaxExceptionHandler` **requires** them to be JSF views! See also code examples in showcase. Non-ajax exceptions are already handled by `` entries in `web.xml` in combination with [`FacesExceptionFilter` of OmniFaces](http://showcase.omnifaces.org/filters/FacesExceptionFilter). No `ExceptionHandler` is necessary for that. – BalusC Mar 18 '13 at 12:42
  • Should have added when using Seam. There seems to be a bug that disables use of jsf pages in web.xml when using it. I still use JSF views, but I modified `String errorPageLocation = WebXml.INSTANCE.findErrorPageLocation(exception);` to use my own mapping – Rasmus Franke Mar 18 '13 at 12:46
  • Okay. Sorry, Seam is beyond me, can't/won't go further in detail with that. – BalusC Mar 18 '13 at 12:48
  • Tried using your code examples when throwing a simple runtime exception from a controller. The first example ended up the html just ending midpage rendering only half my template. The 2nd example using vdl ended up with the same issue as when using dispatch! Any ideas? – Rasmus Franke Mar 18 '13 at 13:49
  • So, you're trying to handle an exception during render response phase while the response is already committed. You can't do that. Period. Your best bet is to increase Facelets and container's buffer size so that the response won't be committed too soon. Note that such an exception indicates a bug in **your own** code, not enduser's mistake. This should be fixed ASAP. If the exception is at its own legit, then it was the wrong moment to perform the business logic during render response. Move the exception-sensitive business job to e.g. `preRenderView` listener or so. – BalusC Mar 18 '13 at 13:51
  • To clarify, the runtime exception was placed there on purpose to test my error handler. Would changing a buffer size really change the phase which the error handler is called? The whole point of the error handler is to catch unhandled exceptions thrown from controllers while rendering the page. – Rasmus Franke Mar 18 '13 at 14:06
  • No, increasing the buffer size would postpone the flush (a flush is automatically done when the length of the written content exceeds the buffer size), so that the response isn't committed at the point the exception occurs. This allows you to clear out any written content to the response buffer via `ExternalContext#responseReset()` and then write new content to it (so that you don't end up with a mixup of a part of original content and thereafter the error page in the response body). See also `FullAjaxExceptionHandler` source code. – BalusC Mar 18 '13 at 14:49