JAX-RS API

Aplicando HATEOAS em uma API JAX-RS

HATEOAS (Hypermedia as the Engine of Application State) é um requisito da arquitetura REST, onde os recursos de uma API deve retornar nas suas respostas links (URI) que indicam como acessar outros recursos e/ou aplicar outras ações nos recursos acessados.

Desta forma, o client necessita conhecer apenas o endpoint principal da API para poder navegar nela. Ao implementar HATEOAS, este endpoint e todos os demais irão retornar links que permitirá ao client explorar todos os recursos fornecidos.

Existem algumas formas de implementar este recurso em API JAX-RS, que veremos a seguir.

Java - Fundamentos de JAX-WS e JAX-RS
Curso de Java - Fundamentos de JAX-WS e JAX-RS
CONHEÇA O CURSO

HATEOAS no JAX-RS com UriBuilder e Link

Para a implementação do HATEOAS, o JAX-RS fornece duas classes: UriBuilder e Link. Como o nome sugere, a UriBuilder facilita a criação de uma URI, enquanto Link é uma representação de um recurso relacionado que atende a especificação RFC 5988.

Para compreendê-los, vamos ver um exemplo prático.

Neste artigo, utilizarei de exemplo a API RESTful implementada em JAX-RS API já apresentada anteriormente. Como ela implementa autenticação JWT, o seu ponto de entrada será o endpoint de login, que no momento retorna apenas o token de acesso:

@POST
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Usuario usuario)
{
    try{
        if(usuario.getUsuario().equals("teste@treinaweb.com.br") 
           && usuario.getSenha().equals("1234"))
        {
            String jwtToken = Jwts.builder()
                .setSubject(usuario.getUsuario())
                .setIssuer("localhost:8080")
                .setIssuedAt(new Date())
                .setExpiration(
                    Date.from(
                        LocalDateTime.now()
                        .plusMinutes(15L)
                        .atZone(ZoneId.systemDefault())
                        .toInstant()
                    )
                )
                .signWith(CHAVE, SignatureAlgorithm.HS512)
                .compact();

            return Response.status(Response.Status.OK).entity(jwtToken).build();
        }
        else
            return Response
                    .status(Response.Status.UNAUTHORIZED)
                    .entity("Usuário e/ou senha inválidos")
                    .build();
    }
    catch(Exception ex)
    {
        return Response
                .status(Response.Status.INTERNAL_SERVER_ERROR)
                .entity(ex.getMessage())
                .build();
    } 
}

A partir dele, ao ser autenticado, o client poderá acessar as pessoas cadastradas. Para indicar isso, podemos utilizar a classe UriBuilder para criar a URI deste endpoint:

UriBuilder.fromUri("http://localhost:8080/")
        .path("pessoa")
        .build();

Este endpoint não define parâmetros, mas caso houve isso poderia ser indicado:

UriBuilder.fromUri("http://localhost:8080/")
        .path("pessoa/{id}")
        .build(2);

Desta forma, a URI criada seria:

http://localhost:8080/pessoa/2

Da mesma forma, também poderia ser adicionadas querystrings:

UriBuilder.fromUri("http://localhost:8080/")
        .path("pessoa/{id}")
        .queryParam("q", "{nome}")
        .build(2, "Carlos");

Neste caso, a URI seria:

http://localhost:8080/pessoa/2?q=Carlos

Para definir a URI do nosso endpoint, além da classe UriBuilder, faremos uso da classe Link:

Link link = Link.fromUriBuilder(
                    UriBuilder.fromUri("http://localhost:8080/")
                    .path("pessoa")
                )
                .rel("lista_pessoas")
                .type("GET")
                .build();

Note que esta classe, além do UriBuilder , define a relação da URI com o endpoint atual (de login) e o tipo da solicitação.

Para que este dado seja retornado na resposta da solicitação, ele deve ser informado no método link da classe Response:

return Response.status(Response.Status.OK).entity(jwtToken).links(link).build();

Ao fazer isso, esta informação estará no header da resposta:

Enpoint Login com Hateoas retornado no header

