/*
* JBoss, Home of Professional Open Source
* Copyright 2010 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.client.hotrod.marshall;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.util.Utf8;
import org.infinispan.CacheException;
import org.infinispan.io.ByteBuffer;
import org.infinispan.io.ExposedByteArrayOutputStream;
import org.infinispan.marshall.AbstractMarshaller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This is a portable serialization marshaller based on Apache Avro. It supports basic type and collection marshalling.
* Basic types include UTF-8 String, int long, float, double, boolean and null, and the collections supported include
* arrays, list, map and set composed of basic types.
*
* Primitive types short and byte are not supported per se. Instead, pass integers which will be encoded efficiently
* using variable-length (http://lucene.apache.org/java/2_4_0/fileformats.html#VInt) zig zag
* (http://code.google.com/apis/protocolbuffers/docs/encoding.html#types) coding.
*
* Primitive arrays not supported except byte arrays. Instead, use their object counter partners, i.e. Integer...etc.
*
* For more detailed information, go to: http://community.jboss.org/docs/DOC-15774
*
* @author Galder ZamarreƱo
* @since 5.0
*/
public class ApacheAvroMarshaller extends AbstractMarshaller {
private static final Schema STRING_SCHEMA = Schema.create(Schema.Type.STRING);
private static final Schema INT_SCHEMA = Schema.create(Schema.Type.INT);
private static final Schema LONG_SCHEMA = Schema.create(Schema.Type.LONG);
private static final Schema FLOAT_SCHEMA = Schema.create(Schema.Type.FLOAT);
private static final Schema DOUBLE_SCHEMA = Schema.create(Schema.Type.DOUBLE);
private static final Schema BOOLEAN_SCHEMA = Schema.create(Schema.Type.BOOLEAN);
private static final Schema BYTES_SCHEMA = Schema.create(Schema.Type.BYTES);
private static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL);
private static final Schema STRING_ARRAY_SCHEMA = Schema.createArray(STRING_SCHEMA);
private static final Schema INT_ARRAY_SCHEMA = Schema.createArray(INT_SCHEMA);
private static final Schema LONG_ARRAY_SCHEMA = Schema.createArray(LONG_SCHEMA);
private static final Schema FLOAT_ARRAY_SCHEMA = Schema.createArray(FLOAT_SCHEMA);
private static final Schema DOUBLE_ARRAY_SCHEMA = Schema.createArray(DOUBLE_SCHEMA);
private static final Schema BOOLEAN_ARRAY_SCHEMA = Schema.createArray(BOOLEAN_SCHEMA);
private static final MarshallableType STRING_TYPE = new StringMarshallableType(0);
private static final MarshallableType INT_TYPE = new MarshallableType(INT_SCHEMA, 1);
private static final MarshallableType LONG_TYPE = new MarshallableType(LONG_SCHEMA, 2);
private static final MarshallableType FLOAT_TYPE = new MarshallableType(FLOAT_SCHEMA, 3);
private static final MarshallableType DOUBLE_TYPE = new MarshallableType(DOUBLE_SCHEMA, 4);
private static final MarshallableType BOOLEAN_TYPE = new MarshallableType(BOOLEAN_SCHEMA, 5);
private static final MarshallableType BYTES_TYPE = new BytesMarshallableType(6);
private static final MarshallableType NULL_TYPE = new MarshallableType(NULL_SCHEMA, 7);
private static final MarshallableType STRING_ARRAY_TYPE = new StringArrayMarshallableType(8);
private static final MarshallableType INT_ARRAY_TYPE = new ArrayMarshallableType<Integer>(INT_ARRAY_SCHEMA, Integer.class, 9);
private static final MarshallableType LONG_ARRAY_TYPE = new ArrayMarshallableType<Long>(LONG_ARRAY_SCHEMA, Long.class, 10);
private static final MarshallableType DOUBLE_ARRAY_TYPE = new ArrayMarshallableType<Double>(DOUBLE_ARRAY_SCHEMA, Double.class, 11);
private static final MarshallableType FLOAT_ARRAY_TYPE = new ArrayMarshallableType<Float>(FLOAT_ARRAY_SCHEMA, Float.class, 12);
private static final MarshallableType BOOLEAN_ARRAY_TYPE = new ArrayMarshallableType<Boolean>(BOOLEAN_ARRAY_SCHEMA, Boolean.class, 13);
private final MarshallableType listType = new ListMarshallableType(14, this);
private final MarshallableType mapType = new MapMarshallableType(15, this);
private final MarshallableType setType = new SetMarshallableType(16, this);
private MarshallableType getType(int type) {
switch (type) {
case 0: return STRING_TYPE;
case 1: return INT_TYPE;
case 2: return LONG_TYPE;
case 3: return FLOAT_TYPE;
case 4: return DOUBLE_TYPE;
case 5: return BOOLEAN_TYPE;
case 6: return BYTES_TYPE;
case 7: return NULL_TYPE;
case 8: return STRING_ARRAY_TYPE;
case 9: return INT_ARRAY_TYPE;
case 10: return LONG_ARRAY_TYPE;
case 11: return DOUBLE_ARRAY_TYPE;
case 12: return FLOAT_ARRAY_TYPE;
case 13: return BOOLEAN_ARRAY_TYPE;
case 14: return listType;
case 15: return mapType;
case 16: return setType;
default: throw new CacheException("Unknown type " + type);
}
}
@Override
protected ByteBuffer objectToBuffer(Object o, int estimatedSize) throws IOException {
ExposedByteArrayOutputStream baos = new ExposedByteArrayOutputStream(estimatedSize);
Encoder encoder = new BinaryEncoder(baos);
objectToBuffer(o, encoder);
return new ByteBuffer(baos.getRawBuffer(), 0, baos.size());
}
private void objectToBuffer(Object o, Encoder encoder) throws IOException {
if (o == null) {
NULL_TYPE.write(o, encoder);
} else {
Class<?> clazz = o.getClass();
MarshallableType type;
if (clazz.equals(String.class)) type = STRING_TYPE;
else if (clazz.equals(byte[].class)) type = BYTES_TYPE;
else if (clazz.equals(Boolean.class)) type = BOOLEAN_TYPE;
else if (clazz.equals(Integer.class)) type = INT_TYPE;
else if (clazz.equals(Long.class)) type = LONG_TYPE;
else if (clazz.equals(Float.class)) type = FLOAT_TYPE;
else if (clazz.equals(Double.class)) type = DOUBLE_TYPE;
else if (clazz.equals(String[].class)) type = STRING_ARRAY_TYPE;
else if (clazz.equals(Integer[].class)) type = INT_ARRAY_TYPE;
else if (clazz.equals(Long[].class)) type = LONG_ARRAY_TYPE;
else if (clazz.equals(Float[].class)) type = FLOAT_ARRAY_TYPE;
else if (clazz.equals(Double[].class)) type = DOUBLE_ARRAY_TYPE;
else if (clazz.equals(Boolean[].class)) type = BOOLEAN_ARRAY_TYPE;
else if (o instanceof List) type = listType;
else if (o instanceof Map) type = mapType;
else if (o instanceof Set) type = setType;
else
throw new CacheException("Unsupported type: " + clazz);
type.write(o, encoder);
}
}
@Override
public Object objectFromByteBuffer(byte[] buf, int offset, int length) throws IOException {
DecoderFactory factory = new DecoderFactory(); // TODO: Could this be cached?
InputStream is = new ByteArrayInputStream(buf, offset, length);
Decoder decoder = factory.createBinaryDecoder(is, null);
return objectFromByteBuffer(decoder);
}
private Object objectFromByteBuffer(Decoder decoder) throws IOException {
int type = decoder.readInt();
return getType(type).read(decoder);
}
@Override
public boolean isMarshallable(Object o) {
Class<?> clazz = o.getClass();
return clazz.equals(String.class) || clazz.equals(byte[].class)
|| clazz.equals(Boolean.class) || clazz.equals(Integer.class)
|| clazz.equals(Long.class) || clazz.equals(Float.class)
|| clazz.equals(Double.class) || clazz.equals(String[].class)
|| clazz.equals(Integer[].class) || clazz.equals(Long[].class)
|| clazz.equals(Float[].class) || clazz.equals(Double[].class)
|| clazz.equals(Boolean[].class) || o instanceof List
|| o instanceof Map || o instanceof Set;
}
private static class MarshallableType {
final Schema schema;
final int id;
MarshallableType(Schema schema, int id) {
this.schema = schema;
this.id = id;
}
Object read(Decoder decoder) throws IOException {
return new GenericDatumReader(schema).read(null, decoder);
}
void write(Object o, Encoder encoder) throws IOException {
GenericDatumWriter<Object> writer = new GenericDatumWriter(schema); // TODO: Could this be cached? Maybe, but ctor is very cheap
encoder.writeInt(id);
write(writer, o, encoder);
}
void write(GenericDatumWriter<Object> writer, Object o, Encoder encoder) throws IOException {
writer.write(o, encoder);
}
}
private static class StringMarshallableType extends MarshallableType {
StringMarshallableType(int id) {
super(STRING_SCHEMA, id);
}
@Override
Object read(Decoder decoder) throws IOException {
return new GenericDatumReader(schema).read(null, decoder).toString();
}
@Override
void write(GenericDatumWriter<Object> writer, Object o, Encoder encoder) throws IOException {
writer.write(new Utf8((String) o), encoder);
}
}
private static class BytesMarshallableType extends MarshallableType {
BytesMarshallableType(int id) {
super(BYTES_SCHEMA, id);
}
@Override
Object read(Decoder decoder) throws IOException {
java.nio.ByteBuffer byteBuffer = (java.nio.ByteBuffer) new GenericDatumReader(schema).read(null, decoder);
byte[] bytes = new byte[byteBuffer.limit()]; // TODO: Limit or capacity ? Limit works
byteBuffer.get(bytes);
return bytes;
}
@Override
void write(GenericDatumWriter<Object> writer, Object o, Encoder encoder) throws IOException {
writer.write(java.nio.ByteBuffer.wrap((byte[]) o), encoder);
}
}
private static class StringArrayMarshallableType extends MarshallableType {
StringArrayMarshallableType(int id) {
super(STRING_ARRAY_SCHEMA, id);
}
@Override
Object read(Decoder decoder) throws IOException {
GenericData.Array<Utf8> utf8s = (GenericData.Array<Utf8>) new GenericDatumReader(schema).read(null, decoder);
List<String> strings = new ArrayList<String>((int) utf8s.size());
for (Utf8 utf8 : utf8s)
strings.add(utf8.toString());
return strings.toArray(new String[0]);
}
@Override
void write(GenericDatumWriter<Object> writer, Object o, Encoder encoder) throws IOException {
String[] strings = (String[]) o;
GenericData.Array<Utf8> array = new GenericData.Array(strings.length, schema);
for (String str : strings)
array.add(new Utf8(str));
writer.write(array, encoder);
}
}
private static class ArrayMarshallableType<T> extends MarshallableType {
private final Class<T> type;
ArrayMarshallableType(Schema schema, Class<T> type, int id) {
super(schema, id);
this.type = type;
}
@Override
Object read(Decoder decoder) throws IOException {
GenericData.Array<T> avroArray = (GenericData.Array<T>) new GenericDatumReader(schema).read(null, decoder);
List<T> list = new ArrayList<T>((int) avroArray.size());
for (T t : avroArray)
list.add(t);
T[] array = (T[]) Array.newInstance(type, list.size());
return toArray(list, array);
}
private <T> T[] toArray(List<T> list, T... ts) {
// Varargs hack!
return list.toArray(ts);
}
@Override
void write(GenericDatumWriter<Object> writer, Object o, Encoder encoder) throws IOException {
T[] array = (T[]) o;
GenericData.Array<T> avroArray = new GenericData.Array(array.length, schema);
for (T t : array)
avroArray.add(t);
writer.write(avroArray, encoder);
}
}
private static abstract class CollectionMarshallableType extends MarshallableType {
final ApacheAvroMarshaller marshaller;
CollectionMarshallableType(int id, ApacheAvroMarshaller marshaller) {
super(null, id);
this.marshaller = marshaller;
}
@Override
Object read(Decoder decoder) throws IOException {
long size = decoder.readArrayStart();
Collection<Object> collection = createCollection((int) size);
for (int k = 0; k < size; k++)
collection.add(marshaller.objectFromByteBuffer(decoder));
return collection;
}
@Override
void write(Object o, Encoder encoder) throws IOException {
Collection<Object> collection = (Collection<Object>) o;
encoder.writeInt(id);
encoder.setItemCount(collection.size());
for (Object element : collection)
marshaller.objectToBuffer(element, encoder);
}
abstract Collection<Object> createCollection(int size);
}
private static class ListMarshallableType extends CollectionMarshallableType {
ListMarshallableType(int id, ApacheAvroMarshaller marshaller) {
super(id, marshaller);
}
@Override
Collection<Object> createCollection(int size) {
return new ArrayList<Object>(size);
}
}
private static class MapMarshallableType extends CollectionMarshallableType {
MapMarshallableType(int id, ApacheAvroMarshaller marshaller) {
super(id, marshaller);
}
@Override
Object read(Decoder decoder) throws IOException {
long size = decoder.readArrayStart();
Map<Object, Object> map = new HashMap<Object, Object>((int) size);
for (int i = 0; i < size; i++)
map.put(marshaller.objectFromByteBuffer(decoder), marshaller.objectFromByteBuffer(decoder));
return map;
}
@Override
void write(Object o, Encoder encoder) throws IOException {
Map<Object, Object> map = (Map<Object, Object>) o;
encoder.writeInt(id);
encoder.setItemCount(map.size());
for (Map.Entry<Object, Object> entry : (Set<Map.Entry<Object, Object>>) map.entrySet()) {
marshaller.objectToBuffer(entry.getKey(), encoder);
marshaller.objectToBuffer(entry.getValue(), encoder);
}
}
@Override
Collection<Object> createCollection(int size) {
return null; // Ignored for this class
}
}
private class SetMarshallableType extends CollectionMarshallableType {
public SetMarshallableType(int id, ApacheAvroMarshaller marshaller) {
super(id, marshaller);
}
@Override
Collection<Object> createCollection(int size) {
return new HashSet<Object>(size);
}
}
}