JSF2: valueChangeListerer e valori null

Problema: setto a null il valore di un inputText tramite jQuery, faccio submit della pagina e scatta la validazione tramite validatore custom. Se la validazione fallisce viene riportato nel campo messo a null l’ultimo valore valido precedente.

Causa: da una discussione trovata su http://stackoverflow.com/questions/3933786/jsf-2-bean-validation-validation-failed-empty-values-are-replaced-with-las pare sia un *bug* di Mojarra:

settando la proprietà nel web.xml:

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

si verifica un bug in HtmlBasicRenderer#getCurrentValue() in Mojarra:

if (component instanceof UIInput) {
    Object submittedValue = ((UIInput) component).getSubmittedValue();
    if (submittedValue != null) {
        // value may not be a String...
        return submittedValue.toString();
    }
}

String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null) {
    currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;

Normalmente, il valore inviato è impostato su null quando il componente UIInput viene convertito e validato con successo. Quando JSF è in procinto di visualizzare nuovamente il valore, prima controlla se il valore inserito non sia null prima di procedere per visualizzare di nuovo il valore di modello. Tuttavia, con questo parametro di contesto, è nullo, invece di una stringa vuota quando non è valido e così sarà sempre visualizzare nuovamente il valore del modello originale quando si rimuove il valore iniziale di un campo obbligatorio.

Soluzione: la parte jQuery setta il valore *stringa vuota* invece di null (nel caso specifico la soluzione funziona perché il test sul valore è effettuato tramite il metodo di utilità org.apache.commons.lang3.StringUtils.isNotBlank sul valore dell’input).

Annunci

The Java 6 EE Tutorial – Validazioni in JSF

Le validazioni JSF si ottengono in diversi modi:

  1. Usando JSF validator tag da annidare nella dichiarazione dei componenti nella view: viene fatto il binding tramite value expression ad una proprietà del backing bean.
  2. Creando un metodo nel backing beane facendovi riferimento nel componente tramite l’attributo validator.
    1. Il metodo del backing bean che esegue la validazione deve accettare un FacesContext, il componente i cui dati devono essere validati e il dato da validare ( stessa signature del metodo validate dell’interfaccia Validator). Possono essere validati solo valori di componenti UIInput o di suoi discendenti).
  3. Usando Bean Validator (NEW IN Java 6). I constraints per la validazione sono definiti tramite annotation su campi, metodi o classi di JavaBean component (quindi anche backing bean). Sono disponibili constraints built-in e in più se ne possono definire altre custom. Si può mettere più di un constraint sullo stesso elemento.

Esempi

JSF validator tag

<h:inputText id="Username" value="#{UserBean.userName}">
   <f:validateLength minimum="6" maximum="15"/>
</h:inputText>

Metodo nel backing bean

public void validateEmail(FacesContext context,UIComponent toValidate, Object value) {
  String message = "";
  String email = (String) value;
  if (email.contains(’@’)) {
    ((UIInput)toValidate).setValid(false);
    message = CoffeeBreakBean.loadErrorMessage(context,CoffeeBreakBean.CB_RESOURCE_BUNDLE_NAME,    "EMailError");
    context.addMessage(toValidate.getClientId(context),new FacesMessage(message));
  }
}

Nella view

<h:inputText value="#{userBean.email}" validator="#{userBean.validateEmail}">
</h:inputText>

Bean Validator 

Built-in

public class Name {
  @NotNull
  @Size(min=1, max=16)
  private String firstname;
  @NotNull
  @Size(min=1, max=16)
  private String lastname;
}

Validatori built-in disponibili

Custom

@NotNull
@Size(min = 3)
@FirstUpper
public String getName() {
  return name;
}
Definizione della annotation
package me.m1key.jsf.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Documented
@Constraint(validatedBy = FirstUpperValidator.class)
@Target( { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)

public @interface FirstUpper {
  String message() default "{me.m1key.jsf.constraints.FirstUpper.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}
Implementazione del validatore
package me.m1key.jsf.constraints;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FirstUpperValidator implements ConstraintValidator<FirstUpper, String> {
@Override
public void initialize(FirstUpper firstUpper) {
 // See JSR 303 Section 2.4.1 for sample implementation.
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
  if (value == null || value.length() == 0) {
    return true;
  }
  return value.substring(0, 1).equals(value.substring(0, 1).toUpperCase());
}
}
See more at:
http://viralpatel.net/blogs/javaserver-faces-jsf-validation-tutorial-error-handling-jsf-validator/
http://www.mkyong.com/jsf2/custom-validator-in-jsf-2-0/
http://blog.m1key.me/2010/07/custom-validation-with-bean-validation.html