В JSF пользовательское исключение, вызванное вызываемым EJB, рассматривается как EJBTransactionRolledBackException или NullPointerException или ServletException

Вот проблема: EJB выдает это исключение (из журналов Glassfish):

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)

И на стороне клиента стека исключений (внизу) я вижу это:

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)

И, конечно, в браузере я получаю общую ошибку 500 исключений, которая говорит, что было исключение нулевого указателя.

Я хочу иметь пользовательскую страницу с ошибкой, которая обнаруживает "com.extremelatitudeoftware.els_commons.exceptions.LoginPreviouslyConfirmedException" и говорит что-то вроде "К сожалению, вы уже подтвердили это!"

Может ли кто-то указать мне правильное направление, чтобы заставить клиентский /JSF-слой правильно "увидеть" исключение, чтобы это можно было сделать.

Это вариант использования для проблемы:
У меня есть метод в слое EJB, который проверяет, подтвердил ли пользователь ранее свою учетную запись. Если у них его нет, он обрабатывает их запрос на подтверждение новой учетной записи. Если они, скажем, вернулись к старому электронному письму и сказали: "Хммммм, давайте снова нажмем на это, серверная часть обнаружит это, сгенерирует исключение с намерением, чтобы его поднял уровень JSF/client, и пользователю будет рекомендовано". что аккаунт уже подтвержден.


Добавление примера кода:

Метод проверки состояния и исключения при необходимости. Обратите внимание, что в большинстве случаев этого не происходит, поэтому я хочу использовать этот механизм вместо того, чтобы делать ненужные звонки каждый раз, когда кто-то подтверждает свою учетную запись.

...
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);
    }
  }

Пользовательское исключение:

public class LoginPreviouslyConfirmedException extends RuntimeException {

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

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

Это находится в ManagedBean на уровне JSF. Вызывается через: 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();
      }

Вот что показывает журнал:

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

Затем пара прослеживает:

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



Согласно предложению BalusC, я изменил код в ManagedBean следующим образом:

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

Это в web.xml

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

Но я все еще получаю это (это изображение страницы ошибки, которая отображается

2 ответа

Решение

Вот самый простой и лучший ответ, который я нашел в сегодняшних исследованиях: Украсьте пользовательское исключение аннотацией @ApplicationException(rollback=true).

например

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

Основная проблема заключалась в том, что перед передачей исключения клиенту контейнер EJB помещает по крайней мере некоторые исключения в оболочку EJBTransactionRolledBackException. Я не уверен, но это похоже на то, как JSF хочет обернуть исключения в "ServletException" для всего, что там возникает. Мне кажется, что в том, чтобы скрывать истинные ошибки приложения, есть недостатки. В любом случае это мешает клиенту "увидеть" любое исключение особого случая, которое вы можете выдать, потому что это то, что вы или ваш пользователь можете исправить, не прерывая всю операцию. То есть все, что он увидит, - это исключение EJBTransactionRolledBackException, которое используется для переноса множества исключений. Это делает невозможным легкую отладку или обработку исключений. К счастью, в последних выпусках JEE они добавили аннотацию @ApplicationException для противодействия этому поведению. Хотя я не понимаю, почему они просто не прекращают упаковывать исключения.

В любом случае, @ApplicationException(rollback=true) говорит серверу не заключать исключение в EJBTransactionRolledBackException и передавать его, как есть, обратно клиенту. Это позволяет клиенту увидеть, что является настоящим исключением, и у него будет шанс обработать его корректно. Указание "rollback=true" указывает серверу откатить текущую транзакцию. Если вы этого не сделаете, по умолчанию используется значение false, и все сразу же будет удалено. Завершение всего этого при любом состоянии вещей.

Если пользовательское исключение наследуется от Exception, и метод в сессионном компоненте EJB выдает его, метод, выполняющий вызов в JSF ManagedBean, должен будет также выбросить его. Если он наследует от RuntimeException, он не будет.

В любом случае исключение не будет заключено в EJBTransactionRolledBackException, поэтому при возврате на уровень JSF будет вызвана страница исключения, определенная в web.xml для пользовательского исключения. Нет необходимости перехватывать исключение в управляемом компоненте. Фильтрация не требуется. Никаких специальных правил навигации не требуется. Однако, если вы хотите поймать его, его можно поймать. Без @ApplicationException этого не произойдет (потому что он упакован).

Информация найдена здесь (среди других мест):
http://docs.oracle.com/javaee/5/api/javax/ejb/ApplicationException.html
http://openejb.apache.org/examples-trunk/applicationexception/
Как использовать пользовательские исключения в сессионных компонентах?

Во всяком случае, это работает. Просто хотелось, чтобы я разобрался с правильными поисковыми терминами в Google.

В методе EJB, который выдает исключение, объявите throws LoginPreviouslyConfirmedException, Затем в управляемом компоненте окружение вызывает EJB с помощью try-catch для этого исключения. В блоке улова; вы можете перенаправить пользователя на страницу с ошибкой; или просто отправьте сообщение в представление (для отображения в h:messages, p:growl и т. д.).

Другие вопросы по тегам