package com.oculusinfo.binning.io.serialization.impl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.*; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.oculusinfo.binning.TileData; import com.oculusinfo.binning.TileIndex; import com.oculusinfo.binning.impl.DenseTileData; import com.oculusinfo.binning.impl.SparseTileData; import com.oculusinfo.binning.io.serialization.TileSerializer; import com.oculusinfo.binning.util.TypeDescriptor; import com.oculusinfo.factory.util.Pair; /** * A generic Kryo serializer capable of serializing all sorts of tile data. * * @author nkronenfeld * * @param <T> The type of data this instance of the serializer intends to serialize. */ public class KryoSerializer<T> implements TileSerializer<T> { private static final long serialVersionUID = 611839716702420914L; public static enum Codec {DEFLATE, BZIP, GZIP}; public static final Set<Class<?>> PRIMITIVE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>() { private static final long serialVersionUID = 1L; { addAll(PrimitiveAvroSerializer.PRIMITIVE_TYPES); add(Short.class); } }); // Store a kryo instance per thread transient private LocalizedKryo _localKryo; // A list of classes each kryo instance must register private Class<?>[] _classesToRegister; // Our type description private TypeDescriptor _typeDesc; private Codec _codec; /** * Create a serializer. * * @param typeDesc * A detailed description of our fully expanded generic type. * This should, of course, match the generic type with which the * class was created; there is no way of enforcing this, but * violating it will cause a host of problems. */ public KryoSerializer (TypeDescriptor typeDesc, Class<?>... classesToRegister) { this(typeDesc, Codec.GZIP, classesToRegister); } public KryoSerializer (TypeDescriptor typeDesc, Codec codec, Class<?>... classesToRegister) { _typeDesc = typeDesc; _codec = codec; _classesToRegister = classesToRegister; } // Get the kryo instance for this thread. private Kryo kryo () { if (null == _localKryo) _localKryo = new LocalizedKryo(); return _localKryo.get(); } @Override public TypeDescriptor getBinTypeDescription() { return _typeDesc; } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public TileData<T> deserialize(TileIndex index, InputStream stream) throws IOException { InputStream compressionStream; switch (_codec) { case BZIP: compressionStream = new BZipInputStreamWrapper(new BZip2CompressorInputStream(stream)); break; case GZIP: compressionStream = new GzipCompressorInputStream(stream); break; case DEFLATE: default: compressionStream = new InflaterInputStream(stream); break; } Input input = new Input(compressionStream); try { Object data = kryo().readClassAndObject(input); if (data instanceof TileData) return (TileData) data; else return null; } finally { compressionStream.close(); input.close(); } } @Override public void serialize(TileData<T> data, OutputStream stream) throws IOException { OutputStream compressionStream; switch (_codec) { case BZIP: compressionStream = new BZip2CompressorOutputStream(stream); break; case GZIP: compressionStream = new GzipCompressorOutputStream(stream); break; case DEFLATE: default: compressionStream = new DeflaterOutputStream(stream); break; } Output output = new Output(compressionStream); try { kryo().writeClassAndObject(output, data); output.flush(); compressionStream.flush(); } finally { output.close(); compressionStream.close(); } } private class LocalizedKryo extends ThreadLocal<Kryo> { protected Kryo initialValue () { Kryo kryo = new Kryo(); kryo.register(TileIndex.class); kryo.register(DenseTileData.class); kryo.register(SparseTileData.class); // Standard collection types kryo.register(java.util.ArrayDeque.class); kryo.register(java.util.ArrayList.class); kryo.register(java.util.BitSet.class); kryo.register(java.util.HashMap.class); kryo.register(java.util.HashSet.class); kryo.register(java.util.Hashtable.class); kryo.register(java.util.IdentityHashMap.class); kryo.register(java.util.LinkedHashMap.class); kryo.register(java.util.LinkedHashSet.class); kryo.register(java.util.PriorityQueue.class); kryo.register(java.util.TreeMap.class); kryo.register(java.util.TreeSet.class); kryo.register(java.util.Vector.class); // Our own standard types kryo.register(Pair.class); // And our custom classes for (Class<?> ctr: _classesToRegister) { kryo.register(ctr); } return kryo; } } /** * This is just a quick wrapping InputStream to get around a bug in apache commons bzip * uncompression, where when it is passed a buffer into which to read, and told to offset by * the length of the buffer, it returns that the file is done, rather than that the buffer is * done. * * The bug is Commons Compress / COMPRESS-300 * (https://issues.apache.org/jira/browse/COMPRESS-309) * * @author nkronenfeld */ public static class BZipInputStreamWrapper extends InputStream { private InputStream _source; public BZipInputStreamWrapper (InputStream source) { _source = source; } // This is the one we care about, the one with the bug in apache commons bzip compression @Override public int read (byte[] buffer, int offset, int length) throws IOException { if (offset == buffer.length) { return 0; } return _source.read(buffer, offset, length); } // The rest are just pass-throughs to _source @Override public int available () throws IOException { return _source.available(); } @Override public void close () throws IOException { _source.close(); } @Override public synchronized void mark (int readlimit) { _source.mark(readlimit); } @Override public boolean markSupported () { return _source.markSupported(); } @Override public synchronized void reset () throws IOException { _source.reset(); } @Override public int read () throws IOException { return _source.read(); } @Override public int read (byte[] buffer) throws IOException { return _source.read(buffer); } @Override public long skip (long n) throws IOException { return _source.skip(n); } } }