VRaptor

O que são Controllers?

Um controller é uma classe que disponibiliza recursos a nossos clientes. No caso de uma aplicação Web baseada no VRaptor, um controller deve ser anotado com a anotação @Controller. Assim que o programador insere tal anotação em seu código, todos os métodos públicos desse recurso se tornam acessíveis através de chamadas do tipo GET ou POST a URIs específicas.

O exemplo a seguir mostra um recurso chamado ClienteController que possui métodos para diversas funcionalidades ligadas a um cliente. Simplesmente criar essa classe e os métodos faz com que as URLs /cliente/adiciona, /cliente/lista, /cliente/visualiza, /cliente/remove e /cliente/atualiza sejam disponibilizadas, cada uma invocando o respectivo método em sua classe.

@Controller
public class ClienteController {

    public void adiciona(Cliente cliente) {}

    public List<Cliente> lista() {
        return ...
    }

    public Cliente visualiza(Cliente perfil) {
        return ...
    }

    public void remove(Cliente cliente) { ... }

    public void atualiza(Cliente cliente) { ... }
}

Parâmetros dos métodos

Você pode receber parâmetros nos métodos dos seus controllers, e se o objeto usar a convenção de java beans (getters e setters para os atributos da classe), você pode usar pontos para navegar entre os atributos. Por exemplo, no método:

public void atualiza(Cliente cliente) { ... }

você pode passar como parâmetro na requisição:

cliente.id=3
cliente.nome=Fulano
cliente.usuario.login=fulano

e os campos correspondentes, navegando por getters e setters a partir do cliente, serão setados. Se um atributo do objeto ou parâmetro do método for uma lista (List<>, array ou Set<>), você pode passar vários parâmetros usando colchetes e índices:

cliente.telefones[0]=(11) 5571-2751 no caso de ser uma lista de String
cliente.dependentes[0].id=1 no caso de ser qualquer objeto, você pode continuar a navegação
cliente.dependentes[3].id=1 os índices não precisam ser sequenciais
cliente.dependentes[0].nome=Cicrano se usar o mesmo índice, vai ser setado no mesmo objeto
clientes[1].id=23 funciona se você receber uma lista de clientes no método

Reflection no nome dos parâmetros

O VRaptor tira proveito do framework Paranamer, que consegue extrair essa informação por meio dos dados de debug. Alguns dos desenvolvedores do VRaptor também participam no desenvolvimento do Paranamer.

Escopos

Por vezes você deseja compartilhar um componente entre todos os usuários, entre todas as requisições de um mesmo usuário ou a cada requisição de um usuário. Para definir em que escopo o seu componente vive, basta utilizar as anotações @ApplicationScoped, @SessionScoped, @RequestScoped, @Dependent e @Conversation. Caso nenhuma anotação seja utilizada, o VRaptor assume que seu componente ficará com escopo dependent, isto é, você terá um novo componente a cada ponto de injeção.

Esses escopos estão definidos na especificação do CDI. Para mais informações veja a página sobre componentes.

@Path

A anotação @Path permite que você customize as URIs de acesso a seus métodos. O uso básico dessa anotação é feito por meio de uma URI fixa. O exemplo a seguir mostra a customização de uma URI para um método, que somente receberá requisições do tipo POST na URI /cliente:

@Controller
public class ClienteController {

    @Path("/cliente")
    @Post
    public void adiciona(Cliente cliente) { ... }
}

Se você anotar o ClienteController com @Path, o valor especificado vai ser usado como prefixo para todas as URIs desse controller:

@Controller
@Path("/clientes")
public class ClienteController {
    
    public void lista() {...}

    @Path("salva")
    public void adiciona() {...}

    @Path("/todosOsClientes")
    public void listaTudo() {...}
}

Anotando os métodos com @Path, teremos as seguntes URIs

void lista() URI: /clientes/lista
void adiciona() URI: /clientes/salva
void listaTudo() URI: /clientes/todosOsClientes

Métodos HTTP

Desenvolvendo sua aplicação com recursos REST, o ideal é aproveitar a semântica de cada método HTTP. Portanto às vezes precisamos usar métodos como GET, POST, PUT etc para acessar uma mesma URI. Para atingir esse objetivo, utilizamos as anotações @Get, @Post, @Delete, etc que também permitem configurar uma URI diferente da URI padrão, da mesma forma que a anotação @Path. O exemplo a seguir altera os padrões de URI do ClienteController para utilizar duas URIs distintas, com diversos métodos HTTP:

@Controller
public class ClienteController {

    @Post("/cliente")
    public void adiciona(Cliente cliente) {...}

    @Path("/")
    public List<Cliente> lista() {
        return ...
    }

