JAX-RS é a solução do JCP para o estilo de programação REST. A proposta final da especificação foi liberada para o público no inicio de Agosto de 2008,. A especificação define um conjunto de APIs Java para
auxiliar no desenvolvimento de web services baseados em REST.
O objetivo da API é fornecer um conjunto de anotações, classes e interfaces para expor uma classe POJO
como um web service RESTful, de modo que possamos fazer uma programação fácil e de alto nível.
Trabalhando com os recursos
Para uma classe ser determinada como um recurso, ela tem que ser uma classe POJO com pelo menos um método anotado com a anotação @Path.
A anotação @Path pode ser colocada na declaração de classe ou de um método e possui o elemento value
obrigatório. Por este elemento definimos o prefixo da URI que a classe ou o método irá atender. Na Listagem 01, a classe Repositorio é identificada pela URI relativa “/repositorio/{id}”, onde {id} é o valor do parâmetro id, fornecido junto a URI.
Mais adiante, demonstraremos como extrair valores como esse de uma URI utilizando anotações especificas.
@Path("/repositorio/{id}") public class Repositorio { ... }Listagem 01 - Mapeando uma URI para uma classe com @Path.
A especificação define que no ciclo de vida de um recurso, por padrão, sempre que for feito uma requisição a um recurso, será criada uma nova instância de uma classe REST. Primeiro o construtor é chamado pelo contêiner, por conta disto, o construtor da classe deve ser público. Após este primeiro passo, o contêiner efetua as injeções de dependência nos devidos métodos e o método designado para aquele recurso é invocado. E finalmente após a chamada ao método o objeto fica disponível para o garbage collector.
Geralmente a anotação @Path é incluída na declaração de um método quando queremos atribuir um caminho mais específico para um recurso, de forma a especializar nosso método, como na Listagem 02. Note no exemplo como a URI é mapeada com a classe e o método.
@Path("/vendas/") public class Repositorio { @GET @Produces("application/xml") @Path("/pedidos/{numPedido}/") public PedidoAsXML getPedido(@PathParam("numPedido") Integer id){ // retorna Pedido em formato XML. } }Listagem 02 - Mapeando uma URI para uma classe com @Path.
Acessando os Recursos
Para acesso aos recursos, a especificação JAX-RS define um conjunto de anotações correspondentes aos métodos HTTP, como @GET, @POST, @PUT, @DELETE, @HEAD.. Elas devem ser atribuídas a métodos públicos. O método anotado com @GET, por exemplo, irá processar requisições HTTP GET na URI atribuída. O comportamento do recurso é determinado pelo método HTTP ao qual o recurso está respondendo.
É bom entender o papel e o uso de cada um destes métodos HTTP no momento de projetar nossos serviços.
Além dos métodos definidos pelo JAX-RS, podemos criar uma anotação customizada como @MKCOL, com uso da anotação HttpMethod, conforme ilustra a Listagem 03.
Com esta anotação podemos criar métodos customizados e extender a gama dos métodos pré-existentes e utilizar os métodos definidos pelo WebDAV, como fazemos na Listagem 3, ou podemos alternar um método padrão para a anotação customizada, onde para isso poderíamos simplesmente informar como valor para anotação HttpMethod o valor do método que queremos sobrepor, por exemplo, para sobrepor o método PUT a declaração da anotação ficaria
@HttpMethod("PUT"). @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @HttpMethod("MKCOL") public @interface MKCOL { }Listagem 03: Utilizando a anotação HttpMethod para criar uma anotação customizada.
Representações: Quais são os sabores?
As classes de uma aplicação RESTful podem ser declaradas para suportar diversos tipos de formatos durante um ciclo requisição-resposta. Para isso a especificação define as anotações @Consumes e @Produces para o tratamento da request e response respectivamente.
Com estas anotações o servidor declara o tipo de conteúdo que será trafegado, através do cabeçalho (header) do protocolo HTTP, estas anotações podem ser aplicadas na declaração de uma classe, ou podemos utilizar estas anotações na declaração do método para sobrescrever a declaração da classe, na ausência destas anotações, será assumido como default qualquer tipo de retorno ("*/*").
No caso da anotação @Produces, ela é utilizada para mapear uma requisição de um cliente com o cabeçalho do cliente (parâmetro Accept). Desta maneira, podemos definir mais de um tipo de retorno da URI solicitada, como JSON e XML, como no exemplo da Listagem 04:
@GET @Produces({"application/xml", "application/json"}) public PessoaConverter getPessoa(@QueryParam("CPF") String numCPF) { // Retorna representação em XML ou JSON }Listagem 04 - Declarando o tipo de retorno com a anotação @Produces.
Pegando o exemplo da Listagem 04, podemos testar o retorno da chamada utilizando uma das ferramentas
citadas no tópico "Como consumir serviços REST", como a ferramenta RESTClient, por exemplo, e incluir na aba "Headers" o parâmetro "Accept" e no campo Value especificar o tipo de retorno primeiro com XML e depois com JSON, conforme Figura 01:
Com a anotação @Consumes por outro lado, podemos definir os tipos de mídia que um recurso em particular consome. Como o exemplo da Listagem 05, onde estamos declarando que o método consome apenas formatos do tipo XML e JSON:
@PUT @Consumes("application/xml","application/json") @Path("/autores/") public Response putPessoa(PessoaBinding pessoa) {}Listagem 05: Uso da anotação @Consumes.
Extraindo parâmetros e valores da URI na requisição.
JAX-RS fornece algumas anotações para extrair informações de uma requisição. Existem seis tipos de parâmetros (ver Tabela 01) que podemos extrair para utilizar em nossa classe recurso. Para os parâmetros de query utilizamos a anotação @QueryParam. Para os parâmetros de URI(path) existe a anotação @PathParam.
Para os parâmetros de formulário existe @FormParam. Já para parâmetros de cookie existe @CookieParam.
Para os parâmetros de header existe @HeaderParam e, finalmente, para os parâmetros de matriz existe a anotação @MatrixParam.
Estas anotações podem ser aplicadas diretamente a parâmetros de um método. Desta forma vinculamos o valor do parâmetro de uma URI a algum parâmetro de entrada de um método. Para contextualizar, veja um exemplo na Listagem 06:
@Path("/editora/") public class EditoraResource { @GET @Produces("application/xml") @Path("/autores/{nomeAutor}/") public PessoaBinding getPessoa(@PathParam("nomeAutor") String name, @QueryParam("idade") int idade, @HeaderParam("CPF") String numCPF, @MatrixParam("statusCivil") String statusCivil) { return new PessoaBinding(name, idade, numCPF, statusCivil); } }Listagem 06 – Mapeando os parâmetros de uma URI para os parâmetros de um método.
Na Listagem 06, vemos a aplicação de várias anotações de parâmetro de URI em um único método. Para entender melhor como será feito o DE-PARA da URI para os parâmetros de entrada, vamos fazer uma chamada a este recurso com a biblioteca curl, apresentado na Figura 02.
Figura 02 - Fragmentando a URI para demonstrar anotações de mapeamento.
Tabela 01 - Anotações JAX-RS para extração de informações da URI.
No exemplo da Listagem 06 não demonstramos o uso das anotações CookieParam e FormParam, mas o seu uso é bem similar. No caso de FormParam, esta anotação pode capturar todos os valores submetidos de um formulário HTML e injetá-los nos parâmetros desejados. Veja um exemplo simples na listagem 07. Trata-se de um formulário HTML e de um método que irá receber estas informações.
@Path("/editora/") public class EditoraResource { @GET @Path("/autores/") public PessoaBinding getPessoa(@FormParam("nomeAutor") String name, @FormParam("idade") int idade) { return new PessoaBinding(name, idade); } }Listagem 07 - Uso da anotação FormParam.
No caso da anotação CookieParam, conseguimos injetar o valor de cookie ou a classe Cookie do javax.ws.rs.core, que representa o valor de um cookie HTTP na invocação de um método.
Dados de Contexto
A especificação JAX-RS dispõe de um recurso para a obtenção de informações do contexto da aplicação e de requisições individuais. Estas informações são disponíveis tanto para as classes recursos quanto para os providers. Para a recuperação destas informações existe a anotação @Context, que ao ser aplicada sobre um campo, método ou parâmetro, identifica um alvo a ser injetado pelo contêiner.
O contêiner fornece instâncias dos recursos listados na tabela 02, mediante a aplicação da anotação @Context.
Tabela 02: Lista de recursos injetados pelo contêiner pela anotação @Context.
Tratando o retorno dos métodos ao Cliente.
Os tipos de retorno de uma chamada a um método recurso podem ser do tipo void, Resource, GenericType ou outro tipo Java. Esses tipos de retorno são mapeados ao entity body da Response cada um de uma maneira, de acordo o provider padrão, conforme veremos a seguir.
Para os retornos do tipo void, o retorno será um corpo vazio com status 204 do HTTP. Para tratar o retorno ao cliente foi disponibilizada a classe abstrata Response. Com essa classe definimos um contrato entre a instância de retorno e o ambiente de execução, quando uma classe precisa fornecer metadados para ambiente de execução.
Podemos estender esta classe diretamente ou, ainda melhor, podemos criar uma instância utilizando sua classe interna ResponserBuilder. Por essa classe podemos construir objetos do tipo Response, adicionar metadados, adicionar cookies, adicionar dados no Header, informar a linguagem, entre outras informações.
Na Listagem 08, no método putPessoa, fazemos uso do método Response. Note que não a instanciamos diretamente, pois ela é uma classe abstrata que implementa o padrão de projeto Builder.
Dentro do método, primeiramente efetuamos uma chamada ao método estático created, passando como parâmetro a URI que obtemos através da classe injetada UriInfo. Esta classe retorna o objeto ResponseBuilder, que é uma subclasse de Response, esta subclasse é utilizada exclusivamente para criar instâncias de Response.
Por ResponseBuilder ser uma classe de construção (Builder), podemos efetuar chamadas recursivas aos métodos de parametrização. Após a chamada ao método created, chamamos o método status, no qual atribuímos o código de status HTTP 202 (Accepted) e logo após atribuímos uma entidade à requisição, no nosso exemplo um código HTML simples, pelo método entity. Na chamada ao método type seguinte, especificamos o tipo de mídia trafegado, neste caso TEXT_HTML. No final fazemos uma chamada ao método build, que constrói o objeto Response.
E por fim o objeto GenericEntity representa uma entidade de um tipo genérico, muito útil quando precisamos retornar uma Response personalizada e reter informações genéricas. Pois informações de tipos genéricos são apagadas ao utilizar uma instância.
@Context protected UriInfo uriInfo; @PUT @Consumes("application/xml") @Path("/MundoJava/update/") public Response putPessoa(PessoaBinding pessoa) { String retorno = "Bem vindo "+pessoa.getNome(); Response response = Response.created(uriInfo.getAbsolutePath()). status(Response.Status.ACCEPTED). entity(retorno). type(MediaType.TEXT_HTML). build(); return response; }
@Produces("text/plain") @Provider public class PropertiesProvider implements MessageBodyWriter{ public void writeTo(Properties p, Class type, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap headers, OutputStream out) throws IOException { p.store(out, null); } public boolean isWriteable(Class type, Type genericType, Annotation annotations[], MediaType mediaType) { return Properties.class.isAssignableFrom(type); } public long getSize(Properties p, Class type, Type genericType, Annotation annotations[], MediaType mediaType) { return -1; } }
@GET @Produces("application/xml") @Path("/autores/{personName}/{idade: [0-9]+}/") public PessoaBinding getPessoa(@PathParam("personName") String name, @PathParam("idade") int idade, @HeaderParam("CPF") String numCPF) { if (idade <= 0 || idade > 120){ throw new WebApplicationException(Response. status(412). entity("Idade inválida!"). build()); } return new PessoaBinding(name, idade, numCPF); }
@Provider public class IdadeInvalidaExceptionMapper implements ExceptionMapper{ public Response toResponse(IdadeInvalidaException ex) { return Response.status(412).entity(ex.getMessage()).build(); } }
@GET @Produces("application/xml") @Path("/autores/{personName}/{idade: [0-9]+}/") public PessoaBinding getPessoa(@PathParam("personName") String name, @PathParam("idade") int idade, @HeaderParam("CPF") String numCPF) throws IdadeInvalidaException { if (idade <= 0 || idade > 120) throw new IdadeInvalidaException("Idade Inválida!"); return new PessoaBinding(name, idade, numCPF); }