
com.couchbase.client.java.transcoder.LegacyTranscoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-client Show documentation
Show all versions of java-client Show documentation
The official Couchbase Java SDK
/**
* Copyright (C) 2014 Couchbase, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
* IN THE SOFTWARE.
*/
package com.couchbase.client.java.transcoder;
import com.couchbase.client.core.lang.Tuple;
import com.couchbase.client.core.lang.Tuple2;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.core.message.kv.MutationToken;
import com.couchbase.client.deps.io.netty.buffer.ByteBuf;
import com.couchbase.client.deps.io.netty.buffer.Unpooled;
import com.couchbase.client.deps.io.netty.util.CharsetUtil;
import com.couchbase.client.java.document.LegacyDocument;
import java.io.*;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* A {@link Transcoder} which mimics the behavior of the Java SDK 1.* series for compatibility.
*
* @author Michael Nitschinger
* @since 2.0
*/
public class LegacyTranscoder extends AbstractTranscoder {
/**
* The logger used.
*/
private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(LegacyTranscoder.class);
public static final int DEFAULT_COMPRESSION_THRESHOLD = 16384;
// General flags
static final int SERIALIZED = 1;
static final int COMPRESSED = 2;
// Special flags for specially handled types.
private static final int SPECIAL_MASK = 0xff00;
static final int SPECIAL_BOOLEAN = (1 << 8);
static final int SPECIAL_INT = (2 << 8);
static final int SPECIAL_LONG = (3 << 8);
static final int SPECIAL_DATE = (4 << 8);
static final int SPECIAL_BYTE = (5 << 8);
static final int SPECIAL_FLOAT = (6 << 8);
static final int SPECIAL_DOUBLE = (7 << 8);
static final int SPECIAL_BYTEARRAY = (8 << 8);
private final int compressionThreshold;
public LegacyTranscoder() {
this(DEFAULT_COMPRESSION_THRESHOLD);
}
public LegacyTranscoder(int compressionThreshold) {
this.compressionThreshold = compressionThreshold;
}
@Override
public Class documentType() {
return LegacyDocument.class;
}
@Override
protected LegacyDocument doDecode(String id, ByteBuf content, long cas, int expiry, int flags, ResponseStatus status)
throws Exception {
byte[] data = new byte[content.readableBytes()];
content.readBytes(data);
Object decoded = null;
if ((flags & COMPRESSED) != 0) {
data = decompress(data);
}
int maskedFlags = flags & SPECIAL_MASK;
if ((flags & SERIALIZED) != 0 && data != null) {
decoded = deserialize(data);
} else if (maskedFlags != 0 && data != null) {
switch(maskedFlags) {
case SPECIAL_BOOLEAN:
decoded = data[0] == '1';
break;
case SPECIAL_INT:
decoded = (int) decodeLong(data);
break;
case SPECIAL_LONG:
decoded = decodeLong(data);
break;
case SPECIAL_DATE:
decoded = new Date(decodeLong(data));
break;
case SPECIAL_BYTE:
decoded = data[0];
break;
case SPECIAL_FLOAT:
decoded = Float.intBitsToFloat((int) decodeLong(data));
break;
case SPECIAL_DOUBLE:
decoded = Double.longBitsToDouble(decodeLong(data));
break;
case SPECIAL_BYTEARRAY:
decoded = data;
break;
default:
LOGGER.warn("Undecodeable with flags %x", flags);
}
} else {
decoded = new String(data, CharsetUtil.UTF_8);
}
return newDocument(id, expiry, decoded, cas);
}
@Override
public LegacyDocument newDocument(String id, int expiry, Object content, long cas) {
return LegacyDocument.create(id, expiry, content, cas);
}
@Override
public LegacyDocument newDocument(String id, int expiry, Object content, long cas,
MutationToken mutationToken) {
return LegacyDocument.create(id, expiry, content, cas, mutationToken);
}
@Override
protected Tuple2 doEncode(LegacyDocument document)
throws Exception {
int flags = 0;
Object content = document.content();
ByteBuf encoded = Unpooled.buffer();
if (content instanceof String) {
encoded.writeBytes(((String) content).getBytes(CharsetUtil.UTF_8));
} else if (content instanceof Long) {
flags |= SPECIAL_LONG;
encoded.writeBytes(encodeNum((Long) content, 8));
} else if (content instanceof Integer) {
flags |= SPECIAL_INT;
encoded.writeBytes(encodeNum((Integer) content, 4));
} else if (content instanceof Boolean) {
flags |= SPECIAL_BOOLEAN;
boolean b = (Boolean) content;
encoded = Unpooled.buffer().writeByte(b ? '1' : '0');
} else if (content instanceof Date) {
flags |= SPECIAL_DATE;
encoded.writeBytes(encodeNum(((Date) content).getTime(), 8));
} else if (content instanceof Byte) {
flags |= SPECIAL_BYTE;
encoded.writeByte((Byte) content);
} else if (content instanceof Float) {
flags |= SPECIAL_FLOAT;
encoded.writeBytes(encodeNum(Float.floatToRawIntBits((Float) content), 4));
} else if (content instanceof Double) {
flags |= SPECIAL_DOUBLE;
encoded.writeBytes(encodeNum(Double.doubleToRawLongBits((Double) content), 8));
} else if (content instanceof byte[]) {
flags |= SPECIAL_BYTEARRAY;
encoded.writeBytes((byte[]) content);
} else {
flags |= SERIALIZED;
encoded.writeBytes(serialize(content));
}
if (encoded.readableBytes() >= compressionThreshold) {
byte[] compressed = compress(encoded.copy().array());
if (compressed.length < encoded.array().length) {
encoded.clear().writeBytes(compressed);
flags |= COMPRESSED;
}
}
return Tuple.create(encoded, flags);
}
public static byte[] encodeNum(long l, int maxBytes) {
byte[] rv = new byte[maxBytes];
for (int i = 0; i < rv.length; i++) {
int pos = rv.length - i - 1;
rv[pos] = (byte) ((l >> (8 * i)) & 0xff);
}
int firstNon0 = 0;
// Just looking for what we can reduce
while (firstNon0 < rv.length && rv[firstNon0] == 0) {
firstNon0++;
}
if (firstNon0 > 0) {
byte[] tmp = new byte[rv.length - firstNon0];
System.arraycopy(rv, firstNon0, tmp, 0, rv.length - firstNon0);
rv = tmp;
}
return rv;
}
public static long decodeLong(byte[] b) {
long rv = 0;
for (byte i : b) {
rv = (rv << 8) | (i < 0 ? 256 + i : i);
}
return rv;
}
private static byte[] serialize(final Object content) {
if (content == null) {
throw new NullPointerException("Can't serialize null");
}
byte[] rv=null;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
try {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
os.writeObject(content);
os.close();
bos.close();
rv = bos.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException("Non-serializable object", e);
} finally {
try {
if (os != null) {
os.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close output stream.", ex);
}
try {
if (bos != null) {
bos.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close byte output stream.", ex);
}
}
return rv;
}
protected Object deserialize(byte[] in) {
Object rv=null;
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
try {
if(in != null) {
bis=new ByteArrayInputStream(in);
is=new ObjectInputStream(bis);
rv=is.readObject();
is.close();
bis.close();
}
} catch (IOException e) {
LOGGER.warn("Caught IOException decoding %d bytes of data",
in == null ? 0 : in.length, e);
} catch (ClassNotFoundException e) {
LOGGER.warn("Caught CNFE decoding %d bytes of data",
in == null ? 0 : in.length, e);
} finally {
try {
if (is != null) {
is.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close input stream.", ex);
}
try {
if (bis != null) {
bis.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close byte input stream.", ex);
}
}
return rv;
}
protected byte[] compress(byte[] in) {
if (in == null) {
throw new NullPointerException("Can't compress null");
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gz = null;
try {
gz = new GZIPOutputStream(bos);
gz.write(in);
} catch (IOException e) {
throw new RuntimeException("IO exception compressing data", e);
} finally {
try {
if (gz != null) {
gz.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close gzip output stream.", ex);
}
try {
bos.close();
} catch(Exception ex) {
LOGGER.error("Could not close byte output stream.", ex);
}
}
return bos.toByteArray();
}
protected byte[] decompress(byte[] in) {
ByteArrayOutputStream bos = null;
if(in != null) {
ByteArrayInputStream bis = new ByteArrayInputStream(in);
bos = new ByteArrayOutputStream();
GZIPInputStream gis = null;
try {
gis = new GZIPInputStream(bis);
byte[] buf = new byte[8192];
int r = -1;
while ((r = gis.read(buf)) > 0) {
bos.write(buf, 0, r);
}
} catch (IOException e) {
LOGGER.error("Could not decompress data.", e);
bos = null;
} finally {
try {
if (bos != null) {
bos.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close byte output stream.", ex);
}
try {
if (gis != null) {
gis.close();
}
} catch(Exception ex) {
LOGGER.error("Could not close gzip input stream.", ex);
}
try {
bis.close();
} catch(Exception ex) {
LOGGER.error("Could not close byte input stream.", ex);
}
}
}
return bos == null ? null : bos.toByteArray();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy