VRaptor

Compartilhando objetos com a view

Para registrar objetos a serem acessados na view, usamos o método include:

@Controller
class ClientController {

    @Inject
    private Result result;

    public void busca(int id) {
        result.include("mensagem", "Alguma mensagem");
        result.include("cliente", new Cliente(id));
    }
}

Agora as variáveis mensagem e cliente estão disponíveis para uso em seu template engine. É possível registrar o objeto por meio da invocação do método include com um único argumento:

@Controller
class ClientController {

    @Inject
    private Result result;

    public void busca(int id) {
        result.include("Alguma mensagem").include(new Cliente(id));
    }
}

Nesse caso, a primeira invocação registra a chave string e a segunda, a chave cliente. Você pode alterar o comportamento de convenção de chaves no seu próprio TypeNameExtractor.

Custom PathResolver

Por padrão, para renderizar suas views, o VRaptor segue a convenção:

public class ClientsController {
    public void list() { 
        //... 
    }
}

Este método acima renderizará a view /WEB-INF/jsp/clients/list.jsp. No entanto, nem sempre queremos esse comportamento, e precisamos usar algum template engine, como por exemplo, Freemarker ou Velocity, e precisamos mudar essa convenção. Um jeito fácil de mudar essa convenção é estendendo a classe DefaultPathResolver:

@Specializes
public class FreemarkerPathResolver extends DefaultPathResolver {
    protected String getPrefix() {
        return "/WEB-INF/freemarker/";
    }

    protected String getExtension() {
        return "ftl";
    }
}

Desse jeito, a lógica irá renderizar a view /WEB-INF/freemarker/clients/list.ftl. Se, ainda assim, isso não for o suficiente, você pode implementar a interface PathResolver e fazer qualquer convenção que você queira, não esquecendo de anotar a classe com @Specializes.

View

Se você quiser mudar a view de alguma lógica específica, você pode usar o objeto Result:

@Controller
public class ClientsController {

    @Inject
    private Result result;

    public void list() { ... }

    public void save(Client client) {
        //...
        result.use(Results.logic()).redirectTo(ClientsController.class).list();
    }
}

Por padrão, existem estes tipos de views implementadas:

Results.logic() que vai redirecionar para uma outra lógica qualquer do sistema
Results.page() que vai redirecionar diretamente para uma página, podendo ser um JSP, um HTML, ou qualquer URI relativa ao web application dir, ou ao contexto da aplicação.
Results.http() que manda informações do protocolo HTTP como status codes e headers.
Results.status() manda status codes com mais informações.
Results.referer() que usa o header Referer para fazer redirects ou forwards.
Results.nothing() apenas retorna o código de sucesso (HTTP 200 OK).
Results.xml() serializa objetos em XML.
Results.json() serializa objetos em JSON.
Results.representation() serializa objetos em um formato determinado pela requisição (parâmetro _format ou header Accept)

Atalhos no Result

Alguns redirecionamentos são bastante utilizados, então foram criados atalhos para eles. Os atalhos disponíveis são:

result.forwardTo(“/some/uri”) result.use(page()).forward(“/some/uri”);
result.redirectTo(“/some/uri”) result.use(page()).redirect(“/some/uri)
result.permanentlyRedirectTo(“/some/uri”) result.use(status()).movedPermanentlyTo(“/some/uri”);
result.forwardTo(ClientController.class).list() result.use(logic()).forwardTo(ClientController.class).list();
result.redirectTo(ClientController.class).list() result.use(logic()).redirectTo(ClientController.class).list();
result.of(ClientController.class).list() result.use(page()).of(ClientController.class).list();
result.permanentlyRedirectTo(Controller.class) use(status()).movedPermanentlyTo(Controller.class);
result.notFound() use(status()).notFound()
result.nothing() use(nothing());

Além disso, se o redirecionamento é para um método do mesmo Controller, podemos usar:

result.forwardTo(this).list() result.use(logic()).forwardTo(this.getClass()).list();
result.redirectTo(this).list() result.use(logic()).redirectTo(this.getClass()).list();
result.of(this).list() result.use(page()).of(this.getClass()).list();
result.permanentlyRedirectTo(this) use(status()).movedPermanentlyTo(this.getClass());

Redirecionamento e forward

No VRaptor, podemos tanto realizar um redirect ou um forward do usuário para uma outra lógica ou um JSP. Apesar de serem conceitos da API de Servlets, vale a pena relembrar a diferença: o redirecionamento acontece no lado do cliente, através de códigos HTTP que farão o browser acessar uma nova URL; já o forward acontece no lado do servidor, totalmente transparente para o cliente/browser.