Este é o formato que a especificação RFC 5899 define. Porém este não é o usual, o client normalmente espera esta informação no corpo da resposta. Veremos como fazer isso conhecendo o recursos para HATEOAS do Jersey.

Problemas do HATEOAS no corpo da resposta

Para que o link do HATEOAS também seja mostrado no corpo de uma resposta, é necessário que ele seja definido como um campo do recurso:

public class Pessoa {
    private int id;
    private String nome;
    private int idade;
    private Link link;

    public Link getLink() {
        return link;
    }

    public void setLink(Link link) {
        this.link = link;
    }

    //..Código omitido
}

E ao retornar este recurso, este campo deve ser preenchido:

@Authorize
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Pessoa getById(@PathParam("id") int id) {
    Pessoa pessoa = _repositorio.Get(id);
    pessoa.setLink(
        Link.fromUriBuilder(
            UriBuilder.fromUri("http://localhost:8080/")
            .path("pessoa/{id}")
        )
        .rel("self")
        .type("GET")
        .build(id)
    );
    return pessoa;
}

Com isso, no corpo da resposta desta solicitação, o link será informado:

Endpoint Pessoa com Hateoas com um link mostrando todas as propriedades da classe Link

Entretanto, um problema disso é que também são retornadas algumas informações indesejadas. Para que isso seja resolvido, é necessário alterar json provider para o Jackson:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
</dependency>

Registrá-lo:

public static HttpServer startServer() {
    final ResourceConfig rc = new ResourceConfig().packages("br.com.treinaweb");
    rc.register(JacksonFeature.class);
    return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}

E por fim, criar um Adapter:

public class LinkAdapter extends XmlAdapter<LinkJaxb, Link> {

    public LinkAdapter() {}

    public Link unmarshal(LinkJaxb p1) {
        Link.Builder builder = Link
                                .fromUri(p1.getUri())
                                .rel(p1.getRel())
                                .type(p1.getType());

        return builder.build();
    }

    public LinkJaxb marshal(Link p1) {
        return new LinkJaxb(
                        p1.getUri(), 
                        p1.getRel(), 
                        p1.getType()
                    );
    }
}

class LinkJaxb {

    private URI uri;
    private String rel;
    private String type;

    public LinkJaxb() {
        this(null, null, null);
    }

    public LinkJaxb(URI uri, 
                    String rel, 
                    String type) 
    {
        this.uri = uri;
        this.rel = rel;
        this.type = type;
    }

    @XmlAttribute(name = "href")
    public URI getUri() {
        return uri;
    }

    public void setUri(URI uri) {
        this.uri = uri;
    }

    @XmlAttribute(name = "rel")
    public String getRel(){
        return rel;
    }

    public void setRel(String rel) {
        this.rel = rel;
    }

    @XmlAttribute(name = "type")
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

Que deve ser informado no campo do recurso:

@XmlJavaTypeAdapter(LinkAdapter.class)
private Link link;

Agora, o link retornado será mais amigável:

Enpoint Pessoa com HATEOAS de um link amigável

Entretanto, até agora foi necessário definir “manualmente” o link do recurso. Imagine quando for uma lista, este procedimento será bem “chato”. Felizmente o Jersey tem a solução para este problema.

HATEOAS no Jersey com @InjectLink

Para resolver o trabalho de criar um link individualmente para cada recurso, o Jersey fornece módulo Declarative Linking, que define anotação @InjectLink, que pode ser aplicada diretamente no campo Link:

@InjectLink(
        resource = PessoaResource.class,
        style = Style.ABSOLUTE,
        rel = "self",
        bindings = @Binding(name = "id", value = "${instance.id}"),
        method = "GET"
)
@XmlJavaTypeAdapter(LinkAdapter.class)
private Link link;

Este módulo requer a dependência abaixo:

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-declarative-linking</artifactId>
</dependency>

Com isso não é necessário criá-lo manualmente:

@Authorize
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Pessoa pessoa)
{
    try{
        _repositorio.Add(pessoa);
        return Response.status(Response.Status.CREATED).entity(pessoa).build();
    }
    catch(Exception ex)
    {
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
    } 
}

Pois será adicionado automaticamente pelo Jersey:

Enpoint Pessoa com HATEOAS de um link amigável

Caso o recurso possua vários links, eles podem ser agrupados em uma lista:

List<Link> links;

E esta lista pode receber a anotação @InjectLinks:

@InjectLinks({
    @InjectLink(
        resource = PessoaResource.class,
        style = Style.ABSOLUTE,
        rel = "self",
        bindings = @Binding(name = "id", value = "${instance.id}"),
        method = "GET"
    ),
    @InjectLink(
            resource = PessoaResource.class,
            style = Style.ABSOLUTE,
            rel = "update",
            bindings = @Binding(name = "id", value = "${instance.id}"),
            method = "PUT"
    ),
    @InjectLink(
            resource = PessoaResource.class,
            style = Style.ABSOLUTE,
            rel = "delete",
            bindings = @Binding(name = "id", value = "${instance.id}"),
            method = "DELETE"
    )
})
@XmlJavaTypeAdapter(LinkAdapter.class)
List<Link> links;

Ao acessar o recurso, ele retornará todos esses links:

Endpoint de Pessoa com HATEOAS com três links

Java - Criação de aplicações web com Spring MVC
Curso de Java - Criação de aplicações web com Spring MVC
CONHEÇA O CURSO

Conclusão

O HATEOAS possui pontos negativos e positivos (que não foram abordados aqui pois este não é o objetivo do artigo), entretanto é um novo padrão de projeto que facilita a compreensão de uma API RESTful. Como a sua implementação não é muito complexa, é algo que deve ser avaliado e sempre que possível implementado nas aplicações.

Por hoje é só, até a próxima 🙂

Implementando autenticação baseada em JWT em uma API RESTful JAX-RS

Segurança deve ser um ponto vital para qualquer aplicação web. Não importando o tamanho ela sempre conterá dados que necessitam de alguma proteção. APIs também se enquadram neste quesito, mas as formas tradicionais de autenticação, baseadas em telas de login e sessão, não podem ser aplicadas neste tipo de aplicação.

Por serem stateless por definição, APIs RESTful procuram implementar autenticações baseadas em alguma informação nas solicitações do usuário. Sendo que as opções mais comuns são: HTTP Basic Authentication, onde o usuário e senha codificado em base64 é enviado no header Authorization da solicitação; e Token Based Authentication, onde é enviado no header Authorization da solicitação um token assinado, garantindo que ele não foi adulterado.

Devido a facilidade de implementação e a maior segurança, dessas duas opções, a opção mais utilizada é a Token Based Authentication, que utiliza o padrão aberto JSON Web Token (JWT). Baseado em JSON este padrão nos permite fornecer várias informações sobre o usuário de forma compacta e auto-contida.

Neste artigo veremos como implementar este tipo de autenticação em uma API RESTful JAX-RS.

Java - Fundamentos de JAX-WS e JAX-RS
Curso de Java - Fundamentos de JAX-WS e JAX-RS
CONHEÇA O CURSO

Gerando o Token de acesso da API

Para este artigo não criarei uma aplicação do zero, utilizarei como base a API RESTful com a JAX-RS API que demostrei no meu artigo passado.

Para gerar o token, utilizaremos a biblioteca jjwt, assim, a primeira coisa a ser feita na aplicação é a adição das dependências dela:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.1</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.1</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.1</version>
    <scope>runtime</scope>
</dependency>

Com as dependências adicionadas, criaremos uma nova entidade chamada Usuario:

public class Usuario {
    private int idUsuario;
    private String usuario;
    private String senha;

    public int getIdUsuario() {
        return idUsuario;
    }

    public String getSenha() {
        return senha;
    }

    public void setSenha(String senha) {
        this.senha = senha;
    }

    public String getUsuario() {
        return usuario;
    }

    public void setUsuario(String usuario) {
        this.usuario = usuario;
    }

    public void setIdUsuario(int idUsuario) {
        this.idUsuario = idUsuario;
    }
}

E o recurso abaixo:

@Path("/login")
public class LoginResource {
    private final SecretKey CHAVE = Keys.hmacShaKeyFor(
        "7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
        .getBytes(StandardCharsets.UTF_8));

