/**
* 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.consensus.internal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.diqube.consensus.ConsensusStateMachineManager;
import org.diqube.context.AutoInstatiate;
import com.google.common.collect.Sets;
import io.atomix.catalyst.buffer.BufferInput;
import io.atomix.catalyst.buffer.BufferOutput;
import io.atomix.catalyst.buffer.OutputStreamBufferOutput;
import io.atomix.catalyst.serializer.JdkTypeResolver;
import io.atomix.catalyst.serializer.PrimitiveTypeResolver;
import io.atomix.catalyst.serializer.SerializationException;
import io.atomix.catalyst.serializer.Serializer;
import io.atomix.catalyst.serializer.TypeSerializer;
import io.atomix.catalyst.serializer.util.JavaSerializableSerializer;
/**
* Catalyst serializer used by diqube.
*
* @author Bastian Gloeckle
*/
@AutoInstatiate
public class DiqubeCatalystSerializer extends Serializer {
private static final int BASE_SERIALIZATION_ID = 2500;
/**
* Additional classes that we need to register in order to serialize them correctly.
*/
private static final Class<?>[] ADDITIONAL_SERIALIZATION_CLASSES = { //
// IllegalStateException is send e.g. by InactiveState if a client tries to communicate with an inactive server -
// be sure that the server response is received by the client and it can retry.
IllegalStateException.class //
};
@Inject
private ConsensusStateMachineManager consensusStateMachineManager;
public DiqubeCatalystSerializer() {
super(new PrimitiveTypeResolver(), new JdkTypeResolver());
}
@PostConstruct
public void initialize() {
// register all the operation classes in the serializer so they can be serialized (we need to whitelist them).
// Register all additional classes, too.
this.resolve((registry) -> {
Set<Class<?>> allSerializationClasses = Sets.union(consensusStateMachineManager.getAllOperationClasses(),
consensusStateMachineManager.getAllAdditionalSerializationClasses());
List<Class<?>> serializationClassesSorted = allSerializationClasses.stream()
.sorted((c1, c2) -> c1.getName().compareTo(c2.getName())).collect(Collectors.toList());
serializationClassesSorted.addAll(Arrays.asList(ADDITIONAL_SERIALIZATION_CLASSES));
// start suing IDs at an arbitrary, but fixed point, so we do not overwrite IDs used internally by copycat.
int nextId = BASE_SERIALIZATION_ID;
for (Class<?> opClass : serializationClassesSorted) {
registry.register(opClass, nextId++, DiqubeJavaSerializableSerializer.class);
}
});
}
/**
* Helper method to validate an object can be sent as parameter for the consensus client.
*
* TODO #107: Remove this.
*
* @throws IllegalArgumentException
* If object is invalid.
*/
public void validateSerializationObject(Object o) throws IllegalArgumentException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
new DiqubeJavaSerializableSerializer<>().write(o, new OutputStreamBufferOutput(baos), this);
} catch (IOException | SerializationException e) {
throw new IllegalArgumentException("Object invalid", e);
}
}
/**
* As long as catalysts {@link JavaSerializableSerializer} is buggy, we use this fixed implementation.
*
* TODO #107: remove workaround.
*/
public static class DiqubeJavaSerializableSerializer<T> implements TypeSerializer<T> {
private static final int MAX_UNSIGNED_SHORT = (1 << 16) - 1;
@SuppressWarnings("rawtypes")
@Override
public void write(T object, BufferOutput buffer, Serializer serializer) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(os)) {
out.writeObject(object);
out.flush();
byte[] bytes = os.toByteArray();
// Workaround for copycat #173: The copycat Log uses an unsigned short length field, too.
if (bytes.length > MAX_UNSIGNED_SHORT)
throw new SerializationException("Cannot serialize java object because it is too big.");
// Workaround for catalyst #30: Write "int", not "unsigned short"
buffer.writeInt(bytes.length).write(bytes);
} catch (IOException e) {
throw new SerializationException("failed to serialize Java object", e);
}
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public T read(Class<T> type, BufferInput buffer, Serializer serializer) {
byte[] bytes = new byte[buffer.readInt()];
buffer.read(bytes);
try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
try {
return (T) in.readObject();
} catch (ClassNotFoundException e) {
throw new SerializationException("failed to deserialize Java object", e);
}
} catch (IOException e) {
throw new SerializationException("failed to deserialize Java object", e);
}
}
}
}