Pacote java.time com JAX-RS no Wildfly 8.2

Temos a seguinte API de lista de tarefas feita com JAX-RS:

@Path("/tarefas")
public class TarefasResource {
  @Inject
  private TarefasRepository repo;

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public Response nova(Tarefa t) throws URISyntaxException {
    repo.cria(t);
    return Response.created(new URI("/tarefas/" + t.getId()))
      .entity(t)
      .build();
  }

  @GET
  @Path("/{id}")
  @Produces(MediaType.APPLICATION_JSON)
  public Tarefa lista(@PathParam("id") Integer id) {
    return repo.busca(id);
  }
}

Uma tarefa tem as propriedades id, do tipo Integer descricao, do tipo String e data, do tipo LocalDate do pacote java.time:

public class Tarefa {
  private Integer id;
  private LocalDate data;
  private String descricao;
  //getters e setters...
}

A API de estilo REST define o recurso /tarefas. Para criar uma nova tarefa deve ser enviado um POST para a URI /tarefas com uma representação da tarefa em JSON. Para obter um JSON com a tarefa de id 1, deve ser enviado um GET para a URI /tarefas/1.

O código acima foi implantado em um Wildfly 8.2.0.Final.

Problemas ao serializar objetos do java.time de/para JSON

Mas há algo de estranho.

Ao enviarmos um GET para /tarefas/1, obtemos o seguinte JSON:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": {"year":2016,"month":"FEBRUARY","chronology":{"calendarType":"iso8601","id":"ISO"},"era":"CE","dayOfYear":56,"dayOfWeek":"THURSDAY","leapYear":true,"dayOfMonth":25,"monthValue":2}
}

A representação do LocalDate em JSON ficou gigantesca! Vários detalhes internos foram exibidos…

E se tentarmos enviar um POST para /tarefas com o JSON abaixo?

{
  "data": "2016-02-26",
  "descricao": "Configurar Wildfly"
}

O resultado será um erro 400 (Bad Request) com a seguinte mensagem:

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-02-26'); no single-String constructor/factory method
 at [Source: io.undertow.servlet.spec.ServletInputStreamImpl@1c6dc29c; line: 1, column: 2] (through reference chain: br.com.alexandreaquiles.modelo.Tarefa["data"])

A mensagem de erro acima informa que o Jackson, a biblioteca de serialização JSON usada pelo Wildfly, não conseguiu transformar a String em um LocalDate.

Como fazer para ensinar o Jackson a trabalhar com um objeto do tipo LocalDate ou de outras classes do pacote java.time como se fossem Strings?

Melhorando a (de)serialização

O Wildfly 8.2.0.Final usa a versão 2.4.1 do Jackson, que tem a extensão jackson-datatype-jsr310, responsável por serializações mais interessantes de/para classes do pacote java.time.

Se estivermos usando o Maven, basta adicionar mais uma dependência:

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.4.1</version>
</dependency>

Outras ferramentas de gerenciamento de dependência teriam configurações análogas. Caso não esteja usando nenhuma ferramenta do tipo, baixe o jar.

Ao testarmos novamente, os mesmos erros acontecem… Que chato!

É que precisamos configurar um ContextResolver, anotando-o com @Provider e registrando o módulo JSR310Module do Jackson:

@Provider
public class JacksonJavaTimeConfiguration implements ContextResolver<ObjectMapper> {
  private final ObjectMapper mapper;

  public JacksonJavaTimeConfiguration() {
    mapper = new ObjectMapper();
    mapper.registerModule(new JSR310Module());
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return mapper;
  }
}

Note a configuração WRITE_DATES_AS_TIMESTAMPS setada para false. Se não fizermos isso, as datas são representadas como um array no JSON, ao invés de um texto.

Agora, se enviarmos um GET para /tarefas/1, obtemos:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": "2016-02-25";
}

O LocalDate passa a ser representado no JSON como uma String no formato ISO-8601.

Ao enviarmos o POST para /tarefas novamente, tudo dá certo! Recebemos um status 201 (Created).

Funciona em outro servidor de aplicação?

Infelizmente, não!

A grande questão é que nossa configuração foi feita para a biblioteca usada pelo Wildfly, o Jackson, inclusive em uma versão específica.

O Glassfish, por exemplo, usa a biblioteca MOXy para serialização de/para JSON. As configurações seriam diferentes…

Dependências do Java EE 7 no Maven

Um projeto que usa o Maven ganha um monte de facilidades, da compilação a geração dos entregáveis em apenas um comando.

Vamos supor que temos o seguinte pom.xml:

<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.alexandreaquiles</groupId>
  <artifactId>exemplo</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>

  <dependencies>
    <!-- aqui ficam as dependências -->
  </dependencies>
</project>

Considere que nosso projeto tem a seguinte classe:

@Stateless
@Path("/produtos")
public class ProdutosResource {
  @Inject
  private EntityManager em;
  @GET
  public List<Produto> lista(){
    return em.createQuery("select p from Produto p", Produto.class).getResultList();
  }
}

Usamos algumas funcionalidades do Java EE 7 como EJBs (@Stateless), JPA (EntityManager) e JAX-RS (@Path e @GET).

A questão é: como devemos declarar as dependências dessas API do Java EE 7? Será que precisamos declarar cada API utilizada, uma a uma?

Dependência do Java EE 7

Não! Há uma dependência publicada no repositório central do Maven que disponibiliza as APIs do Java EE 7:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Perceba o escopo provided acima. Estamos indicando que essas APIs devem ser utilizadas apenas para compilação e não precisam ser incluídas no entregável (WAR, no nosso caso).

É importante notar que passamos a depender apenas das APIs e não de nenhuma implementação. Isso é muito interessante se desejarmos mudar de servidor de aplicação. Podemos gerar o WAR e implantá-lo no Wildfly, no Glassfish e em qualquer servidor que implemente o Java EE 7.

Dependência do Java EE 7 Web Profile

Na verdade, a dependência acima é da versão Full do Java EE, fazendo com que possamos usar JMS, JCA, entre outros. No nosso caso, é suficiente depender do Web Profile, que possui menos funcionalidades mas tem o que precisamos. Então, poderíamos usar:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-web-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Dependências mantidas pela JBoss

A empresa JBoss, que mantém os servidores de aplicação Wildfly e JBoss EAP e vários outros projetos, disponibiliza dependências alternativas para as APIs do Java EE 7.

Para ter como dependência as APIs do Java EE 7 Full:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

Já para as do Java EE 7 Web Profile:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-web-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

As dependências acima não são para a implementação do Wildfly ou do JBoss EAP. São dependências para as APIs do Java EE alternativas às vistas anteriormente.

Por que é divertido programar?

O livro O Mítico Homem-Mês, um clássico da Engenharia de Software publicado em 1975, tem em seu primeiro capítulo um texto excelente sobre o motivo programar ser tão divertido.

Da tradução em português:

Por que é divertido programar? Que alegrias o praticante dessa arte pode ter como recompensa?

A primeira é a satisfação de construir algo. Da mesma maneira que uma criança delicia-se com seu bolinho de lama, os adultos divertem-se construindo coisas, sobretudo aquelas que eles mesmos projetam. Penso que essa alegria vem da imagem da alegria de Deus em criar, uma alegria que se revela nas diferenças e singularidades de cada folha de árvore, de cada floco de neve.

A segunda é a felicidade de se construir coisas que são úteis para os outros. No fundo, queremos que outros experimentem nosso trabalho e o considerem bem útil. Assim, a programação de um sistema não é em si diferente do primeiro porta-lápis de argila que uma criança faz “para o escritório do papai”.

A terceira é o fascínio da montagem de objetos complexos, à semelhança de quebra-cabeças com peças móveis que se interconectam, e da observação de como tudo trabalha em ciclos sutis, gerando consequências de princípios estabelecidos desde o início. O computador programado tem todo o fascínio de uma máquina de fliperama ou do mecanismo de uma jukebox, mas levado ao extremo.

A quarta é a aprendizagem constante, que vem da natureza não repetitiva da tarefa. De uma forma ou de outra, o problema é sempre novo, e quem o soluciona aprende algo: algumas vezes a prática, outras a teoria, ou ambas.

Finalmente, há a delícia de trabalhar em um meio tão maleável. O programador, como o poeta, trabalha apenas levemente deslocado de um ambiente de pensamento puro. Ele constrói seus castelos no ar, de ar, criando a partir da sua imaginação. Poucos meios de criação são tão flexíveis, tão fáceis de polir e retrabalhar, tão prontamente capazes de produzir grandes estruturas conceituais. ([…], e esta mesma maleabilidade tem seus próprios problemas.) Entretanto, a construção de um programa, ao contrário das palavras de um poeta, é real no sentido de que se move e trabalha, produzindo saídas visíveis e separadas da construção em si. Ele imprime resultados, desenha gráficos, produz sons, move braços. A mágica de mitos e lendas tornou-se realidade em nosso tempo. Basta alguém digitar o encantamento correto em um teclado e logo uma tela de visualização ganha vida, mostrando coisas que não existiam nem poderiam existir.

