Crear filtros personalizados serialización | Java

Los filtros personalizados son filtros que especifica en el código de su aplicación. Se establecen en una secuencia individual o en todas las secuencias de un proceso. Puede implementar un filtro personalizado como patrón, método, expresión lambda o clase.

Leer una secuencia de objetos serializados

Puede establecer un filtro personalizado en uno ObjectInputStream o, para aplicar el mismo filtro a cada flujo, establecer un filtro para todo el proceso. Si ObjectInputStream no tiene un filtro definido para él, se llama al filtro de todo el proceso, si hay uno.

Mientras se decodifica la secuencia, se producen las siguientes acciones:
  • Para cada nuevo objeto de la secuencia, se llama al filtro antes de que se cree una instancia y se deserialice el objeto.
  • Para cada clase de la secuencia, se llama al filtro con la clase resuelta. Se llama por separado para cada supertipo e interfaz en la secuencia.
  • El filtro puede examinar cada clase a la que se hace referencia en la secuencia, incluida la clase de objetos que se crearán, los supertipos de esas clases y sus interfaces.
  • Para cada matriz de la secuencia, ya sea una matriz de primitivas, una matriz de cadenas o una matriz de objetos, se llama al filtro con la clase de matriz y la longitud de la matriz.
  • Para cada referencia a un objeto ya leído de la secuencia, se llama al filtro para que pueda verificar la profundidad, el número de referencias y la longitud de la secuencia. La profundidad comienza en 1 y aumenta para cada objeto anidado y disminuye cuando regresa cada llamada anidada.
  • El filtro no se llama para primitivas ni para instancias java.lang.String que están codificadas de forma concreta en la secuencia.
  • El filtro devuelve un estado de aceptar, rechazar o indeciso.
  • Las acciones de filtrado se registran si el registro está habilitado.
A menos que un filtro rechace el objeto, se acepta el objeto.

Configuración de un filtro personalizado para una transmisión individual

Puede establecer un filtro en un ObjectInputStream individual cuando la entrada a la secuencia no es de confianza y el filtro tiene un conjunto limitado de clases o restricciones que aplicar. Por ejemplo, puede asegurarse de que una transmisión solo contenga números, cadenas y otros tipos especificados por la aplicación.

Un filtro personalizado se ajusta con el método setObjectInputFilter. El filtro personalizado debe establecerse antes de que se lean los objetos de la secuencia.

En el siguiente ejemplo, el método setObjectInputFilter se invoca con el método dateTimeFilter. Este filtro solo acepta clases del paquete java.time. El método dateTimeFilter se define en un ejemplo de código en Configuración de un filtro personalizado como método.
LocalDateTime readDateTime(InputStream is) throws IOException {
    try (ObjectInputStream ois = new ObjectInputStream(is)) {
        ois.setObjectInputFilter(FilterClass::dateTimeFilter);
        return (LocalDateTime) ois.readObject();
    } catch (ClassNotFoundException ex) {
        IOException ioe = new StreamCorruptedException("class missing");
        ioe.initCause(ex);
        throw ioe;
    }
}

Configuración de un filtro personalizado para todo el proceso

Puede establecer un filtro para todo el proceso que se aplique a cada uso de ObjectInputStream a menos que se anule en una secuencia específica. Si puede identificar cada tipo y condición que necesita toda la aplicación, el filtro puede permitirlos y rechazar el resto. Por lo general, los filtros de todo el proceso se utilizan para rechazar clases o paquetes específicos, o para limitar el tamaño de la matriz, la profundidad del gráfico o el tamaño total del gráfico.

Un filtro de todo el proceso se establece una vez utilizando los métodos de la clase ObjectInputFilter.Config. El filtro puede ser una instancia de una clase, una expresión lambda, una referencia de método o un patrón.
ObjectInputFilter filter = ...
ObjectInputFilter.Config.setSerialFilter(filter);
En el siguiente ejemplo, el filtro de todo el proceso se establece mediante una expresión lambda.
ObjectInputFilter.Config.setSerialFilter(info -> info.depth() > 10 ? Status.REJECTED : Status.UNDECIDED);
En el siguiente ejemplo, el filtro de todo el proceso se establece mediante una referencia de método:
ObjectInputFilter.Config.setSerialFilter(FilterClass::dateTimeFilter);

