VRaptor

Começando o projeto: uma loja virtual

Um cadastro de produtos

Para começar o sistema, vamos começar com cadastro de produtos. Precisamos de uma classe que vai representar o produto, e vamos usá-la para guardar produtos no banco, usando o JPA/Hibernate:

@Entity
public class Produto {
    @Id
    @GeneratedValue
    private Long id;

    private String nome;
    private String descricao;
    private Double preco;

    //getter e setters e métodos de negócio que julgar necessário
}

Precisamos também de uma classe que vai controlar o cadastro de produtos, tratando requisições web. Essa classe vai ser o Controller de produtos:

public class ProdutosController {}

A classe ProdutosController vai expor URIs para serem acessadas via web, ou seja, vai expor recursos da sua aplicação. E, para indicar isso, você precisa anotá-la com com @Controller:

@Controller
public class ProdutosController {}

Ao colocar essa anotação na classe, todos os métodos públicos dela serão acessíveis pela web. Por exemplo, se tivermos um método lista na classe:

@Controller
public class ProdutosController {
    public List<Produto> lista() {
        return new ArrayList<Produto>();
    }
}

O VRaptor automaticamente redirecionará todas as requisições à URI /produtos/lista para esse método. Ou seja, a convenção para a criação de URIs é /<nome_do_controller>/<nome_do_metodo>. Ao terminar a execução do método, o VRaptor fará o dispatch da requisição para o JSP /WEB-INF/jsp/produtos/lista.jsp. Ou seja, a convenção para a view padrão é /WEB-INF/jsp/<nome_do_controller>/<nome_do_metodo>.jsp.

O método lista retorna uma lista de produtos, mas como acessá-la no JSP? No VRaptor, o retorno do método é exportado para a JSP por meio de atributos da requisição. No caso do método lista, vai existir um atributo chamado produtoList contendo a lista retornada:

<ul>
<c:forEach items="${produtoList}" var="produto">
    <li> ${produto.nome} - ${produto.descricao} </li>
</c:forEach>
</ul>

A convenção para o nome dos atributos exportados é bastante intuitiva: se for uma collection, como o caso do método acima, o atributo será List, produtoList no caso; se for uma classe qualquer vai ser o nome da classe com a primeira letra minúscula. Se o método retorna Produto, o atributo exportado será produto. Veremos em outro capítulo que podemos expor mais de um objeto sem usar o retorno, por meio do Result, onde podemos dar nome à variável exposta.

Criando o ProdutoDao: Injeção de Dependências (DI)

O VRaptor usa fortemente o conceito de Injeção de Dependências (DI) e Inversão de Controle (IoC). A ideia é que você não precise criar ou buscar as dependências da sua classe, você simplesmente as recebe e o VRaptor se encarrega de criá-las pra você. Estamos retornando uma lista vazia no nosso método lista. Seria muito mais interessante retornar uma lista de verdade, por exemplo todas os produtos cadastrados no sistema. Para isso vamos criar um DAO de produtos, para fazer a listagem:

public class ProdutoDao {

    public List<Produto> listaTodos() {
        return new ArrayList<Produto>();
    }
}

E no nosso ProdutosController podemos usar o dao pra fazer a listagem de produtos:

@Controller
public class ProdutosController {

    @Inject
    private ProdutoDao dao;

    public List<Produto> lista() {
        return dao.listaTodos();
    }
}

Podemos instanciar o ProdutoDao direto do controller, porém é muito mais interessante aproveitar o gerenciamento de dependências que o VRaptor faz e receber o DAO. E, para que isso seja possível, basta anotar o DAO com @RequestScoped, anotar o atributo dao do controller com @Inject e o VRaptor vai se encarregar de criá-lo e injetá-lo na sua classe:

@RequestScoped
public class ProdutoDao {
    //...
}

@Controller
public class ProdutosController {
    //...

    @Inject
    private ProdutoDao dao;

    public List<Produto> lista() {
        return dao.listaTodos();
    }
}

Formulário de adição: redirecionamento

Temos uma listagem de produtos, mas ainda não temos como cadastrá-los. Vamos então criar um formulário de adição de produtos. Para não ter que acessar o JSP diretamente, vamos criar uma lógica vazia que só redireciona para o JSP:

@Controller
public class ProdutosController {
    //...
    public void form() { ... }
}

Podemos acessar o formulário, que estará em /WEB-INF/jsp/produtos/form.jsp, pela URI /produtos/form:

