/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cinchapi.concourse.server.plugin.io; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; import com.cinchapi.common.io.ByteBuffers; import com.cinchapi.common.reflect.Reflection; import com.cinchapi.concourse.server.plugin.RemoteMessage; import com.cinchapi.concourse.thrift.ComplexTObject; import com.cinchapi.concourse.thrift.TObject; import com.cinchapi.concourse.thrift.Type; import com.cinchapi.concourse.util.Serializables; import io.atomix.catalyst.buffer.Buffer; import io.atomix.catalyst.buffer.HeapBuffer; /** * A tool for transparently serializing data passed among plugins and Concourse * Server, regardless of their type. * <p> * A {@link PluginSerializer} is a one-stop shop for dealing with all the * serialization protocols used by different classes within the framework. It * compactly embeds just enough information to be able to represent almost any * class. * </p> * * @author Jeff Nelson */ public class PluginSerializer { /** * Return the object represented by the serialized data in the * {@link ByteBuffer}. * * @param bytes the bytes from which the object is deserialized * @return the deserialized object */ @SuppressWarnings("unchecked") public <T> T deserialize(ByteBuffer bytes) { Scheme scheme = Scheme.values()[bytes.get()]; if(scheme == Scheme.PLUGIN_SERIALIZABLE) { Buffer buffer = HeapBuffer.wrap(ByteBuffers.toByteArray(bytes)); buffer.position(bytes.position()); Class<T> clazz = Reflection.getClassCasted(buffer.readUTF8()); T instance = Reflection.newInstance(clazz); ((PluginSerializable) instance).deserialize(buffer); return instance; } else if(scheme == Scheme.REMOTE_MESSAGE) { Buffer buffer = HeapBuffer.wrap(ByteBuffers.toByteArray(bytes)); buffer.position(bytes.position()); T instance = (T) RemoteMessage.fromBuffer(buffer); return instance; } else if(scheme == Scheme.COMPLEX_TOBJECT) { ByteBuffer bytes0 = ByteBuffers.slice(bytes, 1, bytes.remaining()); return (T) ComplexTObject.fromByteBuffer(bytes0); } else if(scheme == Scheme.TOBJECT) { ByteBuffer bytes0 = ByteBuffers.slice(bytes, 1, bytes.remaining()); Type type = Type.values()[bytes0.get()]; return (T) new TObject( ByteBuffers.slice(bytes0, bytes0.remaining()), type); } else if(scheme == Scheme.JAVA_SERIALIZABLE) { int classLength = bytes.getShort(); byte[] className = new byte[classLength]; bytes.get(className); Class<T> clazz = Reflection.getClassCasted(new String(className, StandardCharsets.UTF_8)); bytes = ByteBuffers.get(bytes, bytes.remaining()); Serializable instance = Serializables.read(bytes, (Class<? extends Serializable>) clazz); return (T) instance; } else { // NOTE: In the future, if/when we add support for storing binary // blobs, we will need to add a scheme to distinguish a legitimate // blob from a blob that is really just a PluginSerializable object throw new IllegalStateException( "Cannot plugin deserialize the provided byte stream"); } } /** * Return a {@link ByteBuffer} that contains the serialized form of the * input {@code object}. * * @param object the object to serialize * @return the serialized form within a ByteBuffer */ public ByteBuffer serialize(Object object) { ByteBuffer buffer = null; if(object instanceof PluginSerializable) { HeapBuffer buffer0 = HeapBuffer.allocate(); buffer0.writeByte(Scheme.PLUGIN_SERIALIZABLE.ordinal()); buffer0.writeUTF8(object.getClass().getName()); ((PluginSerializable) object).serialize(buffer0); byte[] bytes = new byte[(int) buffer0.position()]; buffer0.flip(); buffer0.read(bytes); buffer = ByteBuffer.wrap(bytes); return buffer; } else if(object instanceof RemoteMessage) { HeapBuffer buffer0 = (HeapBuffer) ((RemoteMessage) object) .serialize(); buffer = ByteBuffer.allocate((int) buffer0.remaining() + 1); buffer.put((byte) Scheme.REMOTE_MESSAGE.ordinal()); buffer.put(buffer0.array(), 0, (int) buffer0.remaining()); buffer.flip(); return buffer; } else if(object instanceof ComplexTObject) { byte[] bytes = ((ComplexTObject) object).toByteBuffer().array(); buffer = ByteBuffer.allocate(bytes.length + 1); buffer.put((byte) Scheme.COMPLEX_TOBJECT.ordinal()); buffer.put(bytes); buffer.flip(); return buffer; } else if(object instanceof TObject) { byte[] bytes = ((TObject) object).getData(); buffer = ByteBuffer.allocate(bytes.length + 2); buffer.put((byte) Scheme.TOBJECT.ordinal()); buffer.put((byte) ((TObject) object).getType().ordinal()); buffer.put(bytes); buffer.flip(); return buffer; } else if(object instanceof String || object.getClass().isPrimitive() || object instanceof Number || object instanceof Boolean || object instanceof Map || object instanceof List || object instanceof Set) { return serialize(ComplexTObject.fromJavaObject(object)); } else if(object instanceof Serializable) { byte[] bytes = ByteBuffers.toByteArray(Serializables .getBytes((Serializable) object)); byte[] classBytes = object.getClass().getName() .getBytes(StandardCharsets.UTF_8); buffer = ByteBuffer.allocate(1 + 2 + classBytes.length + bytes.length); buffer.put((byte) Scheme.JAVA_SERIALIZABLE.ordinal()); buffer.putShort((short) classBytes.length); buffer.put(classBytes); buffer.put(bytes); buffer.flip(); return buffer; } else { throw new IllegalStateException( "Cannot plugin serialize an object of type " + object.getClass()); } } /** * A list of all the serialization schemes. * * @author Jeff Nelson */ public enum Scheme { COMPLEX_TOBJECT, JAVA_SERIALIZABLE, PLUGIN_SERIALIZABLE, TOBJECT, REMOTE_MESSAGE; } }