Переход на другую страницу в preRenderViewEvent в зависимости от разрешения пользователя; ExternalContext#dispatch() не работает

Страница должна отображаться только в том случае, если у пользователя есть права на ее просмотр. Я пытаюсь добиться этого, выполнив проверку в методе прослушивателя событий PreRenderView:

<f:event type="preRenderView" listener="#{theBean.init}" />

И сам метод:

public void init() {
    if (user.havePermission()) {
        // backing bean init
    } else {
        FacesContext fc = FacesContext.getCurrentInstance();
        ExternalContext ec = fc.getExternalContext();
        ec.getRequestMap().put("message", no_permissions_message);
        try {
            ec.dispatch(ec.getRequestContextPath()
                + path_to_no_perm_page);
            fc.responseComlete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Когда я пытаюсь просмотреть запрещенную страницу, я вижу ее, но получаю исключение в журнале сервера:

at org.omnifaces.util.FacesLocal.getRequestMap(FacesLocal.java:945) [omnifaces-1.7.jar:1.7]
at org.omnifaces.util.FacesLocal.getRequestAttribute(FacesLocal.java:953) [omnifaces-1.7.jar:1.7]
at org.omnifaces.util.Faces.getRequestAttribute(Faces.java:1416) [omnifaces-1.7.jar:1.7]
at org.omnifaces.eventlistener.CallbackPhaseListener.getCallbackPhaseListeners(CallbackPhaseListener.java:110) [omnifaces-1.7.jar:1.7]
at org.omnifaces.eventlistener.CallbackPhaseListener.afterPhase(CallbackPhaseListener.java:77) [omnifaces-1.7.jar:1.7]
at com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:189) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:107) [jsf-impl-2.1.7-jbossorg-2.jar:]
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139) [jsf-impl-2.1.7-jbossorg-2.jar:]
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594) [jboss-jsf-api_2.1_spec-2.0.1.Final.jar:2.0.1.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
at org.primefaces.webapp.filter.FileUploadFilter.doFilter(FileUploadFilter.java:98) [primefaces-4.0.7.jar:4.0.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:280) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
at org.jboss.weld.servlet.ConversationPropagationFilter.doFilter(ConversationPropagationFilter.java:62) [weld-core-1.1.5.AS71.Final.jar:2012-02-10 15:31]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:280) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
...

Причина: сразу после вызова отправки FacesContext.getCurrentInstance() возвращает null. Но это не прерывает текущую фазу. Итак, в конце фазы выполняется PhaseListener. Он пытается получить экземпляр FacesContext, но получает null.

Могу ли я избежать выполнения PhaseListener здесь? Или, может быть, нужно добавить в омнифейсы проверку того, что текущий экземпляр FacesContext не равен нулю?

Спасибо.


person 12kb    schedule 17.04.2015    source источник
comment
Сделайте эту строку fc.responseComlete(); последней строкой в ​​блоке try...catch. Существует ошибка, которая не будет компилироваться. (OmniFaces не имеет ничего общего с исключением).   -  person Tiny    schedule 18.04.2015
comment
Я пробовал оба случая. Ничего не изменилось. Кажется, оба должны работать нормально. С responseComplete() мы просто прерываем жизненный цикл JSF, но не фиксируем ответ, не так ли?   -  person 12kb    schedule 18.04.2015
comment
Я исправил, как вы говорите. Спасибо. Согласно документам, это более правильный путь. Даже если оба работают нормально.   -  person 12kb    schedule 18.04.2015
comment
Вы отправляете/пересылаете с помощью ExternalContext#dispatch("/path/to/resource"); (вероятно 404 - я вчера не особо концентрировался). Если вам нужно ограничить доступ (при неудачной авторизации (проверке прав)), вам следует условно перенаправить на общедоступный ресурс с помощью ExternalContext#redirect("/path/to/resource");. В обоих случаях FacesContext#responseComplete(); совершенно не нужен. Вам это как-то нужно в вашем реальном приложении? Кроме того, если вы используете JSF 2.2, то <f:event type="preRenderView" .../> лучше заменить на <f:viewAction action="..." .../>.   -  person Tiny    schedule 18.04.2015
comment
Да, мне это нужно в реальном приложении. Я хочу использовать диспетчеризацию, потому что не хочу менять URL-адрес в браузере клиента.   -  person 12kb    schedule 18.04.2015
comment
FacesContext#responseComplete(); нужно здесь. Я попытался удалить его и получил исключение о недопустимом состоянии. Спасибо за viewAction, я рассмотрю его использование.   -  person 12kb    schedule 18.04.2015


Ответы (1)


Не используйте ExternalContext#dispatch(), никогда. У этого метода нет единственного разумного варианта использования в приличном веб-приложении JSF. Это только портит жизненный цикл JSF, потому что создает еще один FacesContext в текущем запросе. Вам следует использовать собственный NavigationHandler JSF или использовать ExternalContext#redirect().

Если вы уже используете JSF 2.2, используйте <f:viewAction action="#{bean.init}">, который поддерживает результат навигации (например, <h:commandButton action>):

public String init() {
    // ...
    return "someViewId";
}

Или, когда вы все еще используете JSF 2.0/2.1 и поэтому можете использовать только <f:event type="preRenderView">, и, учитывая, что вы используете OmniFaces, используйте его Faces#navigate() или Faces#redirect():

public void init() {
    // ...
    Faces.navigate("someViewId");
}

Если вы не используете OmniFaces, вот как это реализовано:

public void init() {
    // ...
    FacesContext context = FacesContext.getCurrentInstance();
    context.getApplication().getNavigationHandler().handleNavigation(context, null, "someViewId");
}

Обратите внимание, что все это может по-прежнему не работать при использовании @PostConstruct вместо f:viewAction или preRenderView. См. также Перенаправление в @PostConstruct вызывает исключение IllegalStateException.

person BalusC    schedule 20.04.2015