Integración en JAX-RS de la nueva libreria de tiempo de Java 8

Con la introducción de las nuevas clases de java.time en Java 8 podemos encontrarnos con la conveniencia de querer utilizarlas como parámetros en algunos métodos REST de nuestra API JAX-RS.

A continuación muestro un ejemplo real de una aplicación en la que utilizo dos tipos de java.time como parámetros de puntos REST. Concretamente las clases LocalDate y MonthDay.

Para añadir el soporte tan sólo tenemos que registrar un proveedor JAX-RS que implemente la interfaz ParamConverterProvider.

@Provider
public class ParamConverters implements ParamConverterProvider {  
  @SuppressWarnings("unchecked")
  @Override
  public <T> ParamConverter<T> getConverter(Class<T> rawType,
                                            Type genericType,
                                            Annotation[] annotations)
  {
    if (rawType == LocalDate.class)
      return (ParamConverter<T>) new ParamConverter<LocalDate>() {
        @Override
        public LocalDate fromString(String value) {
          return TimeUtils.localDateFromJSON(value);
        }

        @Override
        public String toString(LocalDate value) {
          return null;
        }
      };
    else if (rawType == MonthDay.class)
      return (ParamConverter<T>) new ParamConverter<MonthDay>() {
        @Override
        public MonthDay fromString(String value) {
          return TimeUtils.parseMonthDay(value);
        }

        @Override
        public String toString(MonthDay value) {
          return null;
        }
      };
    return null;
  }
}

La clase TimeUtils es una clase propia que utilizo para encapsular algunos métodos extra relaciones con java.time. Las partes interesantes son estas:

public class TimeUtils {  
  private static final Logger log = LoggerFactory.getLogger(TimeUtils.class);

  public static LocalDate localDateFromJSON(String data) {
    if (data.endsWith("Z")) {
      // les dates serialitzades via JSON ho fan amb aquest format
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
      return Instant.parse(data)
                    .atZone(ZoneId.systemDefault())
                    .toLocalDate();
    } else
      return LocalDate.parse(data);
  }

  public static MonthDay parseMonthDay(String s) {
    try {
      int[] ddmm = Arrays.stream(s.split("/"))
                         .mapToInt(Integer::parseInt)
                         .toArray();
      return MonthDay.of(ddmm[1], ddmm[0]);
    } catch (Exception e) {
      log.warn("No he pogut interpretar {} com MonthDay.", s);
      return null;
    }
  }
}

De la anterior clase la parte más notable es un pequeño hackeo para soportar correctamente las fechas en Javascript tal como se indica en el comentario. Dichas fechas se codifican siempre con el mismo formato relativo a UTC independientemente de la zona horaria local. Así que si queremos convertirlo a un LocalDate debemos suponer alguna zona horaria. En el caso de esta aplicación no habia soporte para zonas horarias y todo funcionaba en la misma, clientes y servidor, así que de ahí el uso de la zona de sistema.