Um bom exemplo de uso do redirect é no chamado ‘redirect-after-post’. Por exemplo: quando você adiciona um cliente e que, após o formulário submetido, o cliente seja retornado para a página de listagem de clientes. Fazendo isso com redirect, impedimos que o usuário atualize a página (F5) e reenvie toda a requisição, acarretando em dados duplicados.

No caso do forward, um exemplo de uso é quando você possui uma validação e essa validação falhou, geralmente você quer que o usuário continue na mesma tela do formulário com os dados da requisição preenchidos, mas internamente você vai fazer o forward para outra lógica de negócios (a que prepara os dados necessários para o formulário).

Escopo Flash automático

Se você adicionar objetos no Result e fizer um Redirect, esses objetos estarão disponíveis na próxima requisição.

public void adiciona(Cliente cliente) {
    dao.adiciona(cliente);
    result.include("mensagem", "Cliente adicionado com sucesso");
    result.redirectTo(ClientesController.class).lista();
}

lista.jsp:

<div id="mensagem">
    <h3>${mensagem}</h3>
</div>

Accepts e o parâmetro _format

Muitas vezes, precisamos renderizar formatos diferentes para uma mesma lógica. Por exemplo queremos retornar um JSON, em vez de um HTML. Para fazer isso, podemos definir o Header Accepts da requisição para que aceite o tipo desejado, ou colocar um parâmetro _format na requisição.

Se o formato for JSON, a view renderizada por padrão será: /WEB-INF/jsp/{controller}/{logic}.json.jsp, ou seja, em geral será renderizada a view: /WEB-INF/jsp/{controller}/{logic}.{formato}.jsp. Se o formato for HTML você não precisa colocá-lo no nome do arquivo. O parâmetro _format tem prioridade sobre o header Accepts.

Ajax: construindo na view

Para devolver um JSON na sua view, basta que sua lógica disponibilize o objeto para a view, e dentro da view você forme o JSON como desejar. Como no exemplo, o seu /WEB-INF/jsp/clients/load.json.jsp:

{ nome: '${client.name}', id: '${client.id}' }

E na lógica:

@Controller
public class ClientsController {

    @Inject
    private Result result;

    @Inject
    private ClientDao dao;

    public void load(Client client) {
        result.include("client", dao.load(client));
    }
}

Ajax: Versão programática

Se você quiser que o VRaptor serialize automaticamente seus objetos para XML ou JSON, você pode escrever em sua lógica:

import static br.com.caelum.vraptor.view.Results.*;
@Controller
public class ClientsController {

    @Inject
    private Result result;

    @Inject
    private ClientDao dao;

    public void loadJson(Cliente cliente) {
        result.use(json()).from(cliente).serialize();
    }
    public void loadXml(Cliente cliente) {
        result.use(xml()).from(cliente).serialize();
    }
}

Os resultados vão ser parecidos com:

{"cliente": {
    "nome": "Joao"
}}

e em xml:

<cliente>
    <nome>Joao</nome>
</cliente>

Por padrão, apenas campos de tipos primitivos serão serializados (String, números, enums, datas), se você quiser incluir um campo de tipo não primitivo você precisa incluí-lo explicitamente:

result.use(json()).from(cliente).include("endereco").serialize();

Vai resultar em algo parecido com:

{"cliente": {
    "nome": "Joao",
    "endereco" {
        "rua": "Vergueiro"
    }
}}

Você pode também excluir alguns da serialização:

result.use(json()).from(usuario).exclude("senha").serialize();

Vai resultar em algo parecido com:

{"usuario": {
    "nome": "Joao",
    "login": "joao"
}}

Você também pode excluir ou incluir vários campos.

result.use(json()).from(usuario).recursive().serialize(); // inclui todos os campos recursivamente
result.use(xml()).from(usuario).exclude("email").serialize(); // excluir o campo email
result.use(xml()).from(usuario).excludeAll().serialize(); // exclui todos os campos

Por padrão atributos com valores null não são serializados no json, é possível modificar este comportamento chamando o método serializeNulls: ~~~ #!java result.use(json()).serializeNulls().from(new Pessoa(“João”, null)).serialize(); ~~~

O JSON resultante será o seguinte: ~~~ #!javascript {“pessoa”:{ “nome”: “João”, “idade”: null }} ~~~