Configurar un filtro personalizado usando un patrón

Se puede crear un filtro personalizado basado en patrones, que es conveniente para casos simples, mediante el método ObjectInputFilter.Config.createFilter. Puede crear un filtro basado en patrones como propiedad del sistema o propiedad de seguridad. La implementación de un filtro basado en patrones como método o expresión lambda le brinda más flexibilidad.

Los patrones de filtro pueden aceptar o rechazar clases, paquetes y módulos específicos y pueden establecer límites en el tamaño de la matriz, la profundidad del gráfico, las referencias totales y el tamaño de la secuencia. Los patrones no pueden coincidir con el supertipo o las interfaces de la clase.

En el siguiente ejemplo, el filtro permite example.File y rechaza las clases example.Directory.
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("example.File;!example.Directory");
Este ejemplo solo permite example.File. Todas las demás clases se rechazan.
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("example.File;!*");

Configurar un filtro personalizado como clase

Un filtro personalizado se puede implementar como una clase que implementa la interfaz java.io.ObjectInputFilter, como una expresión lambda o como un método.

Un filtro no suele tener estado y realiza comprobaciones únicamente en los parámetros de entrada. Sin embargo, puede implementar un filtro que, por ejemplo, mantenga el estado entre llamadas al método checkInput para contar artefactos en la secuencia.

En el siguiente ejemplo, la clase FilterNumber permite cualquier objeto que sea una instancia de la clase Number y rechaza todos los demás.
class FilterNumber implements ObjectInputFilter {
    public Status checkInput(FilterInfo filterInfo) {
        Class clazz = filterInfo.serialClass();
        if (clazz != null) {
            return (Number.class.isAssignableFrom(clazz)) ? Status.ALLOWED : Status.REJECTED;
        }
        return Status.UNDECIDED;
    }
}
En el ejemplo:
  • El método checkInput acepta un objeto ObjectInputFilter.FilterInfo. Los métodos del objeto brindan acceso a la clase que se va a verificar, el tamaño de la matriz, la profundidad actual, el número de referencias a los objetos existentes y el tamaño del flujo leído hasta el momento.
  • Si serialClass no es nulo, lo que indica que se está creando un nuevo objeto, se verifica el valor para ver si la clase del objeto es Number. Si es así, se acepta, de lo contrario se rechaza.
  • Devuelve cualquier otra combinación de argumentos UNDECIDED. La deserialización continúa y los filtros restantes se ejecutan hasta que el objeto es aceptado o rechazado. Si no hay otros filtros, se acepta el objeto.

Configurar un filtro personalizado como método

También se puede implementar un filtro personalizado como método. Se usa la referencia del método en lugar de una expresión lambda en línea.

El método dateTimeFilter que se define en el siguiente ejemplo lo utiliza el ejemplo de código en Configuración de un filtro personalizado para una transmisión individual.
public class FilterClass {
     
    static ObjectInputFilter.Status dateTimeFilter(ObjectInputFilter.FilterInfo info) {
        Class serialClass = info.serialClass();
        if (serialClass != null) {
            return serialClass.getPackageName().equals("java.time")
                ? ObjectInputFilter.Status.ALLOWED
                : ObjectInputFilter.Status.REJECTED;
        }
        return ObjectInputFilter.Status.UNDECIDED;
    }
}

Ejemplo: filtro de clases en el módulo java.base

Este filtro personalizado, que también se implementa como método, permite solo las clases que se encuentran en el módulo base del JDK. Este ejemplo funciona con JDK 9 y posteriores.
static ObjectInputFilter.Status baseFilter(ObjectInputFilter.FilterInfo info) {
    Class serialClass = info.serialClass();
    if (serialClass != null) {
        return serialClass.getModule().getName().equals("java.base")
            ? ObjectInputFilter.Status.ALLOWED
            : ObjectInputFilter.Status.REJECTED;
    }
    return ObjectInputFilter.Status.UNDECIDED;
}

Comentarios

Entradas populares