blob: bf4e76f032358963e862ffc10f6765d17885ac09 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.tinkerpop.gremlin.driver.ser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryIo;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
import org.apache.tinkerpop.gremlin.driver.ser.binary.RequestMessageSerializer;
import org.apache.tinkerpop.gremlin.driver.ser.binary.ResponseMessageSerializer;
import org.apache.tinkerpop.gremlin.structure.io.binary.TypeSerializerRegistry;
import org.apache.tinkerpop.gremlin.structure.io.binary.types.CustomTypeSerializer;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.io.IoRegistry;
import org.javatuples.Pair;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
public class GraphBinaryMessageSerializerV1 extends AbstractMessageSerializer {
public static final String TOKEN_CUSTOM = "custom";
public static final String TOKEN_BUILDER = "builder";
public static final String TOKEN_SERIALIZE_RESULT_TO_STRING = "serializeResultToString";
private static final String MIME_TYPE = SerTokens.MIME_GRAPHBINARY_V1D0;
private static final String MIME_TYPE_STRINGD = SerTokens.MIME_GRAPHBINARY_V1D0 + "-stringd";
private byte[] header = MIME_TYPE.getBytes(UTF_8);
private boolean serializeToString = false;
private GraphBinaryReader reader;
private GraphBinaryWriter writer;
private RequestMessageSerializer requestSerializer;
private ResponseMessageSerializer responseSerializer;
/**
* Creates a new instance of the message serializer using the default type serializers.
*/
public GraphBinaryMessageSerializerV1() {
this(TypeSerializerRegistry.INSTANCE);
}
public GraphBinaryMessageSerializerV1(final TypeSerializerRegistry registry) {
reader = new GraphBinaryReader(registry);
writer = new GraphBinaryWriter(registry);
requestSerializer = new RequestMessageSerializer();
responseSerializer = new ResponseMessageSerializer();
}
public GraphBinaryMessageSerializerV1(final TypeSerializerRegistry.Builder builder) {
this(builder.create());
}
@Override
public void configure(final Map<String, Object> config, final Map<String, Graph> graphs) {
final String builderClassName = (String) config.get(TOKEN_BUILDER);
final TypeSerializerRegistry.Builder builder;
if (builderClassName != null) {
try {
Class<?> clazz = Class.forName(builderClassName);
Constructor<?> ctor = clazz.getConstructor();
builder = (TypeSerializerRegistry.Builder) ctor.newInstance();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
} else {
builder = TypeSerializerRegistry.build();
}
final List<String> classNameList = getListStringFromConfig(TOKEN_IO_REGISTRIES, config);
classNameList.forEach(className -> {
try {
final Class<?> clazz = Class.forName(className);
try {
final Method instanceMethod = tryInstanceMethod(clazz);
final IoRegistry ioreg = (IoRegistry) instanceMethod.invoke(null);
final List<Pair<Class, CustomTypeSerializer>> classSerializers = ioreg.find(GraphBinaryIo.class, CustomTypeSerializer.class);
for (Pair<Class,CustomTypeSerializer> cs : classSerializers) {
builder.addCustomType(cs.getValue0(), cs.getValue1());
}
} catch (Exception methodex) {
throw new IllegalStateException(String.format("Could not instantiate IoRegistry from an instance() method on %s", className), methodex);
}
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
});
addCustomClasses(config, builder);
this.serializeToString = Boolean.parseBoolean(config.getOrDefault(TOKEN_SERIALIZE_RESULT_TO_STRING, "false").toString());
this.header = this.serializeToString ? MIME_TYPE_STRINGD.getBytes(UTF_8) : MIME_TYPE.getBytes(UTF_8);
final TypeSerializerRegistry registry = builder.create();
reader = new GraphBinaryReader(registry);
writer = new GraphBinaryWriter(registry);
requestSerializer = new RequestMessageSerializer();
responseSerializer = new ResponseMessageSerializer();
}
@Override
public ByteBuf serializeResponseAsBinary(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException {
final ByteBuf buffer = allocator.buffer();
try {
final ResponseMessage msgToWrite = !serializeToString ? responseMessage :
ResponseMessage.build(responseMessage.getRequestId())
.code(responseMessage.getStatus().getCode())
.statusAttributes(responseMessage.getStatus().getAttributes())
.responseMetaData(responseMessage.getResult().getMeta())
.result(serializeResultToString(responseMessage))
.statusMessage(responseMessage.getStatus().getMessage()).create();
responseSerializer.writeValue(msgToWrite, buffer, writer);
} catch (Exception ex) {
buffer.release();
throw ex;
}
return buffer;
}
@Override
public ByteBuf serializeRequestAsBinary(final RequestMessage requestMessage, final ByteBufAllocator allocator) throws SerializationException {
final ByteBuf buffer = allocator.buffer().writeByte(header.length).writeBytes(header);
try {
requestSerializer.writeValue(requestMessage, buffer, writer);
} catch (Exception ex) {
buffer.release();
throw ex;
}
return buffer;
}
@Override
public RequestMessage deserializeRequest(final ByteBuf msg) throws SerializationException {
return requestSerializer.readValue(msg, reader);
}
@Override
public ResponseMessage deserializeResponse(final ByteBuf msg) throws SerializationException {
return responseSerializer.readValue(msg, reader);
}
@Override
public String[] mimeTypesSupported() {
return new String[]{serializeToString ? MIME_TYPE_STRINGD : MIME_TYPE};
}
private void addCustomClasses(final Map<String, Object> config, final TypeSerializerRegistry.Builder builder) {
final List<String> classNameList = getListStringFromConfig(TOKEN_CUSTOM, config);
classNameList.forEach(serializerDefinition -> {
final String className;
final String serializerName;
if (serializerDefinition.contains(";")) {
final String[] split = serializerDefinition.split(";");
if (split.length != 2)
throw new IllegalStateException(String.format("Invalid format for serializer definition [%s] - expected <class>;<serializer-class>", serializerDefinition));
className = split[0];
serializerName = split[1];
} else {
throw new IllegalStateException(String.format("Invalid format for serializer definition [%s] - expected <class>;<serializer-class>", serializerDefinition));
}
try {
final Class clazz = Class.forName(className);
final Class serializerClazz = Class.forName(serializerName);
final CustomTypeSerializer serializer = (CustomTypeSerializer) serializerClazz.newInstance();
builder.addCustomType(clazz, serializer);
} catch (Exception ex) {
throw new IllegalStateException("CustomTypeSerializer could not be instantiated", ex);
}
});
}
private Object serializeResultToString(final ResponseMessage msg) {
if (msg.getResult() == null) return "null";
if (msg.getResult().getData() == null) return "null";
// the IteratorHandler should return a collection so keep it as such
final Object o = msg.getResult().getData();
if (o instanceof Collection) {
return ((Collection) o).stream().map(d -> null == d ? "null" : d.toString()).collect(Collectors.toList());
} else {
return o.toString();
}
}
}