    @POST
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response post(Usuario usuario)
    {
        try{
            if(
                usuario.getUsuario().equals("teste@treinaweb.com.br") 
                    && 
                usuario.getSenha().equals("1234")
            )
            {
                String jwtToken = Jwts.builder()
                    .setSubject(usuario.getUsuario())
                    .setIssuer("localhost:8080")
                    .setIssuedAt(new Date())
                    .setExpiration(
                        Date.from(
                            LocalDateTime.now().plusMinutes(15L)
                                .atZone(ZoneId.systemDefault())
                            .toInstant()))
                    .signWith(CHAVE, SignatureAlgorithm.RS512)
                    .compact();

                return Response.status(Response.Status.OK).entity(jwtToken).build();
            }
            else
                return Response.status(Response.Status.UNAUTHORIZED)
                        .entity("Usuário e/ou senha inválidos").build();
        }
        catch(Exception ex)
        {
            return Response.status(
                        Response.Status.INTERNAL_SERVER_ERROR
                    ).entity(ex.getMessage())
                    .build();
        } 
    }
}

Note que nele há apenas um método, que realizará o login do usuário. Por esta ser uma aplicação simples, o usuário e senha válidos já estão definidos no código. Ao autenticar, será gerado um token:

String jwtToken = Jwts.builder()
    .setSubject(usuario.getUsuario())
    .setIssuer("localhost:8080")
    .setIssuedAt(new Date())
    .setExpiration(
        Date.from(
            LocalDateTime.now().plusMinutes(15L)
                .atZone(ZoneId.systemDefault())
            .toInstant()))
    .signWith(CHAVE, SignatureAlgorithm.RS512)
    .compact();

Onde, é informado:

  • Subject: o login do usuário;
  • Issuer: quem está gerando o token;
  • IssuedAt: data que o token foi gerado;
  • Expiration: tempo de vida do token;
  • sign: como o token será assinado;

Das informações, a mais importante é a assinatura do token. Neste exemplo ela utiliza uma chave textual visível:

private final SecretKey CHAVE = Keys.hmacShaKeyFor(
    "7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
    .getBytes(StandardCharsets.UTF_8));

Entretanto em uma aplicação real, esta chave precisa ser bem protegida, pois é através dela que os tokens serão validados pela aplicação. Assim, se uma pessoa mal-intencionada obter esta informação, ela poderá gerar tokens válidos e terá acesso aos recursos protegidos da API.

Ao testar o endpoint, se for informado o usuário e senha corretos, um token será gerado:

Requisição de login correta, gerando um token

Caso contrário será retornado 401 Unauthorized:

Requisição com a senha inválida, gerando uma resposta 401 Unauthorized

Com a geração do token pronta, temos que proteger nossos endpoints, o que veremos a seguir.

Autenticando as solicitações

Como vimos acima, o token será enviado no header Authorization da solicitação. Assim, para autenticá-las é necessário analisar este header e verificar se o token informado é valido. Apenas nessas situações as solicitações devem ser permitidas.

Para fazer isso, podemos utilizar o @NameBinding, que é uma meta annotation que nos permite definir filtros e interceptadores no pipeline da solicitação. Estes filtros são implementados via anotação, assim, inicialmente uma anotação deve ser definida:

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface Authorize { }

Onde utilizamos o @NameBinding e indicamos que a anotação será aplicada em métodos.

É na implementação desta anotação que verificamos a validade do token:

@Provider
@Authorize
@Priority(Priorities.AUTHENTICATION)
public class AuthorizeFilter implements ContainerRequestFilter {
    private final SecretKey CHAVE = 
            Keys.hmacShaKeyFor("7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
                                .getBytes(StandardCharsets.UTF_8));

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String authorizationHeader = requestContext
                                        .getHeaderString(HttpHeaders.AUTHORIZATION);
        try {
            String token = authorizationHeader.substring("Bearer".length()).trim();

            Jwts.parserBuilder()
                    .setSigningKey(CHAVE)
                    .build()
                    .parseClaimsJws(token);
        } catch (Exception e) {
            requestContext
                .abortWith(Response.status(Response.Status.UNAUTHORIZED)
                .build());
        }

    }

}