    @Get("/cliente")
    public Cliente visualiza(Cliente cliente) {
        return ...
    }

    @Delete("/cliente")
    public void remove(Cliente cliente) { ... }

    @Put("/cliente")
    public void atualiza(Cliente cliente) { ... }
}

Como você pode notar, utilizamos os métodos HTTP + uma URI específica para identificar cada um dos métodos da nossa classe Java.

No momento de criar os links e formulários HTML devemos tomar um cuidado muito importante pois os browsers só dão suporte aos métodos POST e GET por meio de formulários hoje em dia. Por isso, você deverá criar as requisições para métodos do tipo DELETE, PUT etc usando JavaScript ou passando um parâmetro extra, chamado _method. O último só funciona em requisições POST. Esse parâmetro sobrescreverá o método HTTP real sendo invocado.

O exemplo a seguir mostra um link para o método visualiza de cliente:

<a href="/cliente?cliente.id=5">ver cliente 5</a>

Agora um exemplo de como invocar o método de adicionar um cliente:

<form action="/cliente" method="post">
    <input name="cliente.nome" />
    <input type="submit" />
</form>

E, note que para permitir a remoção pelo método DELETE, temos que usar o parâmetro _method, uma vez que o browser ainda não suporta tais requisições:

<form action="/cliente" method="post">
    <input name="cliente.id" value="5" type="hidden" />
    <button type="submit" name="_method" value="DELETE">remover cliente 5</button>
</form>

Path com injeção de variáveis

Em diversos casos, desejamos que a URI que identifica nosso recurso tenha como parte de seu valor, por exemplo, o identificador único do nosso recurso.

Suponha o exemplo de um controle de clientes onde meu identificador único (chave primária) é um número, podemos então mapear as URIs /cliente/{cliente.id} para a visualização de cada cliente. Isto é, se acessarmos a URI /cliente/2, o método visualiza será invocado e o parâmetro cliente.id será setado para 2. Caso a URI /cliente/1717 seja acessada, o mesmo método será invocado com o valor 1717.

Dessa maneira, criamos URIs únicas para identificar recursos diferentes do nosso sistema. Veja o exemplo:

@Controller
public class ClienteController {

    @Get
    @Path("/cliente/{cliente.id}")
    public Cliente visualiza(Cliente cliente) {
        return ...
    }
}

Você pode ir além e setar diversos parâmetros pela URI:

@Controller
public class ClienteController {

    @Get
    @Path("/cliente/{cliente.id}/visualiza/{secao}")
    public Cliente visualiza(Cliente cliente, String secao) {
        return ...
    }
}

Vários paths para a mesma lógica

Você pode setar mais de um path para a mesma lógica fazendo:

@Controller
public class ClienteController {

    @Path({"/cliente/{cliente.id}/visualiza/{secao}", "/cliente/{cliente.id}/visualiza/"})
    public Cliente visualiza(Cliente cliente, String secao) {
        return ...
    }
}

Paths com expressões regulares

Você pode restringir o valor dos parâmetros da sua URI com expressões regulares, dessa maneira:

@Path("/cor/{cor:[0-9A-Fa-f]{6}}")
public void setCor(String cor) { ... }

Tudo que estiver depois do : será tratado como uma regex, e a URI só vai casar se o parâmetro casar com a regex:

/cor/a0b3c4 => casa
/cor/AABBCC => casa
/cor/branco => não casa

Paths com *

Você também pode utilizar o * como método de seleção para a sua URI. O exemplo a seguir ignora qualquer coisa após a palavra foto/ :

@Controller
public class ClienteController {

    @Get
    @Path("/cliente/{cliente.id}/foto/*")
    public File foto(Cliente cliente) {
        return ...
    }
}

E agora o mesmo código, mas utilizado para baixar uma foto específica de um cliente:

@Controller
public class ClienteController {

    @Get
    @Path("/cliente/{cliente.id}/foto/{foto.id}")
    public File foto(Cliente cliente, Foto foto) {
        return ...
    }
}

Por vezes você deseja que o parâmetro a ser setado inclua também /s. Para isso você deve utilizar o padrão {...*}:

@Controller
public class ClienteController {

    @Get
    @Path("/cliente/{cliente.id}/download/{path*}")
    public File download(Cliente cliente, String path) {
        return ...
    }
}

Definindo prioridades para seus paths

É possível que algumas das nossas URIs possam ser tratada por mais de um método da classe:

@Controller
public class PostController {

    @Get
    @Path("/post/{post.autor}")
    public void mostra(Post post) { ... }

    @Get
    @Path("/post/atual")
    public void atual() { ... }
}

