JSF-конвертер приводит к игнорированию валидатора (-ов) - PullRequest
6 голосов
/ 04 января 2012

Вот поле:

<h:inputText id="mobilePhoneNo"
             value="#{newPatientBean.phoneNo}"
             required="true"
             requiredMessage="Required"
             validator="#{mobilePhoneNumberValidator}"
             validatorMessage="Not valid (validator)"
             converter="#{mobilePhoneNumberConverter}"
             converterMessage="Not valid (converter)"
             styleClass="newPatientFormField"/>

И валидатор:

@Named
@ApplicationScoped
public class MobilePhoneNumberValidator implements Validator, Serializable
{
    @Override
    public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException
    {
        // This will appear in the log if/when this method is called.
        System.out.println("mobilePhoneNumberValidator.validate()");

        UIInput in = (UIInput) uic;
        String value = in.getSubmittedValue() != null ? in.getSubmittedValue().toString().replace("-", "").replace(" ", "") : "";

        if (!value.matches("04\\d{8}"))
        {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Please enter a valid mobile phone number.", null));
        }
    }
}

Когда я нажимаю командную кнопку в форме, я получаю следующее поведение:

  • Если поле не заполнено, появляется сообщение «Недействительно (конвертер)».
  • Когда в поле есть действительная запись, появляется сообщение «Недействительно (валидатор)».
  • Если в поле есть недопустимая запись, появляется сообщение «Недействительно (конвертер)».

Во всех трех случаях вызывается MobilePhoneNumberConverter.getAsObject(). MobilePhoneNumberValidator.validate() никогда не вызывается . И когда поле пустое, оно игнорирует атрибут required="true" и переходит прямо к преобразованию.

Я бы подумал, что правильное поведение будет:

  • Если поле не заполнено, сообщение должно быть «Обязательным».
  • Когда в поле есть допустимая запись, сообщения вообще не должно быть.
  • Если в поле есть недопустимая запись, сообщение должно быть «Недействительно (валидатор)».
  • Если, по какой-то причине, проверка, пройденная преобразованием, не прошла, сообщение должно быть «Недействительно (преобразователь)».

Примечание: базовый компонент имеет объем запроса, поэтому здесь нет никакой причудливой работы с AJAX.

Обновление:

Может быть, это как-то связано с javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL, установленным на true?

Ответы [ 2 ]

13 голосов
/ 04 января 2012

Преобразование происходит до проверки. Преобразователи также будут вызываться, когда значение равно null или пусто. Если вы хотите делегировать значение null валидаторам, то вам необходимо спроектировать конвертеры так, чтобы он просто возвращал null, когда предоставленное значение равно null или пусто.

@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
    if (value == null || value.trim().isEmpty()) {
        return null;
    }

    // ...
}

Не имеет отношения к конкретной проблеме , у вашего валидатора есть недостаток. Вы не должны извлекать представленное значение из компонента. Это не то же самое значение, которое возвращает преобразователь. Переданное право и преобразованное значение уже доступны в качестве третьего аргумента метода.

@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    if (value == null) {
        return; // This should normally not be hit when required="true" is set.
    }

    String phoneNumber = (String) value; // You need to cast it to the same type as returned by Converter, if any.

    if (!phoneNumber.matches("04\\d{8}")) {
        throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Please enter a valid mobile phone number.", null));
    }
}
4 голосов
/ 04 января 2012

После прочтения комментария BalusC я снова обновляю этот пост.

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

Вид:

<h:form>
    <h:inputText value="#{demoBean.field}">
        <f:converter converterId="demoConverter"/>
        <f:validator validatorId="demoValidator"/>
    </h:inputText>
    <h:commandButton value="Submit" action="#{demoBean.demoAxn()}"/>
</h:form>

Управляемый компонент:

@ManagedBean
@SessionScoped
public class DemoBean implements Serializable {
    private String field;

    public DemoBean() {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
    }

    public String getField() {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
        return field;
    }

    public void setField(String field) {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
        this.field = field;
    }

    public String demoAxn() {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
        return null;
    }
}

Преобразователь:

@FacesConverter(value="demoConverter")
public class DemoConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println(Thread.currentThread().getStackTrace()[1]);            
        return value;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
        return (String) value;
    }    
}

Validator:

@FacesValidator(value="demoValidator")
public class DemoValidator implements Validator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
    }

}

Фазовый слушатель:

public class DemoPhaseListener implements PhaseListener {
    @Override
    public void afterPhase(PhaseEvent event) {
        System.out.println(Thread.currentThread().getStackTrace()[1]);
        System.out.println("PhaseId: " + event.getPhaseId() + "  ===============================\n\n");        
    }

    @Override
    public void beforePhase(PhaseEvent event) {
        System.out.println("\n\nPhaseId: " + event.getPhaseId() + "  ===============================");
        System.out.println(Thread.currentThread().getStackTrace()[1]);        
    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }    
}

Зарегистрированный фазовый слушатель:

<lifecycle>
    <phase-listener>pkg.DemoPhaseListener</phase-listener>
</lifecycle>

При этой настройке при нажатии кнопки «Отправить» вывод будет:

INFO: PhaseId: RESTORE_VIEW 1  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: RESTORE_VIEW 1  ===============================

INFO: PhaseId: APPLY_REQUEST_VALUES 2  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: APPLY_REQUEST_VALUES 2  ===============================

INFO: PhaseId: PROCESS_VALIDATIONS 3  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoConverter.getAsObject(DemoConverter.java:13)
INFO: pkg.DemoValidator.validate(DemoValidator.java:14)
INFO: pkg.DemoBean.getField(DemoBean.java:17)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: PROCESS_VALIDATIONS 3  ===============================

INFO: PhaseId: UPDATE_MODEL_VALUES 4  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoBean.setField(DemoBean.java:22)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: UPDATE_MODEL_VALUES 4  ===============================

INFO: PhaseId: INVOKE_APPLICATION 5  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoBean.demoAxn(DemoBean.java:27)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: INVOKE_APPLICATION 5  ===============================

INFO: PhaseId: RENDER_RESPONSE 6  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoBean.getField(DemoBean.java:17)
INFO: pkg.DemoConverter.getAsString(DemoConverter.java:20)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: RENDER_RESPONSE 6  ===============================

Но когда внесете изменения, добавьте NPE в конвертер следующим образом:

@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
    System.out.println(Thread.currentThread().getStackTrace()[1]);            
    throw new NullPointerException();
}

вывод:

INFO: PhaseId: RESTORE_VIEW 1  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: RESTORE_VIEW 1  ===============================

INFO: PhaseId: APPLY_REQUEST_VALUES 2  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: APPLY_REQUEST_VALUES 2  ===============================

INFO: PhaseId: PROCESS_VALIDATIONS 3  ===============================
INFO: pkg.DemoPhaseListener.beforePhase(DemoPhaseListener.java:17)
INFO: pkg.DemoConverter.getAsObject(DemoConverter.java:13)
INFO: pkg.DemoPhaseListener.afterPhase(DemoPhaseListener.java:10)
INFO: PhaseId: PROCESS_VALIDATIONS 3  ===============================

INFO: pkg.DemoBean.getField(DemoBean.java:17)

Но трассировка стека отображается в результирующем представлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...