自定义过滤器

如在过滤页面中所解释的,FiltersManager.applyRelevantFilters方法通过循环遍历所有注册的DataFilter实例,并调用它们的filter方法来处理当前的DataSource。如果filter方法返回的是传递给它的完全相同的实例,则表示该过滤器不起作用。在这种情况下,FiltersManager.applyRelevantFilters方法会继续循环并检查下一个过滤器。如果filter方法返回的是与传递给它的不同的DataSource实例,则表示该过滤器确实对数据流起作用。在这种情况下,FiltersManager.applyRelevantFilters方法将当前的DataSource设置为返回的值,并从头重新开始循环。

该算法允许同一个过滤器在需要时被多次应用,并且它还允许过滤器以任意顺序应用,而不受它们在FiltersManager中注册的顺序的影响。

用户可以利用这个通用功能来添加自己的过滤器。一个例子是,如果敏感数据应该以加密形式存储,并且在加载数据时需要解密,则可以使用解密算法作为过滤器。

实现过滤器

根据applyRelevantFilters方法的工作方式,filter方法必须以这样的方式实现:它应该检查传递给它的DataSource,如果它认为不应该对其进行过滤,则返回其参数,如果它认为应该对其进行过滤,则返回一个新的DataSource

在实现自定义过滤器时,有一个重要的注意事项:过滤器绝不能自己打开DataSource,无论它们在调用其filter方法时返回原始实例还是过滤后的实例。原因是上层将决定是否打开返回的值,而且DataSource只能打开一次;这是DataSource提供的延迟打开的核心原则。

这个注意事项的一个结果是,过滤器不能查看由DataSource引用的数据流的几个字节,例如尝试在头部查找魔数。这就是为什么例如GzipFilter在名称中查找.gz后缀而不是在文件开头查找0x1f8B魔数的原因。

由于applyRelevantFilters在每次向堆栈添加过滤器时都会从头开始重新启动循环,因此必须小心避免在同一个过滤器的无限数量实例之上堆叠。这意味着在过滤后返回的过滤DataSource应该被识别为已经过滤,并且不再被同一个过滤器匹配。如果检查是基于文件名扩展名的(例如对于gzip压缩文件的.gz),那么如果原始DataSource的名称形式为base.ext.gz,那么过滤后的文件应该具有base.ext的名称形式。另一个要点是,如果过滤器不对DataSource进行操作,则它必须返回传递给它的相同实例,它不能简单地创建一个透明的过滤器,只是传递名称和数据流而不做任何更改,否则它将被视为有效的过滤器,并且会一次又一次地添加,直到发生堆栈溢出或内存耗尽异常。

过滤部分本身是通过打开来自底层原始DataSource的数据流,从中读取原始数据,对这些数据进行处理(解压缩、解密等),并将它们作为另一个流返回来实现的。

以下示例展示了如何为基于简单异或的虚拟解密算法实现过滤器(这只是一个玩具示例,不打算提供任何安全性)。

public class XorFilter implements DataFilter {

    /** XOR加密文件的后缀。 */
    private static final String SUFFIX = ".xor";

    /** 高度保密的密钥。 */
    private static final int key = 0x3b;

    /** {@inheritDoc} */
    @Override
    public DataSource filter(final DataSource original) {
        final String            oName   = original.getName();
        final DataSource.Opener oOpener = original.getOpener();
        if (oName.endsWith(SUFFIX)) {
            final String                  fName   = oName.substring(0, oName.length() - SUFFIX.length());
            final DataSource.StreamOpener fOpener = () -> new XORInputStream(oName, oOpener.openStreamOnce());
            return new DataSource(fName, fOpener);
        } else {
            return original;
        }
    }

    /** XOR加密流的过滤器。 */
    private static class XORInputStream extends InputStream {

        /** 文件名。 */
        private final String name;

        /** 底层压缩流。 */
        private final InputStream input;

        /** 输入结束指示器。 */
        private boolean endOfInput;

        /** 简单构造函数。
         * @param name 文件名
         * @param input 底层压缩流
         */
        XORInputStream(final String name, final InputStream input) {
            this.name       = name;
            this.input      = input;
            this.endOfInput = false;
        }

        /** {@inheritDoc} */
        @Override
        public int read() throws IOException {

            if (endOfInput) {
                // 已经到达数据末尾
                return -1;
            }

            final int raw = input.read();
            if (raw < 0) {
              endOfInput = true;
              return -1;
            } else {
              return raw ^ key;
            }

        }

    }

}