/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.data.serialize;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TIOStreamTransport;
import org.diqube.data.serialize.DataSerialization.DataSerializationHelper;
import org.diqube.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Deserializes thrift objects to {@link DataSerialization} objects.
*
* Does not expect any header/metainformation/etc in the stream, but only serialized thrift objects.
*
* An instance of this class can be re-used.
*
* @author Bastian Gloeckle
*/
public class DataDeserializer {
private static final Logger logger = LoggerFactory.getLogger(DataDeserializer.class);
private Map<Class<? extends DataSerialization<?>>, Class<? extends TBase<?, ?>>> thriftClasses;
private Map<Class<? extends TBase<?, ?>>, DataSerializationDelegationManager<?>> delegationManagers;
private Map<Class<? extends TBase<?, ?>>, Class<? extends DataSerialization<?>>> liveClasses;
private Function<Consumer<TBase<?, ?>>, DataSerializationHelper> dataSerializationHelperFactory =
(cleanupConsumer) -> {
return new DataSerializationHelper() {
@Override
public <M extends TBase<?, ?>, I extends DataSerialization<M>, O extends TBase<?, ?>> O serializeChild(
Class<? extends O> targetClass, I obj) throws SerializationException {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
@Override
public <I extends TBase<?, ?>, M extends TBase<?, ?>, O extends DataSerialization<M>> O deserializeChild(
Class<? extends O> targetClass, I thriftObj) throws DeserializationException {
if (thriftObj == null)
return null;
try {
// check if we can deserialize into the given targetClass directly
if (thriftClasses.containsKey(targetClass) && thriftClasses.get(targetClass).isInstance(thriftObj)
&& !targetClass.isInterface() && !Modifier.isAbstract(targetClass.getModifiers())) {
O res = targetClass.newInstance();
M mObj = (M) thriftObj;
res.deserialize(this, mObj);
cleanupConsumer.accept(mObj);
return res;
}
if (delegationManagers.containsKey(thriftObj.getClass())) {
DataSerializationDelegationManager<I> delegationManager =
(DataSerializationDelegationManager<I>) delegationManagers.get(thriftObj.getClass());
Pair<Class<? extends DataSerialization<?>>, TBase<?, ?>> delegateRes =
delegationManager.getDeserializationDelegate(thriftObj);
DataSerialization<?> res = delegateRes.getLeft().newInstance();
if (!targetClass.isInstance(res))
throw new DeserializationException("DelegationManager gave us " + delegateRes.getLeft().getName()
+ " but " + targetClass.getName() + " was expected.");
O oRes = (O) res;
oRes.deserialize(this, (M) delegateRes.getRight());
cleanupConsumer.accept(delegateRes.getRight());
cleanupConsumer.accept(thriftObj);
return oRes;
}
throw new DeserializationException("Object cannot be deserialized into expected type: "
+ thriftObj.getClass().getName() + " expected: " + targetClass.getName());
} catch (InstantiationException | IllegalAccessException e) {
throw new DeserializationException("Cannot deserialize", e);
}
}
};
};
/* package */ DataDeserializer(Map<Class<? extends DataSerialization<?>>, Class<? extends TBase<?, ?>>> thriftClasses,
Map<Class<? extends TBase<?, ?>>, Class<? extends DataSerialization<?>>> liveClasses,
Map<Class<? extends TBase<?, ?>>, DataSerializationDelegationManager<?>> delegationManagers) {
this.thriftClasses = thriftClasses;
this.liveClasses = liveClasses;
this.delegationManagers = delegationManagers;
}
/**
* Deserialize the data available in a byte array into a {@link DataSerialization} object hierarchy.
*
* @param inputStream
* the stream containign the data to be deserialized. It will be tried to close this input stream as soon as
* possible to free any resources.
* @throws DeserializationException
* if anything went wrong.
*/
// we need to capture the generics here to make javac happy
public <T extends TBase<?, ?>, M extends TBase<?, ?>, O extends DataSerialization<M>> O deserialize(
Class<? extends O> targetClass, InputStream inputStream) throws DeserializationException {
logger.trace("Deserializing to thrift...");
@SuppressWarnings("unchecked")
T thrift = deserializeToThrift(inputStream, (Class<? extends T>) thriftClasses.get(targetClass));
try {
inputStream.close();
} catch (IOException e) {
// swallow.
}
inputStream = null;
DataSerializationHelper helper = dataSerializationHelperFactory.apply(new Consumer<TBase<?, ?>>() {
@Override
public void accept(TBase<?, ?> t) {
// cleanup action is to clear all field values to release memory as soon as possible.
t.clear();
}
});
logger.trace("Transforming into final objects...");
@SuppressWarnings("unchecked")
Class<? extends O> targetClz = (Class<? extends O>) liveClasses.get(thrift.getClass());
O res = helper.deserializeChild(targetClz, thrift);
logger.trace("Deserialization done.");
return res;
}
private <T extends TBase<?, ?>> T deserializeToThrift(InputStream inputStream, Class<? extends T> thriftClass)
throws DeserializationException {
TIOStreamTransport transport = new TIOStreamTransport(inputStream);
TProtocol compactProt = new TCompactProtocol(transport);
try {
T thrift = thriftClass.newInstance();
thrift.read(compactProt);
return thrift;
} catch (TException | InstantiationException | IllegalAccessException e) {
throw new DeserializationException("Cannot deserialize", e);
}
}
}