com.flipkart.hbaseobjectmapper.codec.BestSuitCodec Maven / Gradle / Ivy
package com.flipkart.hbaseobjectmapper.codec;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.hbaseobjectmapper.Flag;
import com.flipkart.hbaseobjectmapper.codec.exceptions.DeserializationException;
import com.flipkart.hbaseobjectmapper.codec.exceptions.SerializationException;
import com.flipkart.hbaseobjectmapper.exceptions.BadHBaseLibStateException;
import com.google.common.collect.ImmutableMap;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* This is an implementation of {@link Codec} that:
*
* - uses HBase's native methods to serialize objects of data types {@link Boolean}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link String} and {@link BigDecimal}
* - uses Jackson's JSON serializer for all other data types
* - serializes
null
as null
*
*
* This codec takes the following {@link Flag Flag}s:
*
* {@link #SERIALIZE_AS_STRING}
: When this flag is "true", this codec stores field/rowkey values in it's string representation (e.g. 560034 is serialized into a byte[]
that represents the string "560034"). This flag applies only to fields or rowkeys of data types in point 1 above.
*
*
* This is the default codec for {@link com.flipkart.hbaseobjectmapper.HBObjectMapper HBObjectMapper}.
*/
public class BestSuitCodec implements Codec {
public static final String SERIALIZE_AS_STRING = "serializeAsString";
private static final Map fromBytesMethodNames = ImmutableMap.builder()
.put(Boolean.class, "toBoolean")
.put(Short.class, "toShort")
.put(Integer.class, "toInt")
.put(Long.class, "toLong")
.put(Float.class, "toFloat")
.put(Double.class, "toDouble")
.put(String.class, "toString")
.put(BigDecimal.class, "toBigDecimal")
.build();
private static final Map nativeCounterParts = ImmutableMap.builder()
.put(Boolean.class, boolean.class)
.put(Short.class, short.class)
.put(Long.class, long.class)
.put(Integer.class, int.class)
.put(Float.class, float.class)
.put(Double.class, double.class)
.build();
private static final Map fromBytesMethods, toBytesMethods;
private static final Map constructors;
static {
try {
fromBytesMethods = new HashMap<>(fromBytesMethodNames.size());
toBytesMethods = new HashMap<>(fromBytesMethodNames.size());
constructors = new HashMap<>(fromBytesMethodNames.size());
for (Map.Entry e : fromBytesMethodNames.entrySet()) {
Class> clazz = e.getKey();
String toDataTypeMethodName = e.getValue();
Method fromBytesMethod = Bytes.class.getDeclaredMethod(toDataTypeMethodName, byte[].class);
Method toBytesMethod = Bytes.class.getDeclaredMethod("toBytes", nativeCounterParts.containsKey(clazz) ? nativeCounterParts.get(clazz) : clazz);
Constructor> constructor = clazz.getConstructor(String.class);
fromBytesMethods.put(clazz, fromBytesMethod);
toBytesMethods.put(clazz, toBytesMethod);
constructors.put(clazz, constructor);
}
} catch (Exception ex) {
throw new BadHBaseLibStateException(ex);
}
}
private final ObjectMapper objectMapper;
/**
* Construct an object of class {@link BestSuitCodec} with custom instance of Jackson's Object Mapper
*
* @param objectMapper Instance of Jackson's Object Mapper
*/
@SuppressWarnings("WeakerAccess")
public BestSuitCodec(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* Construct an object of class {@link BestSuitCodec}
*/
public BestSuitCodec() {
this(getObjectMapper());
}
private static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
/*
* @inherit
*/
@Override
public byte[] serialize(Serializable object, Map flags) throws SerializationException {
if (object == null)
return null;
Class clazz = object.getClass();
if (toBytesMethods.containsKey(clazz)) {
boolean serializeAsString = isSerializeAsStringTrue(flags);
try {
Method toBytesMethod = toBytesMethods.get(clazz);
return serializeAsString ? Bytes.toBytes(String.valueOf(object)) : (byte[]) toBytesMethod.invoke(null, object);
} catch (Exception e) {
throw new SerializationException(String.format("Could not serialize value of type %s using HBase's native methods", clazz.getName()), e);
}
} else {
try {
return objectMapper.writeValueAsBytes(object);
} catch (Exception e) {
throw new SerializationException("Could not serialize object to JSON using Jackson", e);
}
}
}
/*
* @inherit
*/
@Override
public Serializable deserialize(byte[] bytes, Type type, Map flags) throws DeserializationException {
if (bytes == null)
return null;
if (type instanceof Class && fromBytesMethods.containsKey(type)) {
boolean serializeAsString = isSerializeAsStringTrue(flags);
try {
Serializable value;
if (serializeAsString) {
Constructor constructor = constructors.get(type);
value = (Serializable) constructor.newInstance(Bytes.toString(bytes));
} else {
Method method = fromBytesMethods.get(type);
value = (Serializable) method.invoke(null, new Object[]{bytes});
}
return value;
} catch (Exception e) {
throw new DeserializationException("Could not deserialize byte array into an object using HBase's native methods", e);
}
} else {
JavaType javaType = null;
try {
javaType = objectMapper.constructType(type);
return objectMapper.readValue(bytes, javaType);
} catch (Exception e) {
throw new DeserializationException(String.format("Could not deserialize JSON into an object of type %s using Jackson%n(Jackson resolved type = %s)", type, javaType), e);
}
}
}
/*
* @inherit
*/
@Override
public boolean canDeserialize(Type type) {
JavaType javaType = objectMapper.constructType(type);
return objectMapper.canDeserialize(javaType);
}
private static boolean isSerializeAsStringTrue(Map flags) {
return flags != null && flags.get(SERIALIZE_AS_STRING) != null && flags.get(SERIALIZE_AS_STRING).equalsIgnoreCase("true");
}
}