Por isso, a programação é divertida, porque satisfaz anseios criativos acalentados profundamente dentro de nós e deleita sensibilidades que temos em comum com todos os seres humanos.

A primeira vez que li esse texto foi no site pessoal de Arun Murthy, um dos contribuidores do Hadoop.

Iteração interna com Java 8 e métodos default

Iterando externamente

Vamos dizer que temos uma lista com nomes de personagens da Disney:

List<String> personagens = Arrays.asList("Pato Donald",
                            "Mickey", "Pateta", "Pluto");

Temos algumas opções para percorrer essa lista e imprimir o conteúdo. A mais intuitiva para iniciantes, é um for:

  for(int i = 0; i < personagens.size(); i++){
    String personagem = personagens.get(i);
    System.out.println(personagem);
  }

Uma opção mais interessante, mas que requer um pouco mais de conhecimento das bibliotecas do Java, é utilizar um Iterator:

  for(Iterator<String> it = personagens.iterator(); it.hasNext();){
    String personagem = it.next();
    System.out.println(personagem);
  }

Ambas as opções, se desconsiderarmos o uso de tipos genéricos, já funcionavam até o Java 4. Porém, do Java 5 em diante, percorrer uma lista ficou mais conciso através do for-each:

  for(String personagem : personagens){
    System.out.println(personagem);
  }

Mesmo com um for-each do Java 5, percorrer uma lista é algo tedioso e repetitivo. Quantas vezes um desenvolvedor Java experiente já escreveu um for desse tipo durante a carreira?

Com esse tipo de código, o desenvolvedor precisa se preocupar em “como” percorrer uma lista ao invés de focar apenas no mais importante: “o que” fazer com os elementos.

Essa maneira clássica, que deixa o “como” a cargo do desenvolvedor, pode ser chamada de iteração externa.

Iterando internamente

Com o Java 8, é possível focar apenas no “o que” fazer com os elementos da lista percorrida, utilizando o método forEach:

personagens.forEach(personagem -> System.out.println(personagem));

Observe que o método forEach pertence à própria lista e recebe um lambda.

Na verdade, pode ser passado qualquer lambda ou classe anônima que atenda à interface funcional Consumer, que recebe um argumento e não retorna resultado.

Poderíamos ter abreviado mais ainda o código utilizando uma referência ao método System.out.println:

personagens.forEach(System.out::println);

Essa maneira nova, que permite ao desenvolvedor focar no “o que” fazer com os elementos, pode ser chamada de iteração interna.

Inserindo implementações em interfaces antigas com métodos default

O método forEach está definido em Iterable e, por consequência, em todos os List, Set e Queue da API de Coleções do Java. Porém, esse método não é abstrato.

O que iria acontecer se colocassem mais um método abstrato na interface Iterable? Código de várias bibliotecas e frameworks por aí, que fornececem suas próprias implementações para essas classes, iriam deixar de funcionar. E o Java tem como um de seus princípios mais fortes a retrocompatibilidade e evolução suave (para muitos, suave até demais).

Para evitar qualquer quebra inaceitável, foi criada uma implementação concreta na interface Iterable para o método forEach. Como foi possível inserir uma implementação em uma interface? Antes do Java 8, as interfaces só podiam ter métodos públicos e abstratos, afinal de contas…

Foi criado um novo mecanismo: os métodos default. Para definir um método default, criamos um método com a palavra-chave default e, em seguida, fornecemos uma implementação para o método.

Veja o que aconteceu com a interface Iterable:

package java.lang;

//imports ...

public interface Iterable<T> {

  Iterator<T> iterator(); //método implicitamente 
                          //abstrato e público 

  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
      for (T t : this) {
        action.accept(t);
      }
  }

  //...
}

Métodos default permitem evoluir uma API antiga de maneira não disruptiva, sem romper com o passado. A implementação definida em uma método default é “copiada” para todas as classes que implementam essa interface. Em um próximo post, veremos com mais detalhes como funciona esse mecanismo.

Métodos default acabaram com a prática comum de ter uma interface acompanhada de uma superclasse abstrata que fornece implementações para a maioria dos métodos e deixando apenas detalhes para as subclasses concretas.

Um exemplo disso é o par List e AbstractList. Agora, se fossemos reprojetar a API de Coleções do Java, poderíamos colocar boa parte do código de AbstractList como métodos default da interface List.

O código desse post pode ser encontrado em: https://gist.github.com/alexandreaquiles/9753849