VRaptor

Quando usar um Conversor?

Quando recebemos um parametro do browser, recebemos como uma String. Por isso é necessário um conversor para fazer a tradução deste parametro para seu respectivo objeto. O VRaptor já possui registrado conversores para todos os tipos definidos na Java Language Specification. Para os tipos primitivos, caso o parâmetro seja recebido como null, será retornado o valor padrão do campo. Por exemplo, se você receber um int cujo valor seja null, o converter irá retornar 0. Já para os tipos objetos, se o parâmetro recebido for null ou vazio, o valor retornado será null.

Por exemplo, se recebermos os seguintes parâmetros do browser:

cliente.id 10
cliente.idade null

para a classe abaixo:

public class Cliente {
    private Long id;
    private int idade;

    // getters and setters
}

Ao injetar os dados da requisição na classe, o VRaptor irá usar o conversor LongConverter para o campo id e o conversor PrimitiveIntegerConverter para o campo idade. Neste caso, o atributo id receberá o valor 10, e o campo idade receberá o valor 0, pois o parametro recebido é null.

Você pode obter a lista completa dos conversores padrão do vraptor aqui.

BigDecimal, Double e Float localizados

São suportados utilizando o padrão de localização da sua aplicação. Double e Float são suportados tanto o tipo primitivo quanto o tipo objeto.

Trabalhando com datas e calendários

Calendar e Date são suportados por padrão usando a localização da sua aplicação.

Existem conversores para os principais tipos do Joda-time como DateTime, LocalDate, LocalDateTime e LocalTime. Os conversores do Joda-time estão disponíveis usando o plugin vraptor-jodatime.

Se você está usando JDK 8, você pode tirar proveito dos conversores do pacote java.time, que estão disponíveis usando o plugin vraptor-java8.

Mensagem de erro de conversão

Quando o VRaptor tenta converter um objeto, e ocorrer algum erro, é retornada para a apicação uma exception indicando o erro de conversão. Por exemplo, se você receber um parametro com valor A para um campo Integer, a conversão irá falhar e você receberá uma ConversionException internacionalizada. Para exibir as mensagens na sua aplicação, basta adicionar as seguintes chaves no seu resource bundle:

is_not_a_valid_number = {0} não é um número válido.
is_not_a_valid_integer = {0} não é um inteiro válido.
is_not_a_valid_character = {0} não é um caracter válido.
is_not_a_valid_enum_value = {0} não é uma opção válida.
is_not_a_valid_date = {0} não é uma data válida.
is_not_a_valid_boolean = {0} não é um valor boolean válido.
is_not_a_valid_time = {0} não é um horário válido.
is_not_a_valid_datetime = {0} não é uma data e horário válidos.

Definindo a localização (Locale) da aplicação

A localização dos componentes pode ser alterada utilizando a seguinte configuração no web.xml:

<context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
    <param-value>pt_BR</param-value>
</context-param>

Ou vocẽ pode iniciar o servidor de aplicações ou servlet container com o parâmetro:

-Duser.language=pt -Duser.region=BR

Criando seu próprio converter

Todos os conversores devem implementar a interface Converter do vraptor. A classe concreta definirá o tipo que ela é capaz de converter e será invocada com o valor do parâmetro do request e o tipo alvo.

public interface Converter<T> {
    T convert(String value, Class<? extends T> type);
}

Além disso, seu conversor deve ser anotado com o tipo que seu conversor é capaz de converter, para isso utilize a anotação @Convert:

@Convert(Long.class)
public class LongConverter implements Converter<Long> { ... }

Por fim, lembre-se de dizer se seu conversor pode ser instanciado em um escopo de Application, Session ou Request, assim como recursos e componentes quaisquer do VRaptor. Por exemplo, um conversor que não requer nenhuma informação do usuário logado pode ser registrado no escopo de Application e instanciado uma única vez:

@Convert(Long.class)
@ApplicationScoped
public class LongConverter implements Converter<Long> { ... }

A seguir, a implementação de LongConverter mostra como tudo isso pode ser utilizado de maneira bem simples:

@Convert(Long.class)
@ApplicationScoped
public class LongConverter implements Converter<Long> {

    public Long convert(String value, Class<? extends Long> type) {
        if (isNullOrEmpty(value)) {
            return null;
        }

        try {
            return Long.valueOf(value);
        } catch (NumberFormatException e) {
            throw new ConversionException(new ConversionMessage("is_not_a_valid_integer", value));
        }
    }
}

Populando outras propriedades de um objeto convertido

Nos exemplos acima, usamos os converters para converter um parâmetro do request em um objeto, por exemplo, parâmetro pais converter em um objeto Pais. Porém há casos onde precisamos apenas criar um objeto com alguma propriedade populada. Exemplo: para o parametro pais.nome podemos ter um objeto Pais com o atributo nome preenchido.

Neste caso, o value recebido no método será o valor do atributo nome. Basta então criar um converter como no exemplo abaixo:

@Convert(Pais.class)
@ApplicationScoped
public class PaisConverter implements Converter<Pais> {

    public Pais convert(String value, Class<? extends Pais> type) {
        Pais pais = new Pais();
        if (!isNullOrEmpty(value)) {
            pais.setNome(value)
        }

        return pais;
    }
}

Sobrescrevendo o comportamento dos conversores existentes

Você pode sobrescrever qualquer converter já existente no VRaptor. Para isso basta estender a classe anotando-a com @Specializes e também adicionando a anotação @Convert com o tipo que será especializado. Um bom exemplo para isso é sobrescrever o conversor BigDecimalConverter para alterar o formato do valor.

@Specializes
@Convert(BigDecimal.class)
public class MeuBigDecimalConverter extends BigDecimalConverter {

    @Override
    public BigDecimal convert(String value, Class<? extends BigDecimal> type) {
        // seu código aqui
        return super.convert(value, type);
    }

}

Você pode utilizar o @Specializes sempre que quiser reaproveitar a implementação da superclasse, caso contrário poderá criar um @Alternative com prioridade maior que a implementação do VRaptor. Como por exemplo:

@Alternative
@Priority(Interceptor.Priority.APPLICATION)
@Convert(BigDecimal.class)
public class MeuBigDecimalConverter implements Converter<BigDecimal>{

    @Override
    public BigDecimal convert(String value, Class<? extends BigDecimal> type) {
        return // seu código aqui
    }
}

Por padrão todo Converter do VRaptor tem prioridade LIBRARY_BEFORE.

Sobrescrevendo conversores de JSON e XML customizados

Você ainda pode customizar o pattern de uma Date implementando um CustomDateGsonConverter e sobrescrevendo o método getPattern():

@Specializes
public class CustomDateGsonConverter extends DateGsonConverter {
    @Override
    public DateFormat getPattern() { return //qualquer padrão customizado }
}