package voldemort.serialization.thrift;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TJSONProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TSimpleJSONProtocol;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import voldemort.serialization.SerializationException;
import voldemort.serialization.Serializer;
/**
* ThriftSerializer uses one of the Thrift protocols (binary, json and
* simple-json) to serialize and deserialize a Thrift generated object.
* <p>
*
* The following is a sample configuration for value-serializer:
*
* <pre>
* <value-serializer>
* <type>thrift</type>
* <schema-info>java=com.linkedin.foobar.FooMessage,protocol=binary</schema-info>
* </value-serializer>
* </pre>
*
* Currently, only Java clients are supported. Once support for clients in other
* languages is available, a semi-colon separated list will be accepted for the
* schema-info element (one for each language).
*/
public class ThriftSerializer<T extends TBase<?, ?>> implements Serializer<T> {
private static final String ONLY_JAVA_CLIENTS_SUPPORTED = "Only Java clients are supported currently, so the format of the schema-info should be: <schema-info>java=com.xyz.Foo,protocol=binary</schema-info> where com.xyz.Foo is the fully qualified name of the message.";
/**
* Supported Thrift Protocols
*/
static enum ThriftProtocol {
BINARY,
JSON,
SIMPLE_JSON,
UNKNOWN
}
private Class<T> messageClass;
private ThriftProtocol protocol;
@SuppressWarnings("unchecked")
public ThriftSerializer(String schemaInfo) {
String[] thriftInfo = parseSchemaInfo(schemaInfo);
if(thriftInfo[1] == null || thriftInfo[1].length() == 0) {
throw new IllegalArgumentException("Thrift protocol is missing from schema-info.");
}
this.protocol = getThriftProtocol(thriftInfo[1]);
if(this.protocol == ThriftProtocol.UNKNOWN) {
throw new IllegalArgumentException("Unknown Thrift protocol found in schema-info");
}
if(thriftInfo[0] == null || thriftInfo[0].length() == 0) {
throw new IllegalArgumentException("Thrift generated class name is missing from schema-info.");
}
try {
this.messageClass = (Class<T>) Class.forName(thriftInfo[0]);
Object msgObj = messageClass.newInstance();
if(!(msgObj instanceof TBase)) {
throw new IllegalArgumentException(thriftInfo[0]
+ " is not a subtype of com.facebook.thrift.TBase");
}
} catch(ClassNotFoundException e) {
throw new IllegalArgumentException(e);
} catch(SecurityException e) {
throw new IllegalArgumentException(e);
} catch(InstantiationException e) {
throw new IllegalArgumentException(e);
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
}
public byte[] toBytes(T object) {
MemoryBuffer buffer = new MemoryBuffer();
TProtocol protocol = createThriftProtocol(buffer);
try {
object.write(protocol);
} catch(TException e) {
throw new SerializationException(e);
}
return buffer.toByteArray();
}
public T toObject(byte[] bytes) {
MemoryBuffer buffer = new MemoryBuffer();
try {
buffer.write(bytes);
} catch(TTransportException e) {
throw new SerializationException(e);
}
TProtocol protocol = createThriftProtocol(buffer);
T msg = null;
try {
msg = messageClass.newInstance();
msg.read(protocol);
} catch(InstantiationException e) {
throw new SerializationException(e);
} catch(IllegalAccessException e) {
throw new SerializationException(e);
} catch(TException e) {
throw new SerializationException(e);
}
return msg;
}
protected String[] parseSchemaInfo(String schemaInfo) {
String[] thriftInfo = new String[2];
String javaToken = null;
String[] tokens = schemaInfo.split(";");
for(int i = 0; i < tokens.length; i++) {
if(tokens[i].trim().startsWith("java")) {
javaToken = tokens[i];
break;
}
}
if(javaToken == null) {
throw new IllegalArgumentException(ONLY_JAVA_CLIENTS_SUPPORTED);
}
tokens = javaToken.split(",");
for(int i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].trim();
if(tokens[i].startsWith("java=")) {
thriftInfo[0] = tokens[i].substring("java=".length());
} else if(tokens[i].startsWith("protocol=")) {
thriftInfo[1] = tokens[i].substring("protocol=".length());
}
}
return thriftInfo;
}
protected ThriftProtocol getThriftProtocol(String protocolStr) {
if(protocolStr.equalsIgnoreCase("binary")) {
return ThriftProtocol.BINARY;
} else if(protocolStr.equalsIgnoreCase("json")) {
return ThriftProtocol.JSON;
} else if(protocolStr.equalsIgnoreCase("simple-json")) {
return ThriftProtocol.SIMPLE_JSON;
} else {
return ThriftProtocol.UNKNOWN;
}
}
protected TProtocol createThriftProtocol(TTransport transport) {
switch(this.protocol) {
case BINARY:
return new TBinaryProtocol(transport);
case JSON:
return new TJSONProtocol(transport);
case SIMPLE_JSON:
return new TSimpleJSONProtocol(transport);
default:
throw new IllegalArgumentException("Unknown Thrift Protocol.");
}
}
}