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.
Criando links na view com o linkTo
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}