<form action="<c:url value='/produtos/adiciona'/>">
    Nome:
    <input type="text" name="produto.nome"/><br/>
    Descrição:
    <input type="text" name="produto.descricao"/><br/>
    Preço:
    <input type="text" name="produto.preco"/><br/>

    <input type="submit" value="Salvar" />
</form>

Como o formulário vai salvar o produto pela URI /produtos/adiciona, precisamos criar esse método no nosso controller:

@Controller
public class ProdutosController {
    
    //...
    public void adiciona() { ... }
}

Repare nos nomes dos nossos inputs: produto.nome, produto.descricao e produto.preco. Se recebermos um Produto no método adiciona com o nome produto, o VRaptor vai popular os seus campos nome, descricao e preco, usando os seus setters no Produto, com os valores digitados nos inputs. Inclusive o campo preco, vai ser convertido para Double antes de ser setado no produto. Veja mais sobre isso no capítulo de converters.

Então, usando os nomes corretamente nos inputs do form, basta criar seu método adiciona:

@Controller
public class ProdutosController {
    
    //...
    public void adiciona(Produto produto) {
        dao.adiciona(produto);
    }
}

Geralmente, depois de adicionar algo no sistema queremos voltar para a sua listagem, ou para o formulário novamente. No nosso caso, queremos voltar pra listagem de produtos ao adicionar um produto novo. Para isso existe um componente do VRaptor: o Result. Ele é responsável por adicionar atributos na requisição, e por mudar a view a ser carregada. Se eu quiser uma instância de Result, basta declarar o atributo no controller e anota-lo com @Inject:

@Controller
public class ProdutosController {

    @Inject
    private ProdutoDao dao;

    @Inject
    private Result result;

    //...
}

E para redirecionar para a listagem basta usar o result:

result.redirectTo(ProdutosController.class).lista();

Podemos ler esse código como: Como resultado, redirecione para o método lista do ProdutosController. A configuração de redirecionamento é 100% java, sem strings envolvidas! Fica explícito no seu código que o resultado da sua lógica não é o padrão e qual resultado você está usando! Você não precisa ficar se preocupando com arquivos de configuração. Mais ainda, se eu quiser mudar o nome do método lista, eu não preciso ficar rodando o sistema inteiro procurando onde estão redirecionando pra esse método, basta usar o refactor do eclipse, por exemplo, e tudo continua funcionando!

Então nosso método adiciona ficaria:

public void adiciona(Produto produto) {
    dao.adiciona(produto);
    result.redirectTo(ProdutosController.class).lista();
}

Veja mais informações sobre o Result no capítulo Views e Ajax.

Usando o JPA/Hibernate para guardar os Produtos

Agora vamos fazer uma implementação de verdade do ProdutoDao, usando o JPA para persistir os produtos. Para isso, nosso ProdutoDao precisa de um EntityManager. Como o VRaptor usa injeção de dependências, basta declarar um atributo do tipo EntityManager e anota-lo com @Inject.

public class ProdutoDao {

    @Inject
    private EntityManager manager;

    public void adiciona(Produto produto) {
        manager.persist(produto);
    }
    //...
}

O VRaptor precisa saber como criar esse EntityManager, e eu não posso simplesmente colocar um @RequestScoped na EntityManager pois é uma classe da JPA. Para isso precisamos criar um @Produces.

Veja mais informações de como fazer isso no capítulo de Componentes. Você pode, ainda, usar o plugin para JPA que já existe para o VRaptor. Ele já faz todo esse trabalho para você e ainda te ajuda a fazer o controle de transações. Você pode ler mais sobre ele na página de plugins.

Controlando transações: Interceptors

Muitas vezes, queremos interceptar todas as requisições (ou uma parte delas) e executar alguma lógica, como acontece com o controle de transações. Para isso, existem os Interceptors no VRaptor. Saiba mais sobre eles no capítulo de Interceptors.

Carrinho de compras: Componentes na sessão

Se quisermos criar um carrinho de compras no nosso sistema, precisamos de alguma forma manter os itens do carrinho na sessão do usuário. Para fazer isso, podemos criar um componente que está no escopo de sessão, ou seja, ele vai ser único na sessão do usuário. Para isso, basta criar um componente anotado com @SessionScoped:

@SessionScoped
public class CarrinhoDeCompras implements Serializable{
    private List<Produto> itens = new ArrayList<Produto>();

    public List<Produto> getTodosOsItens() {
        return itens;
    }

    public void adicionaItem(Produto item) {
        itens.add(item);
    }
}

