15 de abril de 2020

Inyectar un bean a un RestResource

Seguimos con el Paso 3 de la entrada anterior. Ya tenemos nuestra bean ServicioEntidad cargada e inyectándose en las entidades (Sucesos) que se leen de la BD. ¿Qué pasa con los objetos que se crean al deserializarse el JSON de la llamada HTTP? Esos objetos también deben ser inyectados con el ServicioEntidad, no podemos esperar a que sean leídos desde la BD pues podría fallar el proceso que necesitara esa llamada.

Ahora nuestro punto de entrada es el controlador que recibe esa llamada. Si lo hicieramos a mano podríamos gestionarlo en el @Controller, cuando Jackson lo convirtiera y se lo pasara como argumento a nuestro método del controlador. Sin embargo nosotros usamos Data Rest, pero podemos inyectarlo justo antes, personalizando el bean que se encarga de leer ese JSON y convertirlo en nuestro objeto de negocio (en el ejemplo SucesoConId o sus subtipos). Para ello vamos a crear un bean que al deserializar el JSON inyecte nuestro bean ServicioEntidad. Para esto jackson nos da una anotación perfecta: @JsonComponent.

La anotación @JsonComponent se puede usar sobre una clase que implemente (de)serialización o sobre una clase que contenga varias implementaciones internas que cargará todas. Hay varias formas de aprovecharlo, nosotros nos vamos a centrar en crear en una clase todas las implementaciones de deserializador que necesitamos para nuestros distintos sucesos usando StdDeserializer.

Ya que vamos a hacernos un serializador para cada tipo de suceso vamos a aprovechar la herencia para reutilizar código. El esquema general quedará:
@JsonComponent // Registra la clase/clases internas con (de)serializadores
public class JsonDeserializers {

    public static class JsonSucesoSerializer<T extends SucesoConId> extends StdDeserializer<T> {... }

    public static class JsonGolSerializer extends JsonSucesoSerializer<GolConId> {...}

    public static class JsonTarjetaSerializer extends JsonSucesoSerializer<TarjetaConId> {...}
 
}
NOTA: los deserializadores de gol y tarjeta van a heredar del serializador de suceso.

Ya vimos un ejemplo en clase haciendo un deserializador personalizado. En este caso lo vamos a repetir pues se trata de ir leyendo el JSON e ir asignando los valores en cada campo. Para ello vamos a tener que añadir algún setter más a los Sucesos si no queremos hacer uso de Reflection. Lo interesante va a ser el uso de genéricos para que se adapten al resto de implementaciones para GolConId y TarjetaConId y el uso de un callback para poder añadir lo que falte en tipos que lo necesitan (añadir el tipo de tarjeta en TarjetaConId por ejemplo). La implementación de JsonSucesoSerializer<T extends SucesoConId> resumida quedará así:
public static class JsonSucesoSerializer<T extends SucesoConId> extends StdDeserializer<T> {

    // Constructores heredados del padre StdDeserializer<T>

    @Override
    public T deserialize(JsonParser jsonParser, DeserializationContext context)
                            throws IOException, JsonProcessingException {
        return deserializarSuceso(jsonParser, (Class<T>) handledType());
    }

    protected T deserializarSuceso(JsonParser jsonParser, Class<T> tipo)
                            throws IOException, JsonProcessingException {
        T sucesoDeserializado;
        // Construir y asignar valores a campos. Ver a continuacion.

        postDeserializarSuceso(nodo, sucesoDeserializado);

        return sucesoDeserializado;
    }

    // Callback para completar la deserializacion
    protected T postDeserializarSuceso(JsonNode nodo, T sucesoDeserializado) {
        return sucesoDeserializado;
    }

}
Lo más importante de implementar StdDeserializer, aparte del parseo en sí, se puede decir que es el método handledType(). Usando ese método vamos a tener acceso al tipo de objeto que hay que deserializar y con ello podremos tener acceso al constructor que necesitemos. Para eso sí que vamos a usar Class. Con estas herramientas la construcción de un SucesoConId quedará:
protected T deserializarSuceso(JsonParser jsonParser, Class<T> tipo)
                        throws IOException, JsonProcessingException {
    T sucesoDeserializado;
    try {
        // Variable intermedia final para los lambdas
        // El constructor vendra marcado por el tipo a construir y el parametro indicado
        T suceso = tipo.getConstructor(ServicioEntidad.class).newInstance(servicioEntidad);
        JsonNode nodo = jsonParser.getCodec().readTree(jsonParser);
        // Podemos usar Optional para seguir ejecutando
        // o no si no hay campo
        Optional.ofNullable(nodo.get("timestamp")).ifPresent(n -> suceso.setTimestamp(n.asLong()));
        Optional.ofNullable(nodo.get("idParticipante")).ifPresent(n -> suceso.setIdParticipante(n.asText()));
        Optional.ofNullable(nodo.get("partido")).ifPresent(n -> {
            String idPartido = n.asText();
            idPartido = idPartido.substring(idPartido.lastIndexOf("/") + 1, idPartido.length());
            suceso.setPartido(partidoDAO.getOne(Long.parseLong(idPartido)));
        });
        postDeserializarSuceso(nodo, suceso);

        sucesoDeserializado = suceso;
    } catch (Exception e) {
        e.printStackTrace();
        sucesoDeserializado = null;
    }

    return sucesoDeserializado;
}
Lo destacable de aquí es que este método recibe el tipo del método handledType() que será implementado distinto en sus clases hijas. Con el tipo se construye con el constructor que admite un ServicioEntidad y ya estrará inyectado. Lo demás es ir leyendo y asignando valores hasta la invocación de postDeserializarSuceso(nodo, suceso). Con este callback dejamos la puerta abierta a completar la deserialización en las clases hijas.


Ahora que ya hemos completado el serializador padre podemos aprovecharlo para rematar con los otros dos que necesitamos:
// Basta con devolver el tipo a construir
public static class JsonGolSerializer extends JsonSucesoSerializer<GolConId> {
    @Override public Class<?> handledType() { return GolConId.class; }
}

public static class JsonTarjetaSerializer extends JsonSucesoSerializer<TarjetaConId> {
    @Override public Class<?> handledType() { return TarjetaConId.class; }

    @Override
    protected TarjetaConId postDeserializarSuceso(JsonNode nodo, TarjetaConId sucesoDeserializado) {
        Optional.ofNullable(nodo.get("tipoTarjeta"))
            .ifPresent(n -> sucesoDeserializado.setTipoTarjeta(TipoTarjeta.valueOf(n.asText())));
        return sucesoDeserializado;
    }
}
Vemos que nos quedan muy simples: para Gol sólo necesita decir el tipo que maneja y para Tarjeta también utilizar el callback para asignar el tipo de tarjeta (amarilla o roja) que traiga el nodo.

Ahora ya nos pueden hacer llamadas HTTP que Jackson se encargará de inyectar el ServicioEntidad.

Puedes conseguir el código hasta aquí en su repositorio.

Como práctica se propone hacer lo mismo para los partidos, ya que si se invocase código que necesitase del ServicioEntidad en la clase Partido nuestro código fallaría si no lo ha cargado ya @PostLoad en la práctica que vimos en la entrada anterior.

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares