Выполнение перенаправления, когда преобразование/проверка, связанная с параметрами запроса, не выполняется

Ниже приведен простой пример использования <f:viewAction>.

<f:metadata>
    <f:viewParam name="id" value="#{testManagedBean.id}" maxlength="20"/>
    <f:viewAction action="#{testManagedBean.viewAction}"/>
</f:metadata>

Управляемый bean.

@ManagedBean
@ViewScoped
public final class TestManagedBean implements Serializable {

    private static final long serialVersionUID = 1L;
    private Long id; //Getter and setter.

    public void viewAction() {
        System.out.println("viewAction() called : " + id);
    }
}

Параметр id передается через URL-адрес. Существует ошибка преобразования, когда через рассматриваемый URL-адрес передается нечисловое значение типа xxx, и метод viewAction(), связанный с прослушивателем <f:viewAction>, не вызывается.

В этом случае значение id составляет null. Я хотел бы перенаправить на другую страницу, когда id не конвертируется в желаемый тип целевого объекта (например, в этом случае) или id не проверяется на соответствие указанным критериям проверки, чтобы избежать потенциальных исключений, которые, вероятно, будут LazyDataModel#load() метод PrimeFaces или где-нибудь еще в связанном управляемом bean, когда доступ к этим параметрам выполняется в соответствующем управляемом bean, Для этого нужно вызвать метод viewAction().

Как это сделать? Должен ли я использовать

<f:event type="preRenderView">

в сочетании с <f:viewAction>?

Ответ 1

Это указано. Когда фаза PROCESS_VALIDATIONS заканчивается валидацией, обе фазы UPDATE_MODEL_VALUES и INVOKE_APPLICATION пропускаются. Точно так же, как в "обычных" формах с <h:form>. Подумайте <f:viewParam> как <h:inputText> и a <f:viewAction> как <h:commandButton>, и это станет более понятным.

Для вашего конкретного требования, выполняющего перенаправление при завершении преобразования/проверки, существует как минимум 3 решения:

  • Как вы узнали, добавьте <f:event listener>. Я предпочел бы вместо этого использовать postValidate для лучшей самодокупимости.

    <f:metadata>
        <f:viewParam name="id" value="#{bean.id}" maxlength="20" />
        <f:event type="postValidate" listener="#{bean.redirectIfNecessary}" />
        <f:viewAction action="#{bean.viewAction}" />
    </f:metadata>
    
    public void redirectIfNecessary() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();
    
        if (!context.isPostback() && context.isValidationFailed()) {
            context.getExternalContext().redirect("some.xhtml");
        }
    }
    

    Проверка FacesContext#isPostback() предотвращает выполнение перенаправления при ошибках проверки правильных форм в одном и том же представлении (если есть).


  • Расширьте встроенный LongConverter, в котором вы выполняете перенаправление в getAsObject() (валидатор является незаменимым в качестве конвертера по умолчанию для Long будет работать с ошибками на нечисловых входах, если преобразователь не работает, валидаторы никогда не запускаются). Это, однако, плохая конструкция (плотная связь).

    <f:metadata>
        <f:viewParam name="id" value="#{bean.id}" converter="idConverter" />
        <f:viewAction action="#{bean.viewAction}" />
    </f:metadata>
    
    @FacesConverter("idConverter")
    public class IdConverter extends LongConverter {
    
        @Override
        public Object getAsObject(FacesContext context, UIComponent component, String value) {
            if (value == null || !value.matches("[0-9]{1,20}")) {
                try {
                    context.getExternalContext().redirect("some.xhtml");
                    return null;
                }
                catch (IOException e) {
                    throw new FacesException(e);
                }
            }
            else {
                return super.getAsObject(context, component, value);
            }
        }
    
    }
    

    В случае необходимости вы можете поиграть с <f:attribute> внутри <f:viewParam> для передачи параметров преобразователю.

    <f:viewParam name="id" value="#{bean.id}" converter="idConverter">
        <f:attribute name="redirect" value="some.xhtml" />
    </f:viewParam>
    
    String redirect = (String) component.getAttributes().get("redirect");
    context.getExternalContext().redirect(redirect);
    

  • Создайте собственный обработчик меток, который в основном совпадает с <f:event listener>, но без необходимости использования дополнительного метода поддержки bean.

    <html ... xmlns:my="http://example.com/ui">
    
    <f:metadata>
        <f:viewParam name="id" value="#{bean.id}" maxlength="20" />
        <my:viewParamValidationFailed redirect="some.xhtml" />
        <f:viewAction action="#{bean.viewAction}" />
    </f:metadata>
    

    com.example.taghandler.ViewParamValidationFailed

    public class ViewParamValidationFailed extends TagHandler implements ComponentSystemEventListener {
    
        private String redirect;
    
        public ViewParamValidationFailed(TagConfig config) {
            super(config);
            redirect = getRequiredAttribute("redirect").getValue();
        }
    
        @Override
        public void apply(FaceletContext context, UIComponent parent) throws IOException {
            if (parent instanceof UIViewRoot && !context.getFacesContext().isPostback()) {
                ((UIViewRoot) parent).subscribeToEvent(PostValidateEvent.class, this);
            }
        }
    
        @Override
        public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
            FacesContext context = FacesContext.getCurrentInstance();
    
            if (context.isValidationFailed()) {
                try {
                    context.getExternalContext().redirect(redirect);
                }
                catch (IOException e) {
                    throw new AbortProcessingException(e);
                }
            }
        }
    
    }
    

    /WEB-INF/my.taglib.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <facelet-taglib
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
        version="2.0"
    >
        <namespace>http://example.com/ui</namespace>
    
        <tag>
            <tag-name>viewParamValidationFailed</tag-name>
            <handler-class>com.example.taghandler.ViewParamValidationFailed</handler-class>
        </tag>  
    </facelet-taglib>
    

    /WEB-INF/web.xml

    <context-param>
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
        <param-value>/WEB-INF/my.taglib.xml</param-value>
    </context-param>
    

    Правда, это немного кода, но он заканчивается чистым и многоразовым тегом <my:viewParamValidationFailed> и на самом деле хорошо подходит для нового OmniFaces.

Ответ 2

Почему бы просто не утвердить id самостоятельно?

@ManagedBean
@ViewScoped
public final class TestManagedBean implements Serializable
{
    private String id;     //Getter and setter.
    private Long validId;  //Getter and setter.

    public void viewAction() {
        try {
            validId = Long.parseLong(id);
        } catch (NumberFormatException ex) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            String outcome = "redirect.xhtml";
            facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome);
        }
    }
}