¿Cuál es la mejor forma de implementar validaciones en JEE 6?
Hay anotaciones para ello en el paquete javax.validation >>, las cuales están especificadas por el JSR 303 (e-dit!) y cuya implementación por defecto es la de Hibernate org.hibernate.validator >> cuya documentación es la del link que acaba de pasar.
(También hay una sección en la doc de JEE6, ver anotación de e-mail que está más pro que la nuestra >>)
Esto nos da una serie de anotaciones que, en su forma más simple, podemos aplicar a las propiedades de los beans
@NotNull private Object propiedad;
los cuales serán validados al fomento de submitir el form en que se encuentran.
Si queremos que el mensaje aparezca al lado del campo, podemos usar RichFaces >>
<h:inputText id="id" /> <rich:message for="id"/>
//TODO
cachar por qué <rich:validator />no funciona en RichFaces 4.0.0.Final
Nótese que:
- No es necesario anotar con pattern para probar que un input sea numérico ya que JSF valida antes la conversión >>
- Para usar estas validaciones de JSF, hay que sobrescribir los mensajes de error con un .properties propio >>
- @NotNull no funciona con Integer por defecto (…)
También podemos crear nuestras propias anotaciones de validación >> en caso de que necesitemos algo específico, por ejemplo, los RUTs. Para eso hay que anotar la anotación con javax.validation.Constraint
@Target({METHOD, FIELD, ANNOTATION_TYPE}) @Retention(RUNTIME) @Constraint(validatedBy={}) public @interface MiConstraint { String message() default "{mi.paquete.mensaje}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Hay que darle estas tres propiedades, de las cuales la más importante es message() que es – obviamente – el mensaje de error desplegado.
Éstos van en ValidationMessages.properties. Pueden recibir los mismos parámetros que las antoaciones, pero ¿es posible pasarle parámetros desde el validador?
Si os fijáis, validatedBy lo dejé vacío, ahí es donde debe ir la clase que implementa la validación específica, esto porque se pueden componer las anotaciones para crear otras >>, por ejemplo el RUT no sería más que un @Pattern específico, coño.
@Pattern(regexp="expresion de rut") @Target({METHOD, FIELD, ANNOTATION_TYPE}) @Retention(RUNTIME) @Constraint(validatedBy={}) public @interface MiConstraint { //idem }
La shit es que por defecto cuando fallan contraints así compuestas, cada una lanzará un mensaje de error separado.
Para eso existe otra antoación auto explicativa @ReportAsSingleViolation
Otra funcionalidad interesante es la de definir constraints para toda una clase, lo que permite hacer validaciones que usan más de una propiedad del objeto – el ejemplo clásico podría ser el de que una contraseña sea igual a la otra.
Para ello la anotación debe tener como @Target(TYPE) y en el método de validación recibiremos el objeto como parámetro
public class ValidadorContraseñasIguales implements ConstraintValidator<ContraseñasIguales, MiObjeto> { @Override public boolean isValid(MiObjeto miObjeto, ConstraintValidatorContext context) { return miObjeto.password1.equals(miObjeto.password2); } }
luego anotamos MiObjeto
@ContraseñasIguales public class MiObjeto{ //... }
Es importante especificar que este tipo de validación no es aplicada por defecto por JSF 2.
RichFaces tiene un tag específico para ello <rich:graphValidator/>, el cual tiene que encapsular todos los campos necesarios para efectuar esta validación
<rich:graphValidator id="idGrafo" value="#{miObjeto}> <!-- value es el anotado --> <h:inputText value="#{miObjeto.password1}/> <h:inputText value="#{miObjeto.password2/> </rich:graphValidator>
Para referenciar esta validación usamos el id del graphValidator
<rich:message for="idGrafo"/>
(no hay problema con que este message esté dentro del graphValidator)
También hay un tag de JSF 2 <f:validateBean /> que también lo hace, por no atarse a un framework propietario. Pero no lo he probado.
Un caso interesante. ¿Qué pasa si en estas validaciones de graffos quiero lanzar distintos errores dependiendo de qué falla, de modo a mostrar los mensajes en ciertos inputs específicos? ¿Se entiende?
Una opción que funciona es utilizar los grupos de las @Constraints
@MiConstraint(groups=InterfazGrupo.class) @MiConstraint2(groups=InterfazGrupo2.class) public class MiClase{ //... }
los cuales son simples interfaces vacías
public interfaces InterfazGrupo{ } public interfaces InterfazGrupo2{ }
Así, puedo crear un <rich:graphValidator/> apuntando al mismo objeto, uno para cada grupo
<rich:graphValidator id="id1" value="#{bean.obj}" groups="paquete.InterfazGrupo"> <rich:graphValidator id="id" value="#{bean.nuevo}" groups="paquete.InterfazGrupo2"> <!-- <h:inputText ... inputs a validar --> <h:message for="id1"/> <h:message for="id2"/> </rich:graphValidator> </rich:graphValidator>
Lo que nos permite dejar los mensajes de error en distintos lados dependiendo de la validación que falle.
Nota interesante: los grupos de validaciones se ejecutan en orden – para lo que existe la anotación @GroupSequence. Pero qué pasa si quiero que dos o más grupos de validaciones se ejecuten simultáneamente. Como los grupos son interfaces, podemos hacer que hereden los unos de los otros:
import javax.validation.groups.Default; public interfaces GrupoEspecifico extends Default{ }