In JSF, a custom exception thrown by called EJB is seen as EJBTransactionRolledBackException or NullPointerException or ServletException

StackOverflow https://stackoverflow.com/questions/10906155

Question

Here's the problem: The EJB throws this exception (from the glassfish logs):

SEVERE: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
    at com.extremelatitudesoftware.security.auth.CredentialsController.checkForPreviousConfirmation(CredentialsController.java:244)

And on the client side of the exception stack (down at the bottom) I see this:

WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
    at com.extremelatitudesoftware.accesscontrol.registration.RegistrationConfirmationBean.setChallengeQuestion(RegistrationConfirmationBean.java:61)
    at com.extremelatitudesoftware.accesscontrol.registration.RegistrationConfirmationBean.fetchChallengeResponse(RegistrationConfirmationBean.java:51)

And of course in the browser I get a generic 500 exception error that says there was a null pointer exception.

I want to have a custom error page that detects the "com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException" and say something like "Oops you already confirmed this!"

Can someone point me in the right direction for getting the client/JSF layer to correctly "see" the exception so that this may be done.

This is the use case for the problem:
I have a method in the EJB layer that checks to see if a user has previously confirmed their login account. If they haven't it processes their request to confirm their new account. If they have say gone back to an old email and said, hmmmmm let's click on that again, the back end will detect that, throw the exception with the intent of it being picked up by the JSF/client layer and the user will be advised that the account is already confirmed.


Adding example code:

Method checking for condition and throwing exception if necessary. Note that the majority of the time this won't happen so I want to use this mechanism instead of making unnecessary calls every time someone confirms their account.

...
private void checkForPreviousConfirmation(Credentials cr) 
                                    throws LoginPreviouslyConfirmedException {

    if (!(CredentialsStatusType.PENDING.equals(cr.getStatus()))
         || !(CredentialsDispositionType.WAITING.equals(cr.getDisposition()))) {
      String msg = "Attempting to confirm previously confirmed login using "
              + "confirmation UUID: " + cr.getConfirmationUuid();
      Logger.getLogger(CredentialsController.class.getName()).log(Level.INFO,
              msg);
      throw new LoginPreviouslyConfirmedException(msg);
    }
  }

The custom exception:

public class LoginPreviouslyConfirmedException extends RuntimeException {

    public LoginPreviouslyConfirmedException(String msg, Throwable cause) {
        super(msg, cause);
    }

    public LoginPreviouslyConfirmedException(String msg) {
        super(msg);
    }
}

This is in the ManagedBean in the JSF tier. It is called via: preRenderView

        ...
public void fetchChallengeResponse() {
    try {
         crespList = regFacade.fetchChallengeResponse(confirmUuid);
    } catch (LoginPreviouslyConfirmedException ex) {
         String msg = "Attempting to confirm previously confirmed login using " + "confirmation UUID: " + getConfirmUuid();
         Logger.getLogger(RegistrationConfirmationBean.class.getName()).log(Level.SEVERE, msg, ex);
          FacesContext facesContext = FacesContext.getCurrentInstance();
          Application application = facesContext.getApplication();
          NavigationHandler navigationHandler = application.getNavigationHandler();
          navigationHandler.handleNavigation(facesContext, null, "faces/errorpages/already_confirmed.xhtml");
          facesContext.renderResponse();
        }catch (Exception ex){
          String msg = "Including this to see if the previously confirmed exception was skipped over";
              Logger.getLogger(RegistrationConfirmationBean.class.getName()).log(Level.SEVERE, msg, ex);
        }

        this.setChallengeQuestion();
      }

This is what the log shows:

INFO: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
WARNING: EJB5184:A system exception occurred during an invocation on EJB CredentialsController, method: public java.util.ArrayList com.extremelatitudesoftware.security.auth.CredentialsController.fetchChallengeResponseByCredentialUuid(java.lang.String) throws com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException
WARNING: javax.ejb.TransactionRolledbackLocalException: Exception thrown from bean

Then a couple traces down:

SEVERE: Including this to see if the previously confirmed exception was skipped over
javax.ejb.EJBTransactionRolledbackException



Per BalusC's suggestion I changed the code in the ManagedBean to this:

public void fetchChallengeResponse() throws LoginPreviouslyConfirmedException{
     crespList = regFacade.fetchChallengeResponse(confirmUuid);
     this.setChallengeQuestion();
  }

This is in the web.xml

<error-page>
    <exception-type>com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException</exception-type>
    <location>/errorpages/already_confirmed.xhtml</location>
</error-page>

But I still get this(This is an image of the error page that gets displayed).

Was it helpful?

Solution

Here is the simplest and best answer I have found through researching today: Decorate the custom exception with the @ApplicationException(rollback=true) annotation.

e.g.

@ApplicationException(rollback=true)
public class LoginPreviouslyConfirmedException extends Exception {...

The fundamental problem was that before passing the exception to the client, the EJB container wraps at least some exceptions in an "EJBTransactionRolledBackException" wrapper. I'm not sure but it seems similar to the way JSF wants to wrap exceptions in a "ServletException" for anything raised there. It seems to me to be pretty flawed thinking to hide what the real application errors are. In any case it prevents the client from "seeing" any kind of special case exception you might throw with the reason being it is something you or your user can correct without aborting the whole operation. That is, all it will see is the EJBTransactionRolledBackException which is used to wrap a multitude of exceptions. Doing this makes it impossible to easily debug or handle exceptions. Fortunately in the latest issues of JEE they included @ApplicationException annotation to counteract this behaviour. Although I don't see why they don't just stop wrapping exceptions altogether.

In any case, the @ApplicationException(rollback=true) tells the server not to wrap the exception in a EJBTransactionRolledBackException and pass it, as is, back to the client. This allows the client to see what the real exception is so it will have a chance of handling it gracefully. Specifying "rollback=true" tells the server to roll back the current transaction. If you don't, it defaults to false and will just drop everything right there. Ending it all at whatever state things are in.

If the custom exception inherits from Exception, and the method in the EJB session bean throws it, the method doing the calling in the JSF ManagedBean will have to throw it too. If it inherits from RuntimeException, it will not.

In any case the exception will not be wrapped by EJBTransactionRolledBackException, so when it gets back up to JSF layer the exception page defined in web.xml for the custom exception will be called. No need catch the exception in the managed bean. No filtering required. No special navigation rules required. However if you do want to catch it, it can be caught. Without the @ApplicationException, it won't be (because it is wrapped).

Info found here (among other places):
http://docs.oracle.com/javaee/5/api/javax/ejb/ApplicationException.html
http://openejb.apache.org/examples-trunk/applicationexception/
How to use custom Exception in Session Beans?

Anyway this works. Just wished I figured out the right search terms on Google sooner.

OTHER TIPS

In the EJB method that throws the exception, declare throws LoginPreviouslyConfirmedException. Then, in the managed bean surround the EJB call with a try-catch for that exception. In the catch block; you can redirect the user to an error page; or just send a message to the view (to be rendered in h:messages, p:growl, etc).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top