package com.lambdaworks.redis.codec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.InflaterInputStream;
import com.lambdaworks.redis.internal.LettuceAssert;
/**
* A compressing/decompressing {@link RedisCodec} that wraps a typed {@link RedisCodec codec} and compresses values using GZIP
* or Deflate. See {@link com.lambdaworks.redis.codec.CompressionCodec.CompressionType} for supported compression types.
*
* @author Mark Paluch
*/
public class CompressionCodec {
/**
* A {@link RedisCodec} that compresses values from a delegating {@link RedisCodec}.
*
* @param delegate codec used for key-value encoding/decoding, must not be {@literal null}.
* @param compressionType the compression type, must not be {@literal null}.
* @param <K> Key type.
* @param <V> Value type.
* @return Value-compressing codec.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <K, V> RedisCodec<K, V> valueCompressor(RedisCodec<K, V> delegate, CompressionType compressionType) {
LettuceAssert.notNull(delegate, "RedisCodec must not be null");
LettuceAssert.notNull(compressionType, "CompressionType must not be null");
return (RedisCodec) new CompressingValueCodecWrapper((RedisCodec) delegate, compressionType);
}
private static class CompressingValueCodecWrapper implements RedisCodec<Object, Object> {
private RedisCodec<Object, Object> delegate;
private CompressionType compressionType;
public CompressingValueCodecWrapper(RedisCodec<Object, Object> delegate, CompressionType compressionType) {
this.delegate = delegate;
this.compressionType = compressionType;
}
@Override
public Object decodeKey(ByteBuffer bytes) {
return delegate.decodeKey(bytes);
}
@Override
public Object decodeValue(ByteBuffer bytes) {
try {
return delegate.decodeValue(decompress(bytes));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public ByteBuffer encodeKey(Object key) {
return delegate.encodeKey(key);
}
@Override
public ByteBuffer encodeValue(Object value) {
try {
return compress(delegate.encodeValue(value));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private ByteBuffer compress(ByteBuffer source) throws IOException {
if (source.remaining() == 0) {
return source;
}
ByteBufferInputStream sourceStream = new ByteBufferInputStream(source);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(source.remaining() / 2);
OutputStream compressor = null;
if (compressionType == CompressionType.GZIP) {
compressor = new GZIPOutputStream(outputStream);
}
if (compressionType == CompressionType.DEFLATE) {
compressor = new DeflaterOutputStream(outputStream);
}
try {
copy(sourceStream, compressor);
} finally {
compressor.close();
}
return ByteBuffer.wrap(outputStream.toByteArray());
}
private ByteBuffer decompress(ByteBuffer source) throws IOException {
if (source.remaining() == 0) {
return source;
}
ByteBufferInputStream sourceStream = new ByteBufferInputStream(source);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(source.remaining() * 2);
InputStream decompressor = null;
if (compressionType == CompressionType.GZIP) {
decompressor = new GZIPInputStream(sourceStream);
}
if (compressionType == CompressionType.DEFLATE) {
decompressor = new InflaterInputStream(sourceStream);
}
try {
copy(decompressor, outputStream);
} finally {
decompressor.close();
}
return ByteBuffer.wrap(outputStream.toByteArray());
}
}
/**
* Copies all bytes from the input stream to the output stream. Does not close or flush either stream.
*
* @param from the input stream to read from
* @param to the output stream to write to
* @return the number of bytes copied
* @throws IOException if an I/O error occurs
*/
private static long copy(InputStream from, OutputStream to) throws IOException {
LettuceAssert.notNull(from, "From must not be null");
LettuceAssert.notNull(to, "From must not be null");
byte[] buf = new byte[4096];
long total = 0;
while (true) {
int r = from.read(buf);
if (r == -1) {
break;
}
to.write(buf, 0, r);
total += r;
}
return total;
}
public enum CompressionType {
GZIP, DEFLATE;
}
}