/** * 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.OutputStream; 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; /** * Serializes any class implementing {@link DataSerialization} to thrift objects and then to a stream. * * Does not put any header/metainformation/etc into the stream, but only serializes a given object. * * An instance of this class can be re-used. * * @author Bastian Gloeckle */ public class DataSerializer { /** Version of thrift objects created by this serializer/capable of reading in the deserializer. */ public static final int DATA_VERSION = 1; private Map<Class<? extends DataSerialization<?>>, Class<? extends TBase<?, ?>>> thriftClasses; private Function<ObjectDoneConsumer, DataSerializationHelper> dataSerializationHelperFactory = (objectDoneConsumer) -> { return new DataSerializationHelper() { @SuppressWarnings("unchecked") @Override public <M extends TBase<?, ?>, I extends DataSerialization<M>, O extends TBase<?, ?>> O serializeChild( Class<? extends O> targetClass, I obj) throws SerializationException { if (obj == null) return null; // serialize object into that thrift object which the class itself supports Class<?> thriftClass = thriftClasses.get(obj.getClass()); M target; try { target = (M) thriftClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new SerializationException("Could not instantiate " + thriftClass); } obj.serialize(this, target); objectDoneConsumer.accept(obj); // if the target object is already of the requested type, we're done! if (targetClass.isInstance(target)) return (O) target; // target object is of different type than requested. Try to wrap it using a delegationManager. DataSerializationDelegationManager<O> delegationManager = (DataSerializationDelegationManager<O>) delegationManagers.get(targetClass); if (delegationManager == null) throw new SerializationException("Cannot serialize " + obj + " because there is no delegation manager which would provide " + targetClass.getName()); return delegationManager.serializeWrapObject(target); } @Override public <I extends TBase<?, ?>, M extends TBase<?, ?>, O extends DataSerialization<M>> O deserializeChild( Class<? extends O> targetClass, I obj) throws DeserializationException { throw new UnsupportedOperationException(); } }; }; private Map<Class<? extends TBase<?, ?>>, DataSerializationDelegationManager<?>> delegationManagers; /* package */ DataSerializer(Map<Class<? extends DataSerialization<?>>, Class<? extends TBase<?, ?>>> thriftClasses, Map<Class<? extends TBase<?, ?>>, DataSerializationDelegationManager<?>> delegationManagers) { this.thriftClasses = thriftClasses; this.delegationManagers = delegationManagers; } /** * Serialize a {@link DataSerialization} object into an output stream and flush that stream. * * @param obj * The object to serialize. * @param outputStream * The output stream to fill. * @param objectDoneConsumer * Will be called when single objects (referenced transitively from obj) have been "serialized" and can be * freed by the caller, if needed. * @throws SerializationException * If anything went wrong. */ public void serialize(DataSerialization<?> obj, OutputStream outputStream, ObjectDoneConsumer objectDoneConsumer) throws SerializationException { DataSerializationHelper helper = dataSerializationHelperFactory.apply(objectDoneConsumer); TBase<?, ?> res = helper.serializeChild(thriftClasses.get(obj.getClass()), obj); TIOStreamTransport transport = new TIOStreamTransport(outputStream); TProtocol compactProt = new TCompactProtocol(transport); try { res.write(compactProt); outputStream.flush(); } catch (TException | IOException e) { throw new SerializationException("Could not serialize", e); } } /** * Consumes objects that have been serialized already fully. */ public static interface ObjectDoneConsumer extends Consumer<DataSerialization<?>> { } }