JSF: Cannot catch ViewExpiredException
Asked Answered
T

2

8

I'm developing a JSF 2.0 application on Glassfish v3 and i'm trying to handle the ViewExpiredException. But whatever i do, i always get a Glassfish error report instead of my own error page.

To simulate the occurrence of the VEE, i inserted the following function into my backing bean, which fires the VEE. I'm triggering this function from my JSF page through a commandLink. The Code:

@Named
public class PersonHome {
  (...)
  public void throwVEE() {
    throw new ViewExpiredException();
  }
}

At first i tried it by simply adding an error-page to my web.xml:

<error-page>
  <exception-type>javax.faces.application.ViewExpiredException</exception-type>
  <location>/error.xhtml</location>
</error-page>  

But this doesn't work, i'm not redirected to error but i'm shown the Glassfish errorpage, which shows a HTTP Status 500 page with the following content:

description:The server encountered an internal error () that prevented it from fulfilling this request.
exception: javax.servlet.ServletException: javax.faces.application.ViewExpiredException
root cause: javax.faces.el.EvaluationException:javax.faces.application.ViewExpiredException
root cause:javax.faces.application.ViewExpiredException

Next thing i tried was to write ExceptionHandlerFactory and a CustomExceptionHandler, as described in JavaServerFaces 2.0 - The Complete Reference. So i inserted the following tag into faces-config.xml:

<factory>
  <exception-handler-factory>
    exceptions.ExceptionHandlerFactory
  </exception-handler-factory>
</factory>

And added these classes: The factory:

package exceptions;

import javax.faces.context.ExceptionHandler;

public class ExceptionHandlerFactory extends javax.faces.context.ExceptionHandlerFactory {

    private javax.faces.context.ExceptionHandlerFactory parent;

    public ExceptionHandlerFactory(javax.faces.context.ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler result = parent.getExceptionHandler();
        result = new CustomExceptionHandler(result);
        return result;
    }

}

The custom exception handler:

package exceptions;

import java.util.Iterator;

import javax.faces.FacesException;
import javax.faces.application.NavigationHandler;
import javax.faces.application.ViewExpiredException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;

class CustomExceptionHandler extends ExceptionHandlerWrapper {

    private ExceptionHandler parent;

    public CustomExceptionHandler(ExceptionHandler parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return this.parent;
    }

    @Override
    public void handle() throws FacesException {
        for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
            ExceptionQueuedEvent event = i.next();
            System.out.println("Iterating over ExceptionQueuedEvents. Current:" + event.toString());
            ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
            Throwable t = context.getException();
            if (t instanceof ViewExpiredException) {
                ViewExpiredException vee = (ViewExpiredException) t;
                FacesContext fc = FacesContext.getCurrentInstance();

                NavigationHandler nav =
                        fc.getApplication().getNavigationHandler();
                try {
                    // Push some useful stuff to the flash scope for
                    // use in the page
                    fc.getExternalContext().getFlash().put("expiredViewId", vee.getViewId());

                    nav.handleNavigation(fc, null, "/login?faces-redirect=true");
                    fc.renderResponse();

                } finally {
                    i.remove();
                }
            }
        }
        // At this point, the queue will not contain any ViewExpiredEvents.
        // Therefore, let the parent handle them.
        getWrapped().handle();
    }
}

But STILL i'm NOT redirected to my error page - i'm getting the same HTTP 500 error like above. What am i doing wrong, what could be missing in my implementation that the exception isn't handled correctly? Any help highly appreciated!

EDIT

Ok, i'm honest. In fact, my code is actually written in Scala, but thats a long story. i thought it was a Java problem all the time. The REAL error in this case was my own stupidness. In my (Scala) code, in CustomExceptionHandler, i forgot to add the line with the "i.remove();" So the ViewExpiredException stayed in the UnhandledExceptionsQueue after handling it, and it "bubbled up". And when it bubbles up, it becomes a ServletException.

I'm really sorry for confusing you both!

Threedimensional answered 5/6, 2010 at 14:20 Comment(3)
"So you CAN throw a ViewExpiredException in ANY JSF-phase" : by default, when you do this inside a bean as in your 1st example, this will be wrapped in a FacesException and it will not end up in the expected error page.Dish
Thanks. You're right, i removed the any-jsf-phase-statement from my last edit.Threedimensional
Does your last edited post make it working ? I need to set the same redirection on ViewExpired error as wellNekton
D
14

This test case is bogus. The ViewExpiredException is usually only thrown during restoring the view (because it's missing in the session), not during rendering the response nor instantiating the bean. In your case this exception is thrown during instantiating the bean and this exception is been wrapped in a ServletException.

The real ViewExpiredException is usually only thrown when you send a HTTP POST request to the server while the HTTP session is expired. So there are basically two ways to reproduce this reliably:

  1. Open a JSF page with a POST form (h:form is by default already POST) in a webbrowser, shutdown the server and clean its work directory (important, because most servers will serialize open sessions to disk on shutdown and unserialize them on startup), restart the server and submit the already opened form. A ViewExpiredException will be thrown.

  2. Set the <session-timeout> in web.xml to 1 minute and submit the form over 1 minute after opening the JSF page with the POST form. This will throw the ViewExpiredException as well.

Dish answered 5/6, 2010 at 22:29 Comment(2)
Thanks BalusC. But why is this not redirected correctly to the error page ?Polygyny
Because a ServletException is been thrown.Dish
P
2

I am not an expert. These are just wild guesses or suggestions.

1) Try redirecting to a standard HTML page to see if that works 2) Based on this , it should work with the first approach itself, try writing a JSF PhaseListener and throw the same exception in the RESTORE VIEW Phase.Right now, you are throwing either in the INVOKE APPLICATION or UPDATE MODEL Phase. 3) By a sysout , make sure that the error page is configured - using Servlet Context (I have not tried this but It should be possible)

Even I am curious what could be the problem is!!!

Polygyny answered 5/6, 2010 at 14:51 Comment(2)
thanks for your thoughts! I never used PhaseListeners before, good to know them. I just implemented one, which throws the exception in the RESTORE_VIEW-phase - and now it actually redirects correctly! My exception is thrown in the InvokeApplication phase. I wonder why exceptions thrown in this phase aren't catched correctly?Threedimensional
Hmm no it's not the phase why it didn't work, i also get the Status-500 when i throw the exception in the RESTORE_VIEW-phase (one time it worked, but not anymore) Have to further investigate...Threedimensional

© 2022 - 2024 — McMap. All rights reserved.