Inicialmente é indicado que a classe irá prover (@Provider) a implementação a nossa anotação (@Authorize) e que a prioridade dela no pipeline do JAX-RS é de autenticação (AUTHENTICATION). Ou seja, ela será executada antes dos endpoints.

Na classe, é obtido o token do header da solicitação:

String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
String token = authorizationHeader.substring("Bearer".length()).trim();

Em seguida é validado:

Jwts.parserBuilder()
    .setSigningKey(CHAVE)
    .build()
    .parseClaimsJws(token);

O método parseClaimsJws obtém os dados presentes no token como: Subject, Issuer, etc; caso haja algo errado, é gerada uma exceção. Assim, caso ocorra qualquer erro é retornado que o usuário não tem permissão de acesso:

requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());

Com a anotação definida, ela pode ser aplicada aos endpoints:

@Path("/pessoa")
public class PessoaResource {

    private PessoaRepository _repositorio = new PessoaRepository();

    @Authorize
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Pessoa> get() {
        return _repositorio.GetAll();
    }

    @Authorize
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Pessoa getById(@PathParam("id") int id) {
        return _repositorio.Get(id);
    }

    @Authorize
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response post(Pessoa pessoa)
    {
        try{
            _repositorio.Add(pessoa);
            return Response.status(Response.Status.CREATED).entity(pessoa).build();
        }
        catch(Exception ex)
        {
            return Response.status(
                        Response.Status.INTERNAL_SERVER_ERROR
                    ).entity(ex.getMessage())
                    .build();
        } 
    }

    @Authorize
    @PUT
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response put(@PathParam("id") int id, Pessoa pessoa)
    {
        Pessoa p = _repositorio.Get(id);
        if(p == null)
            return Response.status(Response.Status.NOT_FOUND).build();

        try{
            pessoa.setId(id);
            _repositorio.Edit(pessoa);
            return Response.status(Response.Status.OK).entity(pessoa).build();
        }
        catch(Exception ex)
        {
            return Response.status(
                        Response.Status.INTERNAL_SERVER_ERROR
                    ).entity(ex.getMessage())
                    .build();
        } 
    }

    @Authorize
    @DELETE
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response delete(@PathParam("id") int id)
    {
        Pessoa p = _repositorio.Get(id);
        if(p == null)
            return Response.status(Response.Status.NOT_FOUND).build();

        try{
            _repositorio.Delete(id);
            return Response.status(Response.Status.OK).build();
        }
        catch(Exception ex)
        {
            return Response.status(
                        Response.Status.INTERNAL_SERVER_ERROR
                    ).entity(ex.getMessage())
                    .build();
        } 
    }
}

Para testá-la vamos utilizar o Postman.

Testando a autenticação

Inicialmente gere um token:

Requisição de login gerando o token

Com o token gerado, ele deve ser informado na aba Auth do Postman:

Aba Auth do Postman, informando o token

Não se esqueça de definir o tipo como Bearer Token. Com isso, ao enviar uma solicitação, ela será aceita:

Requisição POST para pessoa, com token válido

Se o token não for informado, ou caso seja inválido, será retornado 401 Unauthorized:

Requisição POST para pessoa, com token inválido

Java - Fundamentos de JAX-WS e JAX-RS
Curso de Java - Fundamentos de JAX-WS e JAX-RS
CONHEÇA O CURSO

Conclusão

Neste artigo vimos uma implementação simples de autenticação utilizando o padrão JWT. Um padrão bem difundido e muito utilizado, então caso necessite implementar este tipo de autenticação na sua aplicação não deixe de dar uma olhada nele.

Mas independente do padrão utilizado, procure não deixar suas aplicações expostas, sempre adote um sistema de autenticação e conexão segura, HTTPS.

Por fim, você pode ver a aplicação demonstrada aqui no meu Github.

Criando uma API RESTful com a JAX-RS API

No Java, quando pensamos na criação de uma API RESTful a primeira opção que vem a mente é o projeto Spring. Este projeto facilita a criação de APIs REST com o Spring Boot, que é uma das bibliotecas fornecidas por ele. Entretanto, o Java define uma especificação para este tipo de aplicação, a JAX-RS API.