Você pode adicionar versionamento aos seus resultados baseados em JSON. Através desse recurso é possível versionar uma API. Para exemplificar a utilidade do versionamento, suponha que foi criada uma API que retorna clientes de um determinado sistema. A primeira versão, retorna clientes definidos através da seguinte estrutura:

public class Cliente {
    private String nome;    
    private String ramo;

    // getters e setters omitidos
}

Agora suponha que você criou um método no seu controlador que possui o seguinte resultado:

@Get{"/clientes"}
public void clientes() {
    List<Cliente> clientes = repositorioDeClientes.getAll();
    result.use(json()).from(clientes).serialize();    
}

Até agora não há problema algum. Porém, caso seja necessário disponibilizar um novo atributo como resultado que só fará sentido para uma nova versão da API, como poderíamos contornar a situação? Poderiamos criar uma segunda classe chamada ClienteV2 com o novo atributo e o método do controlador que retorna os clientes poderia ser reescrito conforme abaixo:

@Get{"/{versaoParam}/clientes"}
public void clientes(String versaoParam) {
    double versao = Double.parseDouble(versaoParam);
    if (versao == 2.0) {
        result.use(json()).from(repositorioDeClientes.getAll()).serialize();
    } else {
        result.use(json()).from(repositorioDeClientes.getAllFromV2()).serialize();
    }
}

Certamente a solução acima não é nada boa. Como o VRaptor utiliza a biblioteca GSON (criada pelo Google), podemos utilizar a mesma classe Cliente utilizando a anotação @Since nos atributos que queremos versionar. Essa anotação indica que um determinado atributo está disponível apartir de uma versão x conforme o exemplo abaixo:

public class Cliente {
    private String nome;
    private String ramo;

    @Since(2.0)
    private Calendar dataDeRegistro;    

    // getters e setters omitidos
}

Dessa forma o método do controlador agora pode ser reescrito da seguinte forma:

@Get{"/{versaoParam}/clientes"}
public void clientes(String versaoParam) {
    double versao = Double.parseDouble(versaoParam);
    List<Cliente> clientes = repositorioDeClientes.getAll();
    result.use(json()).version(versao).from(clientes).serialize();
}

Pronto, agora os clientes de nossa API, podem solicitar um resultado com uma versão específica.

A implementação para serialização de XML padrão é baseada no XStream, então é possível configurar a serialização por anotações ou configurações diretas ao XStream, bastando criar a classe:

@Specializes
public class CustomXStreamBuilder extends XStreamBuilderImpl {

    @Override
    protected XStream xmlInstance() {
        XStream xStream = super.xmlInstance();
        //suas configurações ao XStream aqui
        return xStream;
    }
}

Para configurar a serialização com JSON você pode criar uma classe que estenda de GsonJSONSerialization e sobrescrever o método getSerializer() com as suas configurações.

Na serialização JSON você pode utilizar a anotação SkipSerialization para omitir a serialização de um campo ou de uma classe.

A classe abaixo foi anotada com SkipSerialization, portanto os seus campos nunca serão serializados para JSON:

@SkipSerialization
public class UserPrivateInfo {

    private String cpf;
    private String telefone;
    private String endereco;
    
    // getters e setters
}

Nesta outra classe, o campo senha não será serializado. O campo info também não será serializado pois a classe UserPrivateInfo foi anotado com SkipSerialization:

public class User {

    private String nome;
    private String login;
    
    @SkipSerialization
    private String senha;
    
    private UserPrivateInfo info;
    
    // getters e setters
}

Realizando a seguinte chamada:

User user = ...;
result.use(json()).withoutRoot().from(user).recursive().serialize();

O resultado obtido é semelhante à:

{
    "nome": "Joao",
    "login": "joao"
}

Perceba que o campo senha e todo o UserPrivateInfo não foram serializados.

Serializando Collections

Ao serializar coleções, o padrão é colocar a tag list em volta dos elementos:

List<Cliente> clientes = ...;
result.use(json()).from(clientes).serialize();
//ou
result.use(xml()).from(clientes).serialize();

Vai resultar em algo como:

{"list": [
    { "nome": "Joao" },
    { "nome": "Maria" }
]}

ou

<list>
    <cliente>
        <nome>Joao</nome>
    </cliente>
    <cliente>
        <nome>Maria</nome>
    </cliente>
</list>

É possível personalizar o elemento de fora usando o método:

List<Cliente> clientes = ...;
result.use(json()).from(clientes, "clientes").serialize();
//ou
result.use(xml()).from(clientes, "clientes").serialize();

Vai resultar em algo como:

{"clientes": [
    {"nome": "Joao"},
    {"nome": "Maria"}
]}

ou

<clientes>
    <cliente>
        <nome>Joao</nome>
    </cliente>
    <cliente>
        <nome>Maria</nome>
    </cliente>
</clientes>

Os includes e excludes funcionam como se você os estivesse aplicando num elemento de dentro da lista. Por exemplo se você quiser incluir o endereço no cliente:

List<Cliente> clientes = ...;
result.use(json()).from(clientes).include("endereco").serialize();

Com resultado:

{"list": [
    {
        "nome": "Joao",
        "endereco": { "rua": "Vergueiro, 3185" }
    },
    {
        "nome": "Maria",
        "endereco": { "rua": "Vergueiro, 3185" }
    }
]}

Serializando JSON sem elemento raiz

Se você quiser serializar um objeto em JSON sem dar nomes a eles, pode fazer isso com o método withoutRoot:

result.use(json()).from(carro).serialize(); //=> {'carro': {'cor': 'azul'}}
result.use(json()).withoutRoot().from(carro).serialize(); //=> {'cor': 'azul'}

Indentação do resultado JSON e XML

Por padrão, o resultado da serialização é sem indentação, o que pode ser útil para melhorar o tempo de download do browser. Porém se você precisar exibir o resultado do JSON ou XML com indentação, você pode usar o método indented.

result.use(json()).indented().from(carro).serialize();

Dessa forma o resultado seria:

{
  'carro':
  {
    'cor': 'azul'
  }
}

Ou você pode também usar o Environment para definir a indentação padrão conforme o ambiente. Neste caso você pode definir que no ambiente DEVELOPMENT a indentação será ativada, e no ambiente PRODUCTION será desativada.

development.properties:

br.com.caelum.vraptor.serialization.xml.indented=true
br.com.caelum.vraptor.serialization.json.indented=true

production.properties:

br.com.caelum.vraptor.serialization.xml.indented=false
br.com.caelum.vraptor.serialization.json.indented=false

Nota: A configuração do Environment possui prioridade menor que no código. Ou seja, se você definir br.com.caelum.vraptor.serialization.xml.indented=false no Environment e chamar o método indented no seu código, o resultado será um JSON ou XML indentado.

LinkTo é uma funcionalidade que permite você criar links nas páginas JSP sem precisar escrever o link, mas sim indicando a classe do controller, e o método.

Para o seguinte controller:

public class ClientController {
    @Path("/client/list")
    public void list() {
        // alguma lógica aqui
    }

    @Path("/client/{id}")
    public void show(Long id) {
        // alguma lógica aqui
    }
}

Para chamar os métodos list e show respectivamente, teriamos que escrever no JSP:

<a href="/client/list">listar clientes</a>
<a href="/client/1">ver cliente 1</a>

Usando a funcionalidade do linkTo, podemos simplicar a chamada para o método list:

<a href="${linkTo[ClientController].list()}">listar clientes</a>

Como o método não possui parâmetros, você também pode omitir os parênteses.

<a href="${linkTo[ClientController].list}">listar clientes</a>

Para chamar o método show, podemos fazer a chamada desta forma:

<a href="${linkTo[ClientController].show(1)}">ver cliente 1</a>

Ao invés do parâmetro fixo, você pode passar variáveis do JSTL, como por exemplo:

<a href="${linkTo[ClientController].show(client.id)}">ver cliente ${client.name}</a>

Inclusão automática de parâmetros

Você pode anotar seu método com @IncludeParameters para que todos os parâmetros de um método do seu controller sejam inclusos automaticamente na view.

Então, no lugar de fazer algo como:

public void filtra(Intervalo intervalo, Representante representante,
        Unidade unidade, BigDecimal valorMaximo, BigDecimal valorMin) {

    // lógica de filtro

    result.include("intervalo", intervalo);
    result.include("representante", representante);
    result.include("valorMaximo", valorMaximo);
    result.include("valorMin", valorMin);
}

Agora você pode fazer:

@IncludeParameters
public void filtra(Intervalo intervalo, Representante representante,
        Unidade unidade, BigDecimal valorMaximo, BigDecimal valorMin) {

    // lógica de filtro
}

Todos os parâmetros estarão disponiveis pela jsp com EL: ${intervalo.inicio}