A URI /post/atual pode ser tratada tanto pelo método mostra, quanto pelo atual. Mas eu quero que quando seja exatamente /post/atual o método executado seja o atual. O que eu quero é que o VRaptor teste primeiro o path do método atual, para não correr o risco de cair no método mostra.

Para fazer isso, podemos definir prioridades para os @Paths, assim o VRaptor vai testar primeiro os paths com maior prioridade, ou seja, valor menor de prioridade:

@Controller
public class PostController {

    @Get
    @Path(value = "/post/{post.autor}", priority=Path.LOW)
    public void mostra(Post post) { ... }

    @Get
    @Path(value = "/post/atual", priority=Path.HIGH)
    public void atual() { ... }
}

Assim, o path /post/atual vai ser testado antes do /post/{post.autor}, e o VRaptor vai fazer o que a gente gostaria que ele fizesse. Você não precisa definir prioridades se tivermos as uris: /post/{post.id} e /post/atual, pois na /post/{post.id} o vraptor só vai aceitar números.

Paths com headers

Você também pode injetar HTTP-Headers na sua lógica usando a anotação HeaderParam. No exemplo abaixo o parâmetro username será injetado com o valor do header X-Auth-User.

public void profile(@HeaderParam("X-Auth-User") String username) { ... }

Atenção: No exemplo acima, se você tiver paramêtro com a chave username e você definir o HeaderParam com o mesmo nome do parâmetro, o valor do parâmetro será sobrescrito com o valor do header.

Consumindo em outros formatos

Outra possibilidade é enviar os dados do objeto que você recebe como parâmetro nos métodos de seu controller em XML ou JSON. Para que isso funcione basta adicionar a anotação @Consumes, definindo o media type que será aceito neste método.

Um exemplo seria:

@Consumes("application/json")
@Post
public void adicionar(Cliente cliente) {
    clienteDAO.salvar(cliente);
}

Com isso você já pode fazer uma requisição POST com o content-type application/json passando seu JSON como parâmetro, algo como:

{"cliente":{"id":3,"nome":"Paulo"}}

Como passamos o elemento da raiz (root) do JSON, o VRaptor populará o parâmetro com o mesmo nome que esse elemento – neste caso cliente.

Você também pode consumir o JSON sem o root:

{"id":3,"nome":"Paulo"}

O VRaptor vai cuidar de converter estes dados automaticamente para o seu objeto Cliente, mas neste caso, como não passamos o root, é necessário que o Cliente seja o primeiro parâmetro do método.

Em outras palavras, a deserialização com o root popula o parâmetro com o nome do root, enquanto a deserialização sem root popula o primeiro parâmetro do método.

Se o JSON contém alguma propriedade com o mesmo nome de algum dos parâmetros do método, você precisa adicionar a classe WithoutRoot à propriedade options da anotação @Consumes para forçar a deserialização sem o elemento raiz.

@Consumes(value="application/json", options=WithoutRoot.class)
@Post
public void adicionar(Cliente cliente) {
    clienteDAO.salvar(cliente);
}

Da mesma forma, se você quer forçar a deserialização com o elemento raiz, você precisa adicionar a classe WithRoot, usando a anotação @Consumes da seguinte forma:

@Consumes(value="application/json", options=WithRoot.class)

Há ainda como definir mais de um content-type aceito pelo seu método, como por exemplo:

@Consumes({"application/json", "application/xml"})
@Post
public void adicionar(Cliente cliente) {
    clienteDAO.salvar(cliente);
}

Nota: O VRaptor vai retornar um 415 (Unsupported Media Type) se o media type enviado não for suportado.

Tratando exceções

O VRaptor possui um Exception Handler, que captura as exceções não tratadas em sua aplicação.

No exemplo abaixo, se o método adicionar(Cliente) lançar uma ClienteJaExisteException ou qualquer exceção filha, o usuário será redirecionado para o método formulario().

@Get
public void formulario() { ... }

@Post
public void adicionar(Cliente cliente) {
    result.on(ClienteJaExisteException.class).forwardTo(this).formulario();
    
    clienteDAO.salvar(cliente);
}

Sempre que você utilizar o Result.on(...) dessa forma, o exception handler vai interceptar sua exceção e deixá-la acessível como um atributo da request, chamado exception. Isso significa que em seu formulario.jsp, por exemplo, você poderá fazer algo como:

<c:if test="${not empty exception}">
O seguinte erro ocorreu: ${exception.message}
</c:if>

Monitorando as exceções do seu projeto

Se quiser, você também pode usar o plugin vraptor-error-control para acompanhar as exceptions que acontecem em sua aplicação em tempo real. Ele te envia um e-mail com a mensagem de erro.