Validação
A validação do VRaptor tira proveito do Bean Validation, especificação presente no Java EE 7 que permite validar nosso modelo baseado em anotações. Com o Bean Validation você pode usar as validações já existentes na especificação ou criar suas próprias anotações.
public class Cliente {
// valida se o nome não é nulo e possui tamanho entre 10 e 50
@NotNull @Size(min=10, max=50) private String nome;
// valida se a data de nascimento está no passado
@Past private Date nascimento;
}
Em seu controller:
public class ClienteController {
private final Validator validation;
/**
* @deprecated CDI eyes only
*/
protected ClienteController() {
this(null);
}
@Inject
public ClienteController(Validator validation) {
this.validation = validation;
}
public void formulario() {
}
public void cadastrar(@NotNull @Valid Cliente cliente) {
// em caso de erros irá redirecionar para a página de formulário
validation.onErrorForwardTo(this).formulario();
}
}
Nota: Se sua aplicação usa Hibernate ou JPA e você tem seu modelo anotado com as anotações do Bean Validation, você precisará chamar os métodos Session.flush()
ou EntityManager.flush()
para disparar a validação do Bean Validation antes de fazer qualquer redirect
ou forward
. O método flush()
é responsável por sincronizar as alterações no estado dos objetos com o banco de dados. Sendo assim as validações do Bean Validation em entidates só serão executadas no flush()
.
Criando validações customizadas
Com o Bean Validator você pode criar anotações com suas validações customizadas. Se você quer, por exemplo, validar se o telefone está no formato (00) 0000-0000
, você pode criar uma anotação assim:
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Pattern(regexp = "\\((\\d{2})\\) (\\d{4})-(\\d{4})")
public @interface PhoneNumber {
String message() default "{br.com.caelum.vraptor.validations.phonenumber.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
O valor da mensagem com {}
será buscado do ResourceBundle
.
Ou você pode colocar uma mensagem fixa, como por exemplo:
String message() default "Invalid number";
Você pode também criar validações mais complexas que fazem acesso ao banco de dados, ou algum componente existente. Se você quiser validar se o login de um usuário já está em uso, basta criar o código abaixo:
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { LoginAvailableValidator.class })
@Documented
public @interface LoginAvailable {
String message() default "{login_already_exists}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
E agora o LoginAvailableValidator
que fará a validação:
public class LoginAvailableValidator implements ConstraintValidator<LoginAvailable, User> {
@Inject private UserDao userDao;
public boolean isValid(User user, ConstraintValidatorContext context) {
return !userDao.containsUserWithLogin(user.getLogin());
}
}
Note no código que é possível injetar qualquer componente gerenciável pelo CDI, que pode ser uma DAO ou qualquer outro componente.
Customizando as Mensagens
Se você preferir é possível customizar as mensagens do Bean Validation. Para isso, basta adicionar o arquivo ValidationMessages.properties
no seu classpath. Se você usa Maven o local é /src/main/resources
.
Considerando o exemplo anterior, o arquivo poderia ter a seguinte estrutura para customizar as mensagens padrões:
cliente.nome.vazio = O nome do cliente não pode ser vazio
cliente.nome.tamanho = O nome do cliente deve possuir entre {min} e {max} caracteres
cliente.nascimento.data = A data de nascimento ${validatedValue} informada deve ser no passado
E a classe teria que ser alterada para incluir essas chaves
public class Cliente {
@NotNull(message="{cliente.nome.vazio}")
@Size(min=10, max=50, message="{cliente.nome.tamanho}")
private String nome;
@Past(message="{cliente.nascimento.data}")
private Date nascimento;
}
Observe que o valor contido em message={}
, nas anotações indica que a mensagem vem do ResourceBundle
.
Veja que foi adicionado parâmetros afim de deixar nossas mensagens mais dinâmicas e reaproveitáveis. As possibilidades atualmente são:
- os valores dos atributos da restrição mapeados com os nomes de atributo. Ex.
{min}
e{max}
no caso de@Size
- o atual valor a ser validado sob o nome de
${validatedValue}
- uso de if-else curto, como por exemplo:
${value > 1 ? 's' : ''}
- um bean mapeado com o nome de
formatter
expondo método var-argformat(String format, Object... args)
o qual se comporta comojava.util.Formatter.format(String format, Object... args)
. Ex.:${formatter.format('%1$.2f', validatedValue)}
Validação usando o Validator
Caso você não queira usar o Bean Validation, você pode usar os métodos de validação do próprio Validator
do VRaptor. O método mais simples é o add
:
if (cliente.getNome() == null) {
//mensagem simples
validator.add(new SimpleMessage("nome", "O nome deve ser preenchido"));
//mensagem internacionalizada
validator.add(new I18nMessage("nome", "nome.deve.ser.preenchido"));
}
Podemos também usar os métodos addIf
que adiciona a mensagem se a condição for verdadeira e o ensure
que adiciona se a condição for falsa:
validator.addIf(cliente.getNome() == null, new SimpleMessage("nome", "O nome deve ser preenchido"));
validator.ensure(cliente.getNome() != null, new SimpleMessage("nome", "O nome deve ser preenchido"));
Redirecionando em caso de erro
Quando ocorre um erro de validação, você pode redirecionar o usuário para qualquer outra tela. Abaixo alguns exemplos:
validator.onErrorForwardTo(MusicController.class).list() ==> validator.onErrorUse(logic()).forwardTo(MusicController.class).list();
validator.onErrorRedirectTo(MusicController.class).list() ==> validator.onErrorUse(logic()).redirectTo(MusicController.class).list();
validator.onErrorUsePageOf(MusicController.class).list() ==> validator.onErrorUse(page()).of(MusicController.class).list();
validator.onErrorSendBadRequest() ==> validator.onErrorUse(status()).badRequest(errors);
Além disso, se o redirecionamento é para um método do mesmo controller, podemos usar:
validator.onErrorForwardTo(this).list() ==> validator.onErrorUse(logic()).forwardTo(this.getClass()).list();
validator.onErrorRedirectTo(this).list() ==> validator.onErrorUse(logic()).redirectTo(this.getClass()).list();
validator.onErrorUsePageOf(this).list() ==> validator.onErrorUse(page()).of(this.getClass()).list();
Mostrando os erros de validação no JSP
Quando existem erros de validação, o VRaptor coloca um atributo na requisição chamado errors
contendo a lista de erros. Essa lista é representada por items chave-valor, onde temos:
-
category
: representa o caminho do atributo que originou o erro, cujo valor é uma convenção paraobjeto.atributo
-
message
: representa a mensagem padrão da API do Bean Validation, normalmente um sufixo como: “deve estar no futuro”, “não pode ser nulo”, etc.
E na view você pode imprimir:
<c:forEach var="error" items="${errors}">
${error.category} - ${error.message}<br />
</c:forEach>
Você também pode buscar por um erro somente de uma determinada categoria, que é muito útil para quando você quer exibir os erros de validação ao lado de cada campo.
<input type="text" name="cliente.nome" />
<span class="error">${errors.from('cliente.nome')}</span>
O código acima irá imprimir “não pode ser nulo, não pode ser menor que 50”. Você também pode usar o método join
caso preferir exibir as mensagens usando outro separador, como o exemplo abaixo:
<span class="error">${errors.from('cliente.nome').join(' - ')}</span>
Que irá imprimir: “não pode ser nulo - não pode ser menor que 50”.
Adicionando outras mensagens
Quando você adiciona uma mensagem, por padrão o VRaptor adiciona a mensagem como um erro. É possível também que você adicione mensagem de sucesso, avisos e informações. Para isso basta criar sua mensagem usando os valores disponíveis no enum Severity
, conforme os exemplos abaixo:
validator.add(new SimpleMessage("cliente.nome", "nome do cliente possui acentuação", Severity.WARN));
E no JSP basta usar o atributo vmessages
:
<input type="text" name="cliente.nome" />
<span class="warn">${vmessages.warnings.from('cliente.nome')}</span>
Que irá imprimir “nome do cliente possui acentuação”.
Validação programática
Caso prefira, você pode usar o plugin vraptor-simple-validator para fazer validação de forma fluente em seu código. Ele te oferece uma variedade bem grande de estratégias de validação que você provavelmente vai gostar de usar.