Primeiros passos com os serviços REST: Este artigo tem como objetivo servir de ponto de partida para o desenvolvedor que deseja se aventurar no universo REST. Aqui temos uma introdução aos conceitos básicos dessa abordagem para criação de web services que vem ganhando cada vez mais espaço no mercado. Assim, aprendemos como implementar um serviço REST em Java e como consumir esse serviço.

Em que situação esse artigo é útil: A utilização de serviços REST tem crescido amplamente e gigantes como Google, Facebook, Yahoo!, Amazon, eBay e Microsoft vêm aderindo e suportando essa tecnologia. Portanto, nesse cenário em que nos encontramos, onde o conceito de “serviço” ganha força a cada dia, REST é atualmente uma opção importantíssima a se considerar na hora de definir como será disponibilizado um serviço. O desenvolvedor que conhece tecnologias REST com certeza estará um passo à frente dos demais.

A interoperabilidade entre sistemas tem sido uma tendência bem evidente do mercado de TI de modo que praticamente qualquer aplicação corporativa desenvolvida nos dias de hoje possui integrações com outros sistemas, seja para backup, e-mail, storage, conversão de arquivos, busca de informações, processamento de imagens ou qualquer outra dentre um incontável número de possibilidades. Também é notório o destaque que conceitos como web service, SOA, SaaS, PaaS, entre outros, vem ganhando no cenário atual de desenvolvimento de software.

Hoje em dia é muito comum ouvir alguém falar que vai fornecer ou utilizar um serviço, mas o que, de fato, é um “serviço”? Será que todos nós estamos falando a mesma língua?

Quando você ouve alguém falar em implementar ou utilizar um serviço, em 99% dos casos essa pessoa está se referindo a web services, e quando alguém fala em web services, temos a probabilidade de 99% de essa pessoa estar se referindo a serviços SOAP (Simple Object Access Protocol). Isto se deve ao fato de que o protocolo SOAP foi, e de certa forma ainda continua sendo, o mais utilizado para a criação de web services, de um modo tal que para muitos o termo web service está necessariamente atrelado ao protocolo SOAP. Contudo, veremos neste artigo que o conceito de web service é bem mais abstrato e que podemos criar/utilizar esses “serviços da web” usando outros protocolos e tecnologias, inclusive próprios.

No decorrer do artigo, veremos uma breve introdução do modelo REST para criação de web services, bem como os recursos oferecidos pelo Java para esse propósito. Como prova de conceito, desenvolveremos uma agenda de contatos, utilizando o framework Jersey.

O que é REST?

Conceitualmente falando, o modelo REST (REpresentational State Transfer) representa nada mais que uma “nova” possibilidade para a criação de web services, cujas principais diferenças em relação ao modelo tradicional (SOAP) estão na utilização semântica dos métodos HTTP (GET, POST, PUT e DELETE), na leveza dos pacotes de dados transmitidos na rede e na simplicidade, fazendo desnecessária a criação de camadas intermediárias (Ex.: Envelope SOAP) para encapsular os dados.

Devido, irônica e principalmente, à sua simplicidade em relação ao SOAP (que leva Simple no nome), os serviços REST vêm ganhando espaço no mercado e têm sido utilizados por gigantes como Google, Facebook, Yahoo!, Amazon, eBay, Microsoft, dentre outros. Portanto, saber REST nos dias atuais é de fundamental importância para qualquer desenvolvedor ou empresa. Vejamos então como essa coisa toda funciona.

Nota: O criador do modelo REST é nada menos que Roy Fielding, um dos principais autores da especificação HTTP e cofundador do projeto Apache HTTP Server. A criação do termo REST, apresentado pela primeira vez em 2000 na sua tese de doutorado, foi uma forma de dizer: “Vocês estão subestimando o HTTP. Aprendam como utilizá-lo da forma correta!”.

No mundo REST, uma requisição HTTP é equivalente a uma chamada de um método (operação) em um objeto (recurso) residente no servidor.

Como principais características de uma requisição REST, podemos destacar:

  • O método HTTP é utilizado para determinar a operação a ser realizada em um determinado recurso. Em geral, utiliza-se o GET para recuperar, POST para criar, PUT para alterar e DELETE para apagar;
  • O recurso, por sua vez, é indicado na URL da requisição;
  • Parâmetros podem ser passados na própria URL e/ou no corpo na requisição;
  • Os tipos de dados utilizados na requisição e na resposta devem ser acordados entre o servidor e o(s) cliente(s). JSON e XML estão entre os tipos mais utilizados.

Nota: Além dos famosos GET e POST, o protocolo HTTP define os métodos: CONNECT, HEAD, PUT, DELETE, TRACE e OPTIONS. Ainda é possível criar extensões e consequentemente a adição de novos métodos. A especificação RFC 5789 define o método PATCH, por exemplo.

Nota: Existe certa divergência em relação à função dos métodos HTTP POST e PUT no contexto de serviços REST. Alguns autores defendem o uso do POST para criação e PUT para atualização, já outros pensam o inverso. Temos também o PATCH, que tem o propósito de servir para atualizações parciais de um objeto. Em todo o caso, na prática, o provedor do serviço é quem irá decidir qual abordagem utilizar.

