All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kr.pe.kwonnam.hibernate4memcached.spymemcached.KryoTranscoder Maven / Gradle / Ivy

package kr.pe.kwonnam.hibernate4memcached.spymemcached;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer;
import de.javakaffee.kryoserializers.KryoReflectionFactorySupport;
import kr.pe.kwonnam.hibernate4memcached.util.IntToBytesUtils;
import kr.pe.kwonnam.hibernate4memcached.util.Lz4CompressUtils;
import kr.pe.kwonnam.hibernate4memcached.util.OverridableReadOnlyProperties;
import net.spy.memcached.CachedData;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * Default transcoder for {@link SpyMemcachedAdapter}.
 *
 * This transcoder uses Kryo Serializer and compress data with Lz4 when data size is greater than compression
 * threashold.
 *
 * @author KwonNam Son ([email protected])
 */
public class KryoTranscoder implements InitializableTranscoder {
    private Logger log = LoggerFactory.getLogger(KryoTranscoder.class);

    public static final String COMPRESSION_THREASHOLD_PROPERTY_KEY = SpyMemcachedAdapter.PROPERTY_KEY_PREFIX + ".kryotranscoder.compression.threashold.bytes";

    public static final int BASE_FLAG = 0;

    public static final int COMPRESS_FLAG = 4; // 0b0100

    public static final int DEFAULT_BUFFER_SIZE = 1024 * 20; // 20kb

    public static final int DECOMPRESSED_SIZE_STORE_BYTES_LENGTH = 4;

    private OverridableReadOnlyProperties properties;

    private int bufferSize = DEFAULT_BUFFER_SIZE;

    private int compressionThreasholdBytes;

    @Override
    public void init(OverridableReadOnlyProperties properties) {
        this.properties = properties;

        String compressionThreasholdBytesProperty = properties.getRequiredProperty(COMPRESSION_THREASHOLD_PROPERTY_KEY);
        compressionThreasholdBytes = Integer.parseInt(compressionThreasholdBytesProperty);
    }

    @Override
    public boolean asyncDecode(CachedData d) {
        return false;
    }

    /**
     * Override this method to change serialization rule.
     *
     * @return Kryo serializer instance.
     */
    protected Kryo createKryo() {
        Kryo kryo = new KryoReflectionFactorySupport();
        kryo.setDefaultSerializer(CompatibleFieldSerializer.class);
        return kryo;
    }

    @Override
    public CachedData encode(Object object) {
        int flags = BASE_FLAG;

        byte[] encodedBytes = kryoEncode(object);

        boolean compressionRequired = encodedBytes.length > compressionThreasholdBytes;

        if (compressionRequired) {
            int beforeSize = encodedBytes.length;
            encodedBytes = compress(encodedBytes);

            log.debug("kryotranscoder compress required : {}, original {} bytes -> compressed {} bytes", compressionRequired, beforeSize, encodedBytes.length);
            flags = flags | COMPRESS_FLAG;
        }
        return new CachedData(flags, encodedBytes, getMaxSize());
    }

    private byte[] kryoEncode(Object o) {
        Kryo kryo = createKryo();

        Output output = new Output(bufferSize, getMaxSize());
        kryo.writeClassAndObject(output, o);
        return output.toBytes();
    }

    byte[] compress(byte[] encodedBytes) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(encodedBytes.length);
        try {
            baos.write(IntToBytesUtils.intToBytes(encodedBytes.length));

            byte[] compressedBytes = Lz4CompressUtils.compress(encodedBytes);
            baos.write(compressedBytes);
        } catch (IOException e) {
            throw new IllegalStateException("Failed do compress kryo serialized data.", e);
        }
        return baos.toByteArray();
    }

    @Override
    public Object decode(CachedData data) {
        int flags = data.getFlags();

        byte[] decodedBytes = data.getData();

        boolean compressed = (flags & COMPRESS_FLAG) > 0;

        if (compressed) {
            decodedBytes = decompress(decodedBytes);
        }

        Kryo kryo = createKryo();
        return kryo.readClassAndObject(new Input(decodedBytes));
    }

    private byte[] decompress(byte[] decodedBytes) {
        int decompressedSize = IntToBytesUtils.bytesToInt(ArrayUtils.subarray(decodedBytes, 0, DECOMPRESSED_SIZE_STORE_BYTES_LENGTH));

        return Lz4CompressUtils.decompressFast(decodedBytes, DECOMPRESSED_SIZE_STORE_BYTES_LENGTH, decompressedSize);
    }

    @Override
    public int getMaxSize() {
        return CachedData.MAX_SIZE;
    }
}