O erro do criador do Ant

No livro Pragmatic Project Automation, publicado em 2004, há um trecho em que James Duncan Davidson (criador do Ant e do Tomcat) admite que foi um erro ter usado XML para descrever os passos do build do Ant.

Segue uma tradução livre:

O criador do Ant exorciza um de seus demônios
por James Duncan Davidson

A primeira versão do Ant não tinha esse monte de tags que você vê espalhadas pelos arquivos de build. Ao invés disso, era usado um arquivo de properties e a classe java.util.Properties para definir quais tasks deveriam ser executadas para um determinado target. Funcionou bem para pequenos projetos mas começou a entrar em colapso à medida que os projetos cresciam.

O motivo do colapso era a maneira que o Ant enxerga o mundo: um projeto é uma coleção de targets (alvos). Um target é uma coleção de tasks (tarefas). Cada task tem um conjunto de propriedades. É obviamente uma árvore hierárquica. Porém, arquivos .properties tem apenas mapeamentos chave=valor, nos quais essa estrututura de árvore não encaixa.

Eu queria um formato de arquivos hierárquico que capturasse a visão de mundo do Ant. Mas eu não queria criar meu próprio formato. Eu queria usar um formato padrão e, mais importante, eu não queria criar um parser. Eu queria reusar o trabalho dos outros. Eu queria o caminho mais fácil.

Na época, XML estava despontando no radar. A spec tinha sido finalizada, ainda que bem recentemente. SAX tinha se tornado um padrão de-facto, mas não tínhamos JAXP ainda. Eu estava convencido que XML era a next big thing depois do Java. Código portável e dados portáveis. Duas frases de efeito que ficam bem juntas.

E já que os dados no XML tem uma estrutura de árvore, parecia perfeito para o tipo de coisa que precisava ser expressado em um arquivo de build. Adicione o fato de que XML era um formato de texto editável manualmente, e parecia um casamento feito nos céus. E eu não precisava criar um parser. Trato feito.

Em retrospecto, XML provavelmente não foi a escolha correta como parecia. Eu tenho visto arquivos de build com centenas, e até milhares, de linhas e, nesses tamanhos, editar XML não é tão amigável como eu esperava. Além disso, quando você mistura XML e os mecanismos baseados em reflection do Ant que permitem estender e customizar tasks, você acaba com um ambiente que oferece o poder e a flexibilidade de uma linguagem de script — mas com a dor de cabeça de tentar expressar essa flexibilidade com tags no XML.

Minha intenção nunca foi que o formato de arquivo se transformasse em um linguagem de script. Afinal de contas, minha visão original do Ant era que teríamos a declaração de algumas propriedades que descreveriam o projeto e que as tasks escritas em Java fariam toda a lógica. Os atuais desenvolvedores do Ant em geral pensam o mesmo.

Mas quando eu fundi XML e reflection no Ant, fiz algo que é 70–80% de um ambiente de script. Só que não percebi na época. Negar que as pessoas iriam usar o XML do Ant como uma linguagem de script é equivalente a pedir para que finjam que açúcar não é doce.

Se eu soubesse o que sei agora, eu teria tentado usar uma linguagem de script de verdade, como JavaScript via o componente Rhino ou Python via Jython, com bindings para objetos Java que implementariam a funcionalidade expressadas nas tasks. Então, haveria uma forma natural de expressar lógica, e não estaríamos presos ao XML como formato que é muito ruim para o maneira como as pessoas querem usar a ferramenta.

Um processo de build é algo que comumente será customizado. Usar XML para definir os passos de um build foi um erro no Ant. E que foi herdado pelo Maven.

O poder de uma linguagem de script casa muito bem com a definição de um processo de build. E isso foi levado em conta em ferramentas mais novas como Gradle, que usa a linguagem Groovy.

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.

Métodos default, colisão de interfaces e precedência de superclasses no Java 8

Métodos públicos e abstratos em Interfaces

Imagine que temos a interface Aquatico, que define um método move:

interface Aquatico {
	public abstract void move();
}

Se tivermos uma classe Navio que implementa essa interface mas não fornece uma implementação para o método move, teremos um erro de compilação:

class Navio implements Aquatico {
^
error: Navio is not abstract and does not override abstract method move() in Aquatico
}

Uma interface nos obriga a respeitar um contrato, ou seja, a assinatura (nome, parâmetros e retorno) de um ou mais métodos.

No Java, desde os primórdios, os métodos das interfaces são implicitamente public e abstract. Essa última palavra-chave indica que são métodos abstratos, ou seja, sem corpo definido e sem código, meros moldes a serem preenchidos.

Para o código da classe concreta Navio ser compilado com sucesso, temos que fornecer uma implementação para o método move. Por exemplo:

class Navio implements Aquatico {
	public void move() {
		System.out.println("Navega na água!");
	}
}

O código acima compila normalmente e podemos invocá-lo de maneira polimórfica, como a seguir:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"
	}
}

Métodos com mesma assinatura em Interfaces diferentes

Considere que temos uma interface Terrestre, conforme abaixo:

interface Terrestre {
	public abstract void move();
}

Poderíamos criar um classe Caminhao que implementa a interface Terrestre, de maneira semelhante ao que fizemos acima:

class Caminhao implements Terrestre {
	public void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

Para utilizar a classe Caminhao, faríamos de maneira parecida com a anterior:

public class Programa {
	public static void main(String[] args){
		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Mas o que acontece se tivermos uma classe que representa um Hovercraft, um veículo anfíbio, que pode se deslocar tanto na água como na terra?

Será que podemos implementar tanto a interface Aquatico como a interface Terrestre simultaneamente?

Claro que sim, já que a interface define apenas a assinatura (ou seja, nome, parâmetros e retorno) do método move e não uma implementação.

class Hovercraft implements Aquatico, Terrestre {
	public void move(){
		System.out.println("Move na água e na terra!");
	}
}

Não há problema algum, porque a implementação do método move na classe Hovercraft é a mesma tanto para Aquatico como para Terrestre.

Podemos usar a classe Hovercraft conforme a seguir:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); //imprime "Move na terra e na água!"
	}
}

Métodos default em Interfaces no Java 8

Conforme vimos em um post anterior, no Java 8 agora é possível definir, além de contratos, implementações de métodos em interfaces.

Esse novo mecanismo é chamado de métodos default e, com ele, é possível evoluir interfaces antigas como a Iterable, que agora possui um método forEach, por exemplo.

No exemplo acima, poderíamos definir o comportamento de navegar na água diretamente na interface Aquatico, utilizando a palavra reservada default:

interface Aquatico {
	public default void move() {
		System.out.println("Navega na água!");
	}
}

Métodos default só podem ser definidos em interfaces e são implicitamente públicos e, é claro, não-abstratos porque possuem um corpo definido, com código.

A classe Navio já não precisará fornercer uma implementação para o método move:

class Navio implements Aquatico {
}	

A interface Terrestre também pode ter uma implementação do método move com um método default:

interface Terrestre {
	public default void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

A classe Caminhao também seria simplificada:

class Caminhao implements Terrestre {
}

O programa que utiliza as classes Navio e Caminhao, bem como suas respectivas interfaces, não precisa ser alterado:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"

		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Colisão de métodos default

Mas o que será que acontece com a nossa classe Hovercraft, que implementa tanto Aquatico como Terrestre?

class Hovercraft implements Aquatico, Terrestre {
^
error: class Hovercraft inherits unrelated defaults for move() from types Aquatico and Terrestre

Observe que, para a classe Hovercraft, o código não pode ser compilado. Como com um método default não apenas define uma assinatura mas também uma implementação, há ambiguidade na definição do método move. O compilador não pode definir qual das duas implementações deve ser escolhida e, por isso, nos avisa dessa colisão com um erro de compilação.

Para fazermos a classe Hovercraft compilar com sucesso, temos que definir uma implementação para o método move, evitando qualquer ambiguidade:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		System.out.println("Move na terra e na água!");
	}
}

Uma alternativa é invocar um método default de uma interface a partir de uma classe que a implementa, utilizando o nome da interface seguido de super e do nome do método default. Por exemplo:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		Aquatico.super.move();
		Terrestre.super.move();
		System.out.println("Move na terra e na água!");
	}
}

Se utilizarmos a classe Hovercraft acima em um programa, teremos algo como:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); 
		// imprime 
		//	"Navega na água!"
		//	"Move na longa estrada da vida!"
		//	"Move na terra e na água!"
	}
}

Precedência de métodos da superclasse

Suponha que tenhamos uma interface Aereo, com um método default chamado move:

interface Aereo {
	public default void move() {
		System.out.println("Move no ar!");
	}
}

Também temos uma classe Aviao que implementa a interface Aereo, porém sobreescrevendo o método move:

class Aviao implements Aereo {
	public void move(){
		System.out.println("Voa...");
	}
}

Se criarmos um objeto da classe Aviao, qual método será chamado: o método sobreescrito na classe ou o método default da interface?

Será escolhido o método sobreescrito na classe Aviao, funcionando da mesma maneira que sobreescrita de métodos para classes:

public class Programa {
	public static void main(String[] args){
		Aviao boeing747 = new Aviao();
		boeing747.move(); //imprime "Voa..."
	}
}

Agora, se quisermos representar um Hidroavião, um tipo de avião que pode decolar e pousar na água, teremos uma classe semelhante a seguinte:

class HidroAviao extends Aviao implements Aquatico {
}

A classe HidroAviao é um Aviao que implementa a interface Aquatico.

Mas o que será que acontece ao invocarmos o método move de uma instância de HidroAviao? Será que é invocado a implementação da superclasse Aviao ou da interface Aquatico?

A implementação escolhida será a da superclasse:

public class Programa {
	public static void main(String[] args){
		HidroAviao curtissNC = new HidroAviao();
		curtissNC.move(); //imprime "Voa..."
	}
}

Uma implementação definida em uma superclasse sempre tem precedência sobre uma implementação definida em um método default de uma interface.

É importante lembrar que, em Java, uma classe só pode ter uma superclasse.

Resumindo

As regras do uso de métodos default são bem claras:

  1. Colisão de interfaces – se uma classe implementa duas interfaces que possuem métodos default com a mesma assinatura, o método deve ser sobreescrito
  2. Precedência de superclasses – se uma superclasse provê uma implementação de um método que tem a mesma assinatura de um método default de uma interface, a implementação da superclasse tem precedência