Para entendermos melhor, vamos imaginar um exemplo de um serviço de bookmark, onde o usuário pode guardar seus links favoritos, e suponhamos que este esteja localizado na URL http://mybookmarks.com. Para recuperarmos a lista de todos os bookmarks, pensando segundo a filosofia REST, teríamos que utilizar o método HTTP GET na URL que representa a lista. Por exemplo:

GET http://mybookmarks.com/bookmarks

A resposta dessa requisição poderia ser algo do tipo:

  • <bookmarks>
  • <bookmark>
  • <id>1</id>
  • <title>Google</title>
  • <url>http://google.com</url>
  • </bookmark>
  • <bookmark>…</bookmark>
  • </bookmarks>

Lembrando que o formato, XML no exemplo, é livre e definido pelo provedor do serviço. Caso esse mesmo serviço fornecesse a saída em JSON, o resultado da requisição que fizemos seria algo como:

  • [
  • {“id” : 1, “title” : “Google”, “url” : “http://google.com” },
  • {“id” : 1, “title” : “Yahoo!”, “url” : “http://yahoo.com” }
  • ]

Se ao invés da lista, quiséssemos recuperar apenas um bookmark específico, poderíamos utilizar o HTTP GET para uma URL que representasse esse único registro, por exemplo:

GET http://mybookmarks.com/bookmarks/13

ou

GET http://mybookmarks.com/bookmarks?id=13

Vale ressaltar que o modelo REST não impõe o formato da URL, deixando isso a cargo de quem provê o serviço. A única recomendação é que se use uma URL que identifique, de forma clara e única, o recurso que está sendo acessado.

Por sua vez, se quiséssemos incluir um novo bookmark, provavelmente iríamos utilizar a mesma URL da listagem, porém com o método HTTP POST e informaríamos os dados do objeto novo no corpo da requisição. Vejamos um exemplo:

POST http://mybookmarks.com/bookmarks

{“title” : “Google”, “url” : “http://google.com”}

Seguindo a mesma linha de raciocínio, se quiséssemos alterar um registro existente, poderíamos utilizar o método HTTP PUT juntamente com a URL do item específico que desejamos alterar. Por exemplo:

PUT http://mybookmarks.com/bookmarks/1

{“title” : “Google Brasil”, “url” : “http://www.google.com.br”}

E da mesma forma, se quiséssemos remover um registro, bastaria enviar uma requisição do tipo HTTP DELETE para a URL correspondente ao item a ser removido. A requisição ficaria parecida com a seguinte:

DELETE http://mybookmarks.com/bookmarks/1

Em todos os casos citados, devemos utilizar o código de status HTTP para saber se nossa solicitação foi efetuada com sucesso. No caso de ocorrer algum problema, o servidor deverá retornar um código de erro adequado, opcionalmente acompanhado de uma mensagem. Mais uma vez, fica a cargo do servidor definir seu funcionamento particular; nesse caso, quais códigos de erro retornar em cada situação.

Como podemos perceber, o modelo é simples e intuitivo, o que nos leva a perceber o quanto costumávamos subestimar o poder do protocolo HTTP. Felizmente, o REST veio para nos mostrar como é possível realizar praticamente qualquer operação utilizando puramente os recursos desse protocolo.

Java e REST

Até pouco tempo atrás não existia uma maneira “oficial” de criar serviços REST em Java, devido tanto ao fato de ser uma abordagem relativamente nova, quanto ao fato dela usar basicamente o HTTP, não sendo necessárias bibliotecas adicionais. Entretanto, com o intuito de padronizar e simplificar a criação desse tipo de serviço em Java, foi criada a especificação JAX-RS (Java API for RESTful Web Services). Parte integrante da plataforma Java EE 6, essa especificação define um conjunto de anotações, classes e interfaces para a criação de serviços REST. Como implementação de referência da JAX-RS, temos o projeto Jersey (ver seção Links), o qual utilizaremos em nossos exemplos. Apesar disso, existem diversas outras implementações no mercado, tais como: Apache CFX, RESTEasy, Restlet, dentre outros; o que demonstra mais uma vez que REST vem ganhando espaço no mercado.

Na prática, a facilidade que a JAX-RS nos oferece é a forma de expor um serviço. Seguindo a tendência do Java EE 6, conseguimos fazer praticamente tudo com o uso de anotações. Na Tabela 1, listamos as principais anotações e seus propósitos.

Principais anotações definidas pela JAX-RS
Tabela 1. Principais anotações definidas pela JAX-RS.

Dessa forma, é possível notar como fica fácil disponibilizar um serviço REST, como veremos no nosso exemplo prático.

Exemplo prático: Agenda de contatos

Agora que já entendemos os conceitos básicos sobre REST e descobrimos que existe uma especificação que define como implementar esse tipo de serviço em Java, vamos criar um pequeno exemplo prático. Nosso projeto consistirá numa agenda de contatos, onde o usuário poderá cadastrar seus contatos no servidor a partir de uma interface gráfica extremamente simples, construída utilizando apenas HTML e JavaScript.

Antes de tudo, no entanto, precisamos definir como será a interface do nosso serviço, no que diz respeito ao formato das URLs e métodos HTTP. A Tabela 2 mostra o funcionamento esperado para o serviço através de exemplos.

Descrição do formato das requisições a serem utilizadas pelo serviço
Tabela 2. Descrição do formato das requisições a serem utilizadas pelo serviço.

Configurando o ambiente de desenvolvimento

Para construir e testar nosso exemplo, utilizaremos a IDE Eclipse (versão Juno ou mais recente) com o plugin m2e (plugin do Apache Maven) e o servidor de aplicações Apache Tomcat 7. Na seção Links você encontra os endereços para download dessas ferramentas.

Uma vez que estamos falando de serviço, precisaremos criar além do serviço, propriamente dito, um ou mais clientes para testar esses serviços. Sendo assim, para construir e testar o nosso exemplo, criaremos os seguintes projetos:

  • contacts-api: Projeto Java simples que irá conter os tipos básicos (beans) utilizados pelo nosso serviço.;
  • contacts: Projeto web que fará uso das bibliotecas do Jersey para criar o serviço REST. Esse é o nosso principal projeto, pois tratará do serviço em si. Nele definiremos os métodos que o serviço irá oferecer, os tipos de dados trafegados nas requisições, bem como o formato de URL que o serviço utilizará;
  • contacts-html-client: Projeto web que conterá a interface gráfica HTML + JavaScript. Com esse projeto teremos um cliente visual, não Java, para o nosso serviço;
  • contacts-client: Projeto Java simples que servirá para testar a API cliente da JAX-RS por meio do console. Nesse projeto veremos como chamar nosso serviço a partir de uma aplicação Java;

O código completo dos projetos está disponível para download no site da Java Magazine.

O Projeto contact-api

Para criar esse projeto, utilize a opção File > New > Other... do Eclipse e selecione Maven > Maven Project. Em seguida, escolha o arquétipo maven-archetype-quickstart. Digite “br.com.javamagazine” no campo Group ID, “contacts-api” em Artifact Id e clique em Finish para criar o projeto. Feito isso, não será necessária nenhuma alteração no arquivo pom.xml do mesmo, uma vez que este não terá nenhuma dependência de biblioteca adicional ou framework.

Nota: Utilizaremos o Apache Maven para gerenciar as dependências, eliminando a necessidade de baixar manualmente as bibliotecas que serão utilizadas nos projetos. Na edição 62 da Java Magazine você encontra o artigo “Gerenciando projetos com Maven” e, nas edições 20 e 21 da Easy Java Magazine, a série “Introdução ao Maven”.

Como dissemos anteriormente, neste projeto criaremos apenas os beans da nossa aplicação. Nele, teremos uma classe Contact, que representa um contato, com ID, nome, uma lista de telefones e uma lista de e-mails. Também criaremos as classes Phone e Email, as quais possuirão o atributo type, indicando o tipo de telefone ou e-mail em questão (Ex.: “celular”, “trabalho”, “operadora xyz”).

A Figura 1 mostra o diagrama com nossas classes de domínio e as Listagens 1, 2 e 3 os códigos das classes Contact, Phone e Email, respectivamente. Com isso, finalizamos o primeiro dos nossos projetos.

Diagrama das classes de domínio da aplicação exemplo
Figura 1. Diagrama das classes de domínio da aplicação exemplo.

Listagem 1. Classe de domínio Contact. Representa um contato na agenda.


    package br.com.javamagazine.contacts.bean;
     
    import java.util.ArrayList;
    import java.util.List;
     
    public class Contact {
     
     private int id;
     private String name;
     private List<Phone> phones = new ArrayList<Phone>();
     private List<Email> emails = new ArrayList<Email>();
     
     // gets e sets omitidos...
     
     @Override
     public boolean equals(Object obj) {
      boolean result = false;
     
      if (obj instanceof Contact) {
       Contact c = (Contact) obj;
       result = c.getId() == this.getId();
      }
     
      return result;
     }
     
     @Override
     public int hashCode() {
      return getId() ^ 7;
     }
    }

Listagem 2. Classe de domínio Phone. Representa um telefone.


    package br.com.javamagazine.contacts.bean;
     
    public class Phone {
     
     private String type;
     private String number;
     
     public Phone() {}
     
     public Phone(String type, String number) {
      this.type = type;
      this.number = number;
     }
     
     // gets e sets omitidos...
    }

Listagem 3. Classe de domínio Email. Representa um e-mail.


    package br.com.javamagazine.contacts.bean;
     
    public class Email {
     
     private String type;
     private String address;
     
     public Email() {}
     
     public Email(String type, String address) {
      this.type = type;
      this.address = address;
     }
     
     // gets e sets omitidos...
    }

O projeto contacts

Agora sim vamos criar nosso serviço REST. Para isso, será preciso criar um projeto web utilizando o plugin do Maven. Sendo assim, escolha a opção File > New > Other... e selecione Maven > Maven Project. Desta vez escolha o arquétipo maven-arquetype-webapp. Digite br.com.javamagazine no campo Group ID, contacts em Artifact Id e clique em Finish para criar o projeto.

Uma vez com o projeto criado, é preciso indicar ao Maven as dependências que utilizaremos. Nesse caso, iremos adicionar uma dependência para o nosso projeto contacts-api, para enxergarmos os beans que criamos, assim como para as classes do Jersey. Portanto, iremos alterar a seção <dependencies> do arquivo pom.xml de acordo com o conteúdo mostrado na Listagem 4. Como podemos observar, a primeira dependência é da versão 0.0.1-SNAPSHOT do projeto contacts-api, ou seja, do projeto que criamos com os beans da aplicação. Em seguida temos duas dependências do Jersey. Uma é do servlet responsável por tratar as requisições e mapeá-las nos métodos da nossa classe de serviço, que criaremos em breve, de acordo com a abordagem REST, e a outra se trata do framework MOXy, que será utilizado para serialização dos objetos Java no formato JSON.

Listagem 4. Seção <dependencies> do arquivo pom.xml do projeto contacts.


      ...
    <dependencies>
     <dependency>
      <groupId>br.com.javamagazine</groupId>
      <artifactId>contacts-api</artifactId>
      <version>0.0.1-SNAPSHOT</version>
     </dependency>
     
     <dependency>
      <groupId>org.glassfish.jersey.containers</groupId>
      <artifactId>jersey-container-servlet</artifactId>
      <version>2.0</version>
     </dependency>
     
     <dependency>
      <groupId>org.glassfish.jersey.media</groupId>
      <artifactId>jersey-media-moxy</artifactId>
      <version>2.0</version>
     </dependency>
     
    </dependencies>
    ...

Nota: Observe que não é preciso fazer nenhuma configuração no Jersey para indicar que o MOXy será utilizado para fazer a serialização dos objetos. Habilitado por padrão, o Jersey possui um recurso chamado “Auto-Discoverable Features”, que consegue identificar algumas de suas extensões presentes no classpath e se configurar automaticamente.

Feito isso, vamos criar a classe ContactsService (apresentada na Listagem 5), que representará a interface do nosso serviço, além de conter a lógica de negócio, por questões de simplificação.

O primeiro ponto ao qual devemos atentar é a anotação @Path na declaração da classe. Nela indicaremos a qual caminho HTTP essa classe deve responder. No nosso caso, como não existe nenhuma outra classe de serviço, mapeamos o “/” indicando que essa classe tratará qualquer requisição direcionada ao servlet que representa o ponto de entrada dos serviços REST dessa aplicação. Veremos como configurar esse servlet mais adiante.

Para não fugirmos do foco do artigo, não utilizaremos uma base de dados, como seria numa aplicação do mundo real, para persistir os contatos. Ao invés disso, manteremos uma lista de contatos na memória do servidor. Na nossa classe, a variável contacts terá esse papel.

Temos então o método getContact(), no qual encontramos três anotações. A @GET é uma anotação da JAX-RS e indica que esse método deve ser invocado para requisições HTTP GET. A @Produces indica o formato de mídia (o content-type do HTTP) que esse método irá produzir. No nosso caso, a constante MediaType.APPLICATION_JSON indica que o formato JSON será utilizado. Então temos uma anotação @Path cujo valor apresenta uma sintaxe que ainda não discutimos. Nesse caso estamos criando um path dinâmico, onde {contactId} é uma variável de path que terá seu valor definido pelo cliente no momento em que o serviço é invocado. Note que fazemos uma referência a essa variável na anotação @PathParam que antecede o parâmetro id do método getContact(). Nesse caso estamos dizendo que para invocar esse método, o cliente deve acessar uma URL que contenha o caminho do serviço, acrescido da barra (“/”) e o ID do contato desejado. O valor desse ID será atribuído de forma transparente ao parâmetro com a anotação. No corpo do método, inicialmente fazemos uma busca pelo contato com o ID recebido e, caso encontrado, o atribuímos à variável result, a qual é retornada no final do método. Caso nenhum contato seja encontrado, lançamos uma exceção do tipo WebApplicationException. Essa é uma exceção especial, definida pela JAX-RS, que tem como propósito mapear o erro ocorrido num código de status HTTP. Ela possui diversos construtores, dentre eles um que recebe o código de status HTTP. No nosso exemplo, usamos esse construtor e passamos o código 404, utilizado quando um recurso não é encontrado.

O método list() é o mais simples de todos. Nele simplesmente retornamos o conteúdo do atributo contacts. Associamos esse método ao HTTP GET através da anotação @GET. Mais uma vez, utilizamos a anotação @Produces para indicar que o content-type da resposta será no formato JSON.

Na sequência, encontramos o método findByName(). Nele também temos a anotação @GET, para indicar que ele irá responder a requisições HTTP GET. Com a anotação @Path, definimos um caminho específico para esse método. Note que estamos utilizando @GET em três métodos diferentes da classe ContactsService, entretanto cada um deles atende a um path único. Se tentarmos mapear um único conjunto (método HTTP + path) para mais de um método Java, teremos um erro em tempo de execução quando o serviço for levantado, informando a dubiedade. No caso do findByName(), utilizamos o (sub)path /find/{contactName}, juntamente com a anotação @PathParam que referencia a variável contactName, associando seu valor ao parâmetro name. No corpo do método, iteramos pelos contatos da lista contacts e adicionamos aqueles cujos nomes contêm a String pesquisada em uma lista temporária, representada pela variável result, que corresponde ao retorno do método.

Em seguida, codificamos o método add(). Associamos esse método às requisições HTTP POST através da anotação @POST, presente em sua declaração. Temos também as anotações @Consumes e @Produces que, como os nomes sugerem, sevem para indicar o tipo de dado (content-type) que a requisição irá consumir (receber do cliente) e produzir (devolver ao cliente). No nosso exemplo, utilizaremos o formato JSON nas duas situações. No corpo do método add(), iniciamos fazendo uma validação que verifica se o atributo name do contato a ser inserido não está vazio. Se estiver, lançamos uma exceção do tipo WebApplicationException, explicada anteriormente. Desta vez, ao invés do código de erro HTTP, estamos passando um objeto Response. Dessa forma temos um controle maior da resposta HTTP que será devolvida ao cliente. Em nosso exemplo estamos configurando o código de status com Response.Status.BAD_REQUEST (código 400) e uma mensagem de erro. No mais, adicionamos o contato à lista de contatos, caso este passe pela validação, e setamos seu ID tomando por base a sua posição na lista acrescido de 1, para evitar termos ID com valor 0. Por fim, retornamos o contato adicionado com o ID preenchido.

No método update() usamos a anotação @PUT para informar o método HTTP a ser tratado. Também definimos um @Path dinâmico específico, com a variável {contactId}. Indicamos que o conteúdo recebido pela requisição deve estar no formato JSON através da anotação @Consumes. Ainda na declaração do método, temos os parâmetros id e contact. O primeiro será associado à variável {contactId} do path e o segundo ao corpo da requisição. Neste método simplesmente substituímos o contato da lista pelo recebido. Para tal, utilizamos o id recebido, que corresponde à posição do contato na lista + 1.

Por fim, temos o método delete(), que é associado a requisições HTTP DELETE através da anotação @DELETE. Dessa vez, para demonstrar outras possibilidades, adotamos uma estratégia diferente para recuperar o parâmetro da requisição. Note que utilizamos a anotação @QueryParam, que assim como a @PathParam, associa um parâmetro da requisição ao parâmetro anotado (id, no exemplo). Entretanto, utilizando @QueryParam, mapeamos um parâmetro da query string, e não do path. Uma vez com o ID do contato a ser excluído, removemos o objeto correspondente da lista, lembrando-se de subtrair 1 do ID para obter o índice correto na lista.

Dessa forma, finalizamos a nossa classe de serviço, porém ainda temos uma configuração a fazer, que é mapear o servlet do Jersey na nossa aplicação web. Para isso, edite o arquivo web.xml, localizado na pasta src/main/webapp/WEB-INF do projeto, para que este fique conforme a Listagem 6. Nele, primeiramente declaramos o servlet do Jersey, que será responsável por receber as requisições para o nosso serviço. Devemos atentar ao parâmetro de inicialização jersey.config.server.provider.packages, que diz ao servlet do Jersey em quais pacotes ele deve procurar pelas classes anotadas com @Path, ou seja, classes que representam serviços REST. Como nossa aplicação web consiste unicamente do serviço REST, mapeamos todas as requisições (“/*”) para nosso servlet REST. Caso nossa aplicação tivesse outros módulos, seria necessário mapear um padrão diferente, como “/rest/*”, por exemplo.

Terminada a configuração, vamos agora testar nosso serviço. Como? Diretamente no browser! É possível testar os métodos GET simplesmente acessando a URL correspondente. Sendo assim, inicie o Tomcat, certificando-se de ter adicionado o projeto ao servidor, e acesse as seguintes URLs:

  • http://localhost:8080/contacts;
  • http://localhost:8080/contacts/1;
  • http://localhost:8080/contacts/find/ana.

Provavelmente aparecerá apenas um “[]” na primeira e terceira URLs e um erro “404 – Not Found” na segunda. Isso porque não existe nenhum contato cadastrado. O “[]” representa uma lista vazia no formato JSON, enquanto o erro 404 foi criado a partir a exceção que lançamos no método getContact() da classe ContactsService, quando nenhum contado correspondente ao ID recebido é encontrado.

Para visualizar uma resposta mais satisfatória, podemos criar alguns contatos de exemplo no construtor da classe ContactsService. A Figura 2 mostra um exemplo do serviço sendo acessado do browser.

Nota: A Query String corresponde àquela parte opcional do fim da URL que vem depois de um “?”. Por exemplo: na URL “http://abc.com/blog?tag=java&page=2”, temos o protocolo (“http”), o domínio (“abc.com”), o path (“/blog”) e a query string (“tag=java&page=2”).

Listagem 5. Classe ContactsService. Contém a lógica de negócio e atuará como interface do serviço REST.


    package br.com.javamagazine.contacts.rest.resource;
     
    import java.util.ArrayList;
    import java.util.List;
     
    import javax.ws.rs.Consumes;
    import javax.ws.rs.DELETE;
    import javax.ws.rs.GET;
    import javax.ws.rs.POST;
    import javax.ws.rs.PUT;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
     
    import br.com.javamagazine.contacts.bean.Contact;
    import br.com.javamagazine.contacts.bean.Email;
    import br.com.javamagazine.contacts.bean.Phone;
     
    @Path("/")
    public class ContactsService {
     
     private static List<Contact> contacts = new ArrayList<Contact>();
     
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{contactId}")
     public Contact getContact(@PathParam("contactId") int id) {
     
      Contact result = null;
     
      for (Contact contact : contacts) {
       if (contact.getId() == id) {
        result = contact;
        break;
       }
      }
     
      if (result == null) {
       throw new WebApplicationException(404);
      }
     
      return result;
     }
     
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     public List<Contact> list() {
      return contacts;
     }
     
     @GET
     @Path("/find/{name}")
     @Produces(MediaType.APPLICATION_JSON)
     public List<Contact> findByName(@PathParam("name") String name) {
     
      List<Contact> result = new ArrayList<Contact>();
     
      for (Contact contact : contacts) {
       if (contact.getName() != null
         && contact.getName().toLowerCase()
            .contains(name.toLowerCase())) {
        result.add(contact);
       }
      }
     
      return result;
     }
     
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     public Contact add(Contact contact) {
     
      if (contact.getName() == null || contact.getName().trim().equals("")) {
       throw new WebApplicationException(Response
         .status(Response.Status.BAD_REQUEST)
         .entity("O nome do contato é obrigatório").build());
      }
     
      contacts.add(contact);
      contact.setId(contacts.indexOf(contact) + 1);
      return contact;
     }
     
     @PUT
     @Path("/{id}")
     @Consumes(MediaType.APPLICATION_JSON)
     public void update(@PathParam("id") int id, Contact contact) {
      contacts.set(id - 1, contact);
      contact.setId(contacts.indexOf(contact) + 1);
     }
     
     @DELETE
     @Path("/{id}")
     public void delete(@PathParam("id") int id) {
      Contact contact = contacts.get(id - 1);
      contacts.remove(contact);
     }
     
    }

Listagem 6. Conteúdo do arquivo web.xml.


      <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
     
    <web-app>
        <display-name>Contact - REST Service</display-name>
     
        <servlet>
            <servlet-name>Jersey Web Application</servlet-name>
            <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
     
            <init-param>
                <param-name>jersey.config.server.provider.packages</param-name>
                <param-value>br.com.javamagazine.contacts.rest.resource</param-value>
            </init-param>
     
            <load-on-startup>1</load-on-startup>
        </servlet>
     
        <servlet-mapping>
            <servlet-name>Jersey Web Application</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
     
    </web-app>
Exemplo da utilização do serviço no browser
Figura 2. Exemplo da utilização do serviço no browser.

O projeto contacts-html-client

Com nosso serviço concluído, vamos agora criar uma interface gráfica para nossa aplicação. Esse projeto irá se tratar de uma aplicação web simples, puramente HTML e JavaScript. Portanto, crie um projeto web no Eclipse. Nesse caso o uso do Maven é opcional, pois não se trata de um projeto Java e não teremos nenhuma dependência. Criado o projeto, podemos codificar o arquivo contacts.js (exibido na Listagem 7), que conterá um conjunto de funções JavaScript para acessar o nosso serviço REST. Perceba que todas as funções são bem parecidas, pois todas tratam de uma requisição AJAX ao serviço. Como podemos notar, estamos utilizando o jQuery para fazer essas requisições AJAX através da função $.ajax(). Repare que setamos o parâmetro dataType como “json” e o type com o método HTTP que desejamos utilizar em cada requisição, de acordo com o esperado no serviço. Dessa forma, fica fácil perceber o quanto os serviços REST são simples de serem consumidos, pois não é necessária nenhuma biblioteca adicional, bastando ao cliente ser capaz de enviar solicitações HTTP. Fazendo o download do projeto no site da revista, você irá encontrar o arquivo index.html, que contém a interface gráfica da aplicação. Não explanaremos aqui o código desse arquivo para não desviarmos o foco do artigo.

Agora, para que possamos realizar alguns testes, adicione o projeto ao Tomcat e inicialize o servidor. Note que neste ponto teremos dois projetos rodando no Tomcat: contacts e contacts-html-client. O primeiro representa o serviço REST e o segundo, um exemplo de utilização desse serviço. Em projetos reais, é comum que serviço e cliente rodem em ambientes separados. No entanto, como nosso caso é apenas uma demonstração, podemos ter um único servidor para as duas aplicações, lembrando que não existe nenhum tipo de dependência entre esses projetos. A Figura 3 mostra o nosso cliente HTML/JavaScript em funcionamento.

Listagem 7. Conteúdo do arquivo contacts.js.


    var rootURL = "http://localhost:8080/contacts";
     
    function findAll(callback) {
     $.ajax({
      type : 'GET',
      url : rootURL,
      dataType : "json", 
      success : callback
     });
    }
     
    function findByName(name, callback) {
     
     if (name != null && name != "") {
      $.ajax({
       type : 'GET',
       url : rootURL + '/find/' + name,
       dataType : "json",
       success : callback
      });
     } else {
      findAll(callback);
     }
    }
     
    function findById(id, callback) {
     $.ajax({
      type : 'GET',
      url : rootURL + '/' + id,
      dataType : "json",
      success : callback
     });
    }
     
    function addContact(contact, callback) {
     $.ajax({
      type : 'POST',
      contentType : 'application/json',
      url : rootURL,
      dataType : "json",
      data : contact,
      success : callback,
      error : function(jqXHR, textStatus, errorThrown) {
       alert('Erro criando contato: ' + jqXHR.responseText);
      }
     });
    }
     
    function updateContact(id, contact, callback) {
     $.ajax({
      type : 'PUT',
      contentType : 'application/json',
      url : rootURL + '/' + id,
      data : contact,
      success : callback,
      error : function(jqXHR, textStatus, errorThrown) {
       alert('Erro atualizando contato: ' + textStatus);
      }
     });
    }
     
    function deleteContact(id, callback) {
     $.ajax({
      type : 'DELETE',
      url : rootURL + '?contactId=' + id,
      success : callback,
      error : function(jqXHR, textStatus, errorThrown) {
       alert('Erro excluindo contato: ' + textStatus);
      }
     });
    }
Screenshot da aplicação cliente HTML em funcionamento
Figura 3. Screenshot da aplicação cliente HTML em funcionamento.

O projeto contacts-client

Nesse último projeto, veremos como acessar o serviço através de uma aplicação Java. A exemplo do projeto contacts-api, criado anteriormente, defina um projeto Maven simples utilizando o arquétipo maven-archetype-quickstart.

Com o projeto criado, será necessário adicionar as dependências do projeto contacts-api, que contém os beans que serão utilizados, e do Jersey Client, juntamente com o MOXy. A Listagem 8 mostra como deve ficar o arquivo pom.xml desse projeto.

Listagem 8. Arquivo pom.xml do projeto contacts-client.


      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
     http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>br.com.javamagazine</groupId>
        <artifactId>contacts-client</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>contacts-client</name>
        <url>http://maven.apache.org</url>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        <dependencies>
            <dependency>
                <groupId>br.com.javamagazine</groupId>
                <artifactId>contacts-api</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jersey.core</groupId>
                <artifactId>jersey-client</artifactId>
                <version>2.0</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jersey.media</groupId>
                <artifactId>jersey-media-moxy</artifactId>
                <version>2.0</version>
            </dependency>
        </dependencies>
    </project>

Concluídas as configurações, crie a classe ContactsClient, cujo código encontra-se na Listagem 9. Esta classe representa um cliente Java para nosso serviço. Nela, inicialmente temos o atributo baseTarget, do tipo WebTarget, definido pela JAX-RS, que representa um destino, basicamente uma URL, no servidor que provê o serviço.

No construtor, criamos uma instância de um objeto Client, utilizando o método ClientBuilder.newClient(). Dessa forma a implementação concreta do cliente fica transparente ao desenvolvedor, o que possibilita, por exemplo, trocar o Jersey por outra implementação da JAX-RS sem precisar alterar o código. A partir do objeto client, criamos um WebTarget para a URL recebida como parâmetro e o atribuímos ao atributo baseTarget. Vamos agora dar uma olhada nos métodos da classe.

No método findAll(), que retorna uma lista de objetos Contact, iniciamos criando um Invocation.Builder a partir do baseTarget. Esse objeto é o responsável por fazer as requisições ao serviço. Em seguida invocamos o método get() do invocationBuilder, o que irá gerar uma requisição HTTP GET, para o destino apontado por baseTarget. Esse método tem como retorno um objeto Response, que contém os dados da resposta gerada pelo serviço. Na sequência verificamos o status da resposta e caso este seja diferente de OK (código 200), lançamos uma exceção. Por fim, invocamos o método response.readEntity() para ler a partir da resposta HTTP um objeto do tipo que desejamos; nesse caso uma lista de objetos Contact. Como o tipo de objeto que estamos tentando ler é um java.util.List, o qual faz uso do recurso de Generics do Java, temos que utilizar a classe adaptadora GenericType.

No método findByName(), criamos um novo objeto WebTarget, uma vez que a URL para buscas definidas no serviço é diferente da URL base. Note, porém, que estamos construindo esse novo target a partir do baseTarget existente, através do método path(), produzindo assim um caminho relativo. Na sequência, definimos um Invocation.Builder, utilizando o searchTarget recém-construído, e repetimos os mesmos passos vistos no método findAll().

Na sequência, temos o método findById(), que tem estrutura bastante semelhante aos dois anteriores. A diferença mais relevante aqui é que utilizamos outra versão do método response.readEntity(). Versão esta que recebe como argumento o tipo a ser retornado, útil quando não se trata de um tipo genérico, como era o caso da lista.

O método add(), responsável por cadastrar um novo contato no servidor, tem basicamente a mesma estrutura dos anteriores. Primeiramente definimos um Invocation.Builder utilizando um WebTarget e na sequência invocamos o método do invocationBuilder correspondente ao método HTTP esperado pelo serviço; HTTP POST para essa operação. Para isso, chamamos o método invocationBuilder.post() passando o objeto Contact, recebido como parâmetro. Contudo, note que é preciso serializar o objeto que estamos tentando submeter. Para esse fim, temos o método Entity.entity(), que recebe o objeto a ser serializado, bem como o formato a ser utilizado na serialização; JSON no nosso exemplo. O restante do método é igual aos demais.

Nos métodos update() e delete(), não encontraremos nenhuma novidade. A diferença é a utilização dos métodos put() e delete() do invocationBuilder, gerando, dessa forma, requisições HTTP dos tipos PUT e DELETE, respectivamente.

Feito isso, concluímos o nosso cliente Java, que pode ser testado facilmente com um método main(), e também nossa aplicação exemplo.

Listagem 9. Código da classe ContactsClient.


      package br.com.javamagazine.contactsclient;
    
    import java.util.List;
    import javax.ws.rs.client.Client;
    import javax.ws.rs.client.ClientBuilder;
    import javax.ws.rs.client.Entity;
    import javax.ws.rs.client.Invocation;
    import javax.ws.rs.client.WebTarget;
    import javax.ws.rs.core.GenericType;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import br.com.javamagazine.contacts.bean.Contact;
    
    public class ContactsClient {
    
     private WebTarget baseTarget;
    
     public ContactsClient(String serviceUrl) {
      Client client = ClientBuilder.newClient();
      baseTarget = client.target(serviceUrl);
     }
    
     public List<Contact> findAll() {
      Invocation.Builder invocationBuilder = baseTarget.request
      (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.get();
     
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro listando contatos");
      }
    
      return response.readEntity(new GenericType<List<Contact>>() {});
     }
    
     public List<Contact> findByName(String name) {
      WebTarget searchTarget = baseTarget.path("/find/" + name);
      Invocation.Builder invocationBuilder = searchTarget.request
       (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.get();
    
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro listando contatos");
      }
    
      return response.readEntity(new GenericType<List<Contact>>() {});
     }
    
     public Contact findById(int id) {
      WebTarget searchTarget = baseTarget.path("/" + id);
      Invocation.Builder invocationBuilder = searchTarget.request
      (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.get();
    
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro recuperando contato");
      }
     
      return response.readEntity(Contact.class);
     }
    
     public Contact add(Contact contact) {
      Invocation.Builder invocationBuilder = baseTarget.request
     (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.post(Entity.entity
      (contact,MediaType.APPLICATION_JSON));
     
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro criando contato");
      }
      return response.readEntity(Contact.class);
     }
    
     public void update(Contact contact) {
      WebTarget updateTarget = baseTarget.path("/" + contact.getId());
      Invocation.Builder invocationBuilder = updateTarget.request
     (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.put(Entity.entity
     (contact, MediaType.APPLICATION_JSON));
     
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro atualizando contato");
      }
     }
    
     public void delete(Contact contact) {
      WebTarget deleteTarget = baseTarget.queryParam
     ("contactId", contact.getId());
      Invocation.Builder invocationBuilder = deleteTarget.request
      (MediaType.APPLICATION_JSON);
      Response response = invocationBuilder.delete();
    
      if (response.getStatus() != Response.Status.OK.getStatusCode()) {
       throw new RuntimeException("Erro removendo contato");
      }
     }
    
    }

Conclusão

Neste artigo, tivemos uma introdução ao conceito de serviços REST, bem como da especificação JAX-RS, que define como implementar e consumir esse tipo de serviço em Java. Também vimos como empregar o Jersey 2.0, que é a implementação de referência da JAX-RS, para criar uma agenda de contatos com uma interface REST, além de como consumir esse serviço tanto a partir de um código Java, por meio da biblioteca cliente da JAX-RS, quanto via JavaScript.

Com isso, o leitor terá uma base importante, tanto conceitual, no que diz respeito ao REST e ao protocolo HTTP, quanto prática, quando lidamos com especificações Java e frameworks que implementam tais especificações. A partir dessa base, fica mais fácil para o desenvolvedor criar seus próprios projetos e explorar ainda mais as capacidades do modelo REST.

Saiu na DevMedia!

  • Curso de Spring Security:
    Um requisito fundamental no desenvolvimento de qualquer aplicação é a segurança. Pensando na importância desse assunto, neste curso você aprenderá a adicionar segurança em aplicações web ao mesmo tempo em que dá os primeiros passos com o framework Spring Security.
  • Curso de CDI em um projeto Java JSF:
    Aqui você aprenderá como aplicar a CDI, API para injeção de dependências, em um projeto Java web criado com a JSF, a Java Server Faces. Neste contexto, a JSF fornecerá a infraestrutura necessária para a criação de uma aplicação web MVC e a CDI permitirá um menor acoplamento entre as classes da aplicação ao disponibilizar a injeção de dependências.
  • Principais Anomalias Arquiteturais de Software:
    Entenda quando o mal planejamento arquitetural impacta na qualidade do ciclo de vida do software.

Saiba mais sobre REST ;)

  • Guia de REST e Java:
    Devido a sua simplicidade, os Web Services RESTful têm se tornado cada vez mais populares. Neste guia você encontrará os conteúdos que precisa para dominar esse modelo que permite a construção de serviços menores a APIs completas.
  • Um bate papo sobre REST & RESTful:
    Você sabe o que é REST e RESTful? Será que toda aplicação deveria ser RESTful? Confira um bate papo sobre essas e outras dúvidas aqui.
  • Introdução a web services RESTful:
    Aprenda neste artigo quais são as tecnologias de base e como criar seus primeiros web services no padrão RESTful, não perca e confira!