Introducing JDK 17 easy filter creation – Java I/O: Context-Specific Deserialization Filters


134. Introducing JDK 17 easy filter creation

Starting with JDK 17, we can express filters more intuitively and readable via two convenient methods named allowFilter() and rejectFilter(). And, since the best theory is an example, here is a usage case of these two convenient methods:

public final class Filters {
 private Filters() {
  throw new AssertionError(“Cannot be instantiated”);
 }
 public static ObjectInputFilter allowMelonFilter() {
  ObjectInputFilter filter = ObjectInputFilter.allowFilter(                      
   clazz -> Melon.class.isAssignableFrom(clazz),
            ObjectInputFilter.Status.REJECTED);
   return filter;
 }
 public static ObjectInputFilter rejectMuskmelonFilter() {
  ObjectInputFilter filter = ObjectInputFilter.rejectFilter(                      
   clazz -> Muskmelon.class.isAssignableFrom(clazz),
            ObjectInputFilter.Status.UNDECIDED);
        return filter;
    }
}

The allowMelonFilter() relies on ObjectInputFilter.allowFilter() to allow only objects that are instances of Melon or subclasses of Melon. The rejectMuskmelonFilter() relies on ObjectInputFilter.rejectFilter() to reject all objects that are instances of Muskmelon or subclasses of Muskmelon.We can use each of these filters as you already know from the previous problems, so let’s tackle another use case. Let’s assume that we have the following hierarchy of classes:

Figure 6.1 – An arbitrary hierarchy of classes

Let’s assume that we want to deserialize only objects that are instances of Melon or subclasses of Melon but they are not Muskmelon or subclasses of Muskmelon. In other words, we allow deserializing instances of Melon and Cantaloupe.If we apply the allowMelonFilter() then we will deserialize instances of Melon, Muskmelon, Cantaloupe, HoneyDew, and Persian since all these are Melon.On the other hand, if we apply rejectMuskmelonFilter() then we will deserialize instances of Melon, Cantaloupe, and Pumpkin since these are not Muskmelon.But, if we apply rejectMuskmelonFilter() after applying allowMelonFilter() then we will deserialize only Melon and Cantaloupe, which is exactly what we want.Intuitively, we may think to chain our filters by writing something like this (ois is the current ObjectInputStream):

ois.setObjectInputFilter(Filters.allowMelonFilter());
ois.setObjectInputFilter(Filters.rejectMuskmelonFilter());

But, this will not work! It will cause an java.lang.IllegalStateException: filter cannot be set more than once.The solution relies on ObjectInputFilter.merge (filter, anotherFilter) which returns a filter that merges the status of these two filters by applying the following logic:

Call filter and get the returned status If the returned status is REJECTED then return it Call anotherFilter and get the returned otherStatus If anotherStatus is REJECTED then return it If either status or otherStatus is ALLOWED then return ALLOWED Otherwise, return UNDECIDED

Nevertheless, if anotherFilter is null, the filter is returned.Based on this logic, we can merge the status of Filters.allowMelonFilter() with the status of Filters.rejectMuskmelonFilter() as follows:

public static Object bytesToObject(byte[] bytes,
       ObjectInputFilter allowFilter,
       ObjectInputFilter rejectFilter)
       throws IOException, ClassNotFoundException {
 try ( InputStream is = new ByteArrayInputStream(bytes);
  ObjectInputStream ois = new ObjectInputStream(is)) {
  // set the filters
  ObjectInputFilter filters = ObjectInputFilter.merge(
     allowFilter, rejectFilter);
  ois.setObjectInputFilter(filters);
          
  return ois.readObject();
 }
}

Next, let’s talk about JDK 17, Filter Factories.

Leave a Reply

Your email address will not be published. Required fields are marked *