Java

Obtendo recursos embutidos em JARs com NIO.2

Digamos que no nosso projeto temos a seguinte estrutura:

.
└── src
    ├── meu-pacote
    │   └── nio2
    │       └── MinhaClasse.java
    └── config.xml

Dentro de MinhaClasse, estou interessado em obter o conteúdo do arquivo config.xml.

Para isso:

  • obtemos a URI do recurso através do método getResource de nosso classe seguido ao método toURI
  • utilizamos o método get da classe auxiliar Paths para obter um Path a partir da URI
  • criamos uma String a partir dos bytes retornados pelo método readAllBytes da classe auxiliar Files
public class MinhaClasse {
  public static void main(String[] args) throws URISyntaxException, IOException {

    URI uriDoRecurso = MinhaClasse.class.getResource("/config.xml").toURI();
    Path pathDoRecurso = Paths.get(uriDoRecurso);
    String conteudoDoRecurso = new String(Files.readAllBytes(pathDoRecurso));
    System.out.println(conteudoDoRecurso);

  }
}

Ao executarmos o código anterior de dentro de nossa IDE, o código tem o efeito esperado. Tudo funciona!

Mas e se exportarmos para um JAR?

Continue lendo

Anúncios
Java

Copiando arquivos de um diretório e seus sub-diretórios com Java 7+

Vamos dizer que temos um diretório chamado arquivos com o seguinte conteúdo:

.
└── arquivos
    ├── .bookignore
    ├── book.properties
    ├── imgs
        └── cover.jpg
    └── .gitignore

Queremos copiar todos os arquivos bem como o conteúdo do sub-diretório imgs para outro diretório chamado copia.

Fazer isso é fácil com os novos recursos do pacote java.nio disponíveis a partir do Java 7. Essas novidades foram definidas na JSR-203 e ficaram conhecidas como NIO.2.

Continue lendo

Arquitetura, Java

Fila de Mensagens Mortas

O que acontece quando há uma exceção em um recebedor registrado em uma fila?

Por exemplo:

@MessageDriven(activationConfig = {
  @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/FILA"),
  @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class RecebedorMDB implements MessageListener {
  public void onMessage(Message msg) {
    throw new RuntimeException("Erro!");
  }
}

A classe acima é um Message Driven Bean (MDB) registrado em FILA que lança uma exceção não checada ao receber uma mensagem.

Se verificarmos a fila, a mensagem foi consumida. Só que aconteceu um erro!

Continue lendo

Java, Programming

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…