Essa classe precisa implementar Serializable já que o escopo dela é o SessionScoped e os objetos criados a partir dela podem ser serializados para manter o estado da sessão. Essa necessidade é descrita na especificação no CDI e deve ser respeitada. Caso contrário o container vai lançar uma exception no momento da inicialização.

Como esse carrinho de compras é um componente, podemos recebê-lo no controller que vai cuidar do carrinho de compras:

@Controller
public class CarrinhoController {

    @Inject
    private CarrinhoDeCompras carrinho;

    public void adiciona(Produto produto) {
        carrinho.adicionaItem(produto);
    }

    public List<Produto> listaItens() {
        return carrinho.getTodosOsItens();
    }
}

Além do escopo de sessão existe o escopo de Aplicação com a anotação @ApplicationScoped. Os componentes anotados com @ApplicationScoped serão criados apenas uma vez em toda a aplicação.

Um pouco de REST

Seguindo a idéia REST de que URIs devem identificar recursos na rede para então podermos fazer valer as diversas vantagens estruturais que o protocolo HTTP nos proporciona, note o quão simples fica mapear os diversos métodos HTTP para a mesma URI, e com isso invocar diferentes métodos. Por exemplo, queremos usar as seguintes URIs para o cadastro de produtos:

GET /produtos lista todos os produtos
POST /produtos adiciona um produto
GET /produtos/{id} visualiza o produto com o id passado
PUT /produtos/{id} atualiza as informações do produto com o id passado
DELETE /produtos/{id} remove o produto com o id passado

Para criar esse comportamento REST no VRaptor, podemos usar as anotações @Path - que muda qual é a URI que vai acessar o determinado método, e as anotações com os nomes dos métodos HTTP @Get, @Post, @Delete, @Put, que indicam que o método anotado só pode ser acessado se a requisição estiver com o método HTTP indicado. Então, uma versão REST do nosso ProdutosController seria:

public class ProdutosController {
    //...

    @Get
    @Path("/produtos")
    public List<Produto> lista() { ... }

    @Post("/produtos")
    public void adiciona(Produto produto) { ... }

    @Get("/produtos/{produto.id}")
    public void visualiza(Produto produto) { ... }

    @Put("/produtos/{produto.id}")
    public void atualiza(Produto produto) { ... }

    @Delete("/produtos/{produto.id}")
    public void remove(Produto produto) { ... }
}

Note que podemos receber parâmetros nas URIs. Por exemplo, se chamarmos a URI GET /produtos/5, o método visualiza será invocado, e o parâmetro produto vai ter o id populado com 5.

Mais informações sobre isso no capítulo de Controllers-REST.

Arquivo de mensagens

Internacionalização (i18n) é um recurso poderoso, e que está presente em quase todos os frameworks Web hoje em dia. E não é diferente no VRaptor. Com i18n podemos fazer com que nossa aplicação suporte várias línguas (francês, português, espanhol, inglês etc) de uma maneira que não nos cause muito esforço, bastando apenas fazermos a tradução das mensagens da nossa aplicação.

Para isso, é só criarmos um arquivo chamado messages.properties e disponibilizá-lo no classpath da nossa aplicação (WEB-INF/classes). Esse arquivo conterá várias linhas compostas por um conjunto de chave/valor, como por exemplo:

campo.nomeUsuario = Nome de Usuário
campo.senha = Senha

Até então está fácil, mas e se quisermos criar esses arquivos para várias línguas, como por exemplo, inglês? Simples, basta criarmos um outro arquivo properties chamado messages_en.properties. Repare no sufixo _en no nome do arquivo. Isso indica que quando o usuário acessar sua aplicação em uma máquina configurada com locale em inglês as mensagens desse arquivo serão utilizadas. O conteúdo desse arquivo então ficaria:

campo.nomeUsuario = Username
campo.senha = Password

Repare que as chaves são mantidas, mudando apenas o valor para a língua escolhida. Para usar essas mensagens em seus arquivos JSP, você pode utilizar a JSTL.

Dessa forma, o código ficaria:

<!%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
    <body>
        <fmt:message key="campo.usuario"/>
        <input name="usuario.nomeUsuario"/> <br/>

        <fmt:message key="campo.senha"/>
        <input type="password" name="usuario.senha"/>

        <input type="submit"/>
    </body>
</html>

Próximos passos

Continue explorando as próximas seções desta documentação, os Cookbooks, com soluções de problemas comuns e o Livro de VRaptor pela editora Casa do Código.

Se você tiver dúvidas, procure ajuda no GUJ ou na nossa lista de discussão.

Você pode também ver tudo isso e muitos outros recursos de forma prática em nosso projeto de exemplo, o vraptor-music-jungle