Mesmo não possuindo tanto destaque quanto o Spring Boot, como veremos neste artigo, é bem simples criar uma API REST com a JAX-RS API.

Java - Fundamentos de JAX-WS e JAX-RS
Curso de Java - Fundamentos de JAX-WS e JAX-RS
CONHEÇA O CURSO

A especificação JAX-RS API

A JAX-RS API trata-se de uma especificação, ela define interfaces e anotações fornecidas pelo Java EE, que podem ser utilizadas na criação de uma API RESTful. Um desenvolvedor pode criar a sua aplicação baseada unicamente nesta especificação, entretanto o usual é fazer uso de uma biblioteca que a implemente.

Entre as bibliotecas existentes, as implementações da JAX-RS API mais conhecidas são RESTEasy e Jersey. Sendo que a mais utilizada é a Jersey, assim neste artigo faremos uso dela.

Então vamos colocar a mão na massa.

Criando a aplicação

Para este artigo irei criar um projeto Maven utilizando o arquétipo jersey-quickstart-grizzly2, pois este arquétipo já possui o Jersey configurado e fornece embutido o servidor Grizzly.

Pelo terminal, o projeto pode ser criado com o comando abaixo:

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=br.com.treinaweb -DartifactId=jaxrsexample -Dpackage=br.com.treinaweb

No arquivo pom.xml haverão as dependências do Jersey:

<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.inject</groupId>
  <artifactId>jersey-hk2</artifactId>
</dependency>

Caso não queira utilizar o Jersey com o Grizzly, você pode substituir a primeira dependência acima pelas abaixo:

<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-server</artifactId>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-servlet</artifactId>
</dependency>

Com o projeto criado e o Jersey configurado, vamos começar a criar a nossa API REST com JAX-RS API.

Criando uma entidade e repositório

Este exemplo será um CRUD simples, mas para isso necessitamos de uma entidade, que será representada pela classe abaixo:

public class Pessoa {
    private int id;
    private String nome;
    private int idade;

    public int getId() {
        return id;
    }

    public int getIdade() {
        return idade;
    }

    public void setIdade(int idade) {
        this.idade = idade;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public void setId(int id) {
        this.id = id;
    }
}

Esta aplicação não lidará com um banco de dados, assim o repositório utilizará um hashmap na memória:

public class PessoaRepository {
    private final static HashMap<Integer, Pessoa> pessoas = new HashMap<>();

    public List<Pessoa> GetAll(){
        return new ArrayList<Pessoa>(pessoas.values());
    }

    public Pessoa Get(final int id) {
        return pessoas.get(id);
    }

    public void Add(final Pessoa pessoa) {
        if(pessoa.getId() == 0 )
            pessoa.setId(generateId(pessoas.size() + 1));
        pessoas.put(pessoa.getId(), pessoa);
    }

    public void Edit(final Pessoa pessoa) {
        pessoas.remove(pessoa.getId());
        pessoas.put(pessoa.getId(), pessoa);
    }

    public void Delete(final int id) {
        pessoas.remove(id);
    }

    private int generateId(final int possible)
    {
        if(pessoas.containsKey(possible))
            return generateId(possible + 1);
        return possible;
    }
}

Agora podemos criar o nosso recurso.

Recursos

No Jersey os endpoints são chamados de recursos. Um recurso nada mais é que uma classe que contém a anotação @Path:

import javax.ws.rs.Path;

@Path("/pessoa")
public class PessoaResource {

}

Como é possível notar no código acima, a anotação @Path é utilizada para indicar um caminho. Ao defini-lo na classe, significa que este será o nosso endpoint. Dentro dela, definimos métodos que representam os verbos HTTP, onde cada método deverá conter a anotação equivalente ao verbo que o invocará:

  • @DELETE = DELETE;
  • @GET = GET;
  • @HEAD = HEAD;
  • @OPTIONS = OPTIONS;
  • @POST = POST;
  • @PUT = PUT;
  • @PATCH = PATCH.

Neste nosso endpoint não utilizaremos todas essas anotações, apenas as equivalentes aos principais verbos: DELETE, GET, POST e PUT.

Ao realizar este procedimento, a classe ficará com o seguinte código:

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.core.MediaType;
import javax.ws.rs.core.Response;

import br.com.treinaweb.model.Pessoa;
import br.com.treinaweb.repositories.PessoaRepository;

@Path("/pessoa")
public class PessoaResource {

    private PessoaRepository _repositorio = new PessoaRepository();

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Pessoa> get() {
        return _repositorio.GetAll();
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Pessoa getById(@PathParam("id") int id) {
        return _repositorio.Get(id);
    }

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response post(Pessoa pessoa)
    {
        try{
            _repositorio.Add(pessoa);
            return Response.status(Response.Status.CREATED).entity(pessoa).build();
        }
        catch(Exception ex)
        {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
        } 
    }

    @PUT
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response put(@PathParam("id") int id, Pessoa pessoa)
    {
        Pessoa p = _repositorio.Get(id);
        if(p == null)
            return Response.status(Response.Status.NOT_FOUND).build();

        try{
            pessoa.setId(id);
            _repositorio.Edit(pessoa);
            return Response.status(Response.Status.OK).entity(pessoa).build();
        }
        catch(Exception ex)
        {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
        } 
    }

    @DELETE
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response delete(@PathParam("id") int id)
    {
        Pessoa p = _repositorio.Get(id);
        if(p == null)
            return Response.status(Response.Status.NOT_FOUND).build();

        try{
            _repositorio.Delete(id);
            return Response.status(Response.Status.OK).build();
        }
        catch(Exception ex)
        {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
        } 
    }
}

Repare que os métodos também definem o tipo de conteúdo que irão produzir/retornar:

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Pessoa> get() {
  return _repositorio.GetAll();
}

E o tipo que irão consumir:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Pessoa pessoa)
{
  try{
    _repositorio.Add(pessoa);
    return Response.status(Response.Status.CREATED).entity(pessoa).build();
  }
  catch(Exception ex)
  {
    return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
  } 
}

O Jersey se encarregará de converter o objeto retornado para JSON e o JSON recebido para o tipo do objeto indicado. Mas para que esta a conversão seja possível, é necessário definir a dependência abaixo (caso não esteja definida):

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-binding</artifactId>
</dependency>

Pronto, a nossa aplicação já pode ser utilizada.

Testando a aplicação

Caso esteja acompanhando este artigo e se o seu projeto foi criado com base o arquetipo jersey-quickstart-grizzly2, você pode compilar os códigos pelo Maven:

mvn clean compile

E executar o servidor embutido da aplicação:

mvn exec:java

Indiferente da forma que a aplicação é executada, poderemos interagir com o nosso endpoint:

POST:

Tela no Postman, mostrando o exemplo de uma requisição POST para o endpoint "http://localhost:8080/pessoa"

GET:

Tela no Postman, mostrando o exemplo de uma requisição GET para o endpoint "http://localhost:8080/pessoa"

PUT:

Tela no Postman, mostrando o exemplo de uma requisição PUT para o endpoint "http://localhost:8080/pessoa/1

GET ID:

Tela no Postman, mostrando o exemplo de uma requisição GET para o endpoint "http://localhost:8080/pessoa/1"

DELETE:

Tela no Postman, mostrando o exemplo de uma requisição DELETE para o endpoint "http://localhost:8080/pessoa/1"

Java - Stream API
Curso de Java - Stream API
CONHEÇA O CURSO

Conclusão

A JAX-RS API é uma poderosa especificação que pode ser aplicada em uma aplicação facilmente graças a implementação do Jersey, se tornando uma ótima alternativa para o Spring Boot.

Desta forma, caso necessite criar uma aplicação RESTfull API no Java, não deixe de dar uma olhada nos recursos fornecidos pela JAX-RS API e o Jersey.

Você pode baixar a aplicação deste artigo no meu Github.

© 2004 - 2019 TreinaWeb Tecnologia LTDA - CNPJ: 06.156.637/0001-58 Av. Paulista, 1765, Conj 71 e 72 - Bela Vista - São Paulo - SP - 01311-200