blob: c7b6b4d9f3721dcf80d53c8d7f4d7f2a33ca2d02 [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.
#
try:
import ujson as json
except ImportError:
import json
import struct
import uuid
import io
from gremlin_python.structure.io import graphbinaryV1
from gremlin_python.structure.io import graphsonV2d0
from gremlin_python.structure.io import graphsonV3d0
__author__ = 'David M. Brown (davebshow@gmail.com)'
class Processor:
"""Base class for OpProcessor serialization system."""
def __init__(self, writer):
self._writer = writer
def get_op_args(self, op, args):
op_method = getattr(self, op, None)
if not op_method:
raise Exception("Processor does not support op: {}".format(op))
return op_method(args)
class Standard(Processor):
def authentication(self, args):
return args
def eval(self, args):
return args
class Session(Processor):
def authentication(self, args):
return args
def eval(self, args):
return args
def close(self, args):
return args
class Traversal(Processor):
def authentication(self, args):
return args
def bytecode(self, args):
gremlin = args['gremlin']
args['gremlin'] = self._writer.toDict(gremlin)
aliases = args.get('aliases', '')
if not aliases:
aliases = {'g': 'g'}
args['aliases'] = aliases
return args
def close(self, args):
return self.keys(args)
def gather(self, args):
side_effect = uuid.UUID(args['sideEffect'])
args['sideEffect'] = self._writer.toDict(side_effect)
aliases = args.get('aliases', '')
if not aliases:
aliases = {'g': 'g'}
args['aliases'] = aliases
return args
def keys(self, args):
side_effect = uuid.UUID(args['sideEffect'])
args['sideEffect'] = self._writer.toDict(side_effect)
return args
class GraphSONMessageSerializer(object):
"""
Message serializer for GraphSON. Allow users to pass custom reader,
writer, and version kwargs for custom serialization. Otherwise,
use current GraphSON version as default.
"""
# KEEP TRACK OF CURRENT DEFAULTS
DEFAULT_READER_CLASS = graphsonV3d0.GraphSONReader
DEFAULT_WRITER_CLASS = graphsonV3d0.GraphSONWriter
DEFAULT_VERSION = b"application/vnd.gremlin-v3.0+json"
def __init__(self, reader=None, writer=None, version=None):
if not version:
version = self.DEFAULT_VERSION
self._version = version
if not reader:
reader = self.DEFAULT_READER_CLASS()
self._graphson_reader = reader
if not writer:
writer = self.DEFAULT_WRITER_CLASS()
self.standard = Standard(writer)
self.traversal = Traversal(writer)
self.session = Session(writer)
@property
def version(self):
"""Read only property"""
return self._version
def get_processor(self, processor):
processor = getattr(self, processor, None)
if not processor:
raise Exception("Unknown processor")
return processor
def serialize_message(self, request_id, request_message):
processor = request_message.processor
op = request_message.op
args = request_message.args
if not processor:
processor_obj = self.get_processor('standard')
else:
processor_obj = self.get_processor(processor)
args = processor_obj.get_op_args(op, args)
message = self.build_message(request_id, processor, op, args)
return message
def build_message(self, request_id, processor, op, args):
message = {
'requestId': {'@type': 'g:UUID', '@value': request_id},
'processor': processor,
'op': op,
'args': args
}
return self.finalize_message(message, b"\x21", self.version)
def finalize_message(self, message, mime_len, mime_type):
message = json.dumps(message)
message = b''.join([mime_len, mime_type, message.encode('utf-8')])
return message
def deserialize_message(self, message):
msg = json.loads(message.decode('utf-8'))
return self._graphson_reader.toObject(msg)
class GraphSONSerializersV2d0(GraphSONMessageSerializer):
"""Message serializer for GraphSON 2.0"""
def __init__(self):
reader = graphsonV2d0.GraphSONReader()
writer = graphsonV2d0.GraphSONWriter()
version = b"application/vnd.gremlin-v2.0+json"
super(GraphSONSerializersV2d0, self).__init__(reader, writer, version)
class GraphSONSerializersV3d0(GraphSONMessageSerializer):
"""Message serializer for GraphSON 3.0"""
def __init__(self):
reader = graphsonV3d0.GraphSONReader()
writer = graphsonV3d0.GraphSONWriter()
version = b"application/vnd.gremlin-v3.0+json"
super(GraphSONSerializersV3d0, self).__init__(reader, writer, version)
class GraphBinarySerializersV1(object):
DEFAULT_READER_CLASS = graphbinaryV1.GraphBinaryReader
DEFAULT_WRITER_CLASS = graphbinaryV1.GraphBinaryWriter
DEFAULT_VERSION = b"application/vnd.graphbinary-v1.0"
max_int64 = 0xFFFFFFFFFFFFFFFF
header_struct = struct.Struct('>b32sBQQ')
header_pack = header_struct.pack
int_pack = graphbinaryV1.int32_pack
int32_unpack = struct.Struct(">i").unpack
def __init__(self, reader=None, writer=None, version=None):
if not version:
version = self.DEFAULT_VERSION
self._version = version
if not reader:
reader = self.DEFAULT_READER_CLASS()
self._graphbinary_reader = reader
if not writer:
writer = self.DEFAULT_WRITER_CLASS()
self._graphbinary_writer = writer
self.standard = Standard(writer)
self.traversal = Traversal(writer)
@property
def version(self):
"""Read only property"""
return self._version
def get_processor(self, processor):
processor = getattr(self, processor, None)
if not processor:
raise Exception("Unknown processor")
return processor
def serialize_message(self, request_id, request_message):
processor = request_message.processor
op = request_message.op
args = request_message.args
if not processor:
processor_obj = self.get_processor('standard')
else:
processor_obj = self.get_processor(processor)
args = processor_obj.get_op_args(op, args)
message = self.build_message(request_id, processor, op, args)
return message
def build_message(self, request_id, processor, op, args):
message = {
'requestId': request_id,
'processor': processor,
'op': op,
'args': args
}
return self.finalize_message(message, 0x20, self.version)
def finalize_message(self, message, mime_len, mime_type):
ba = bytearray()
request_id = uuid.UUID(message['requestId'])
ba.extend(self.header_pack(mime_len, mime_type, 0x81,
(request_id.int >> 64) & self.max_int64, request_id.int & self.max_int64))
op_bytes = message['op'].encode("utf-8")
ba.extend(self.int_pack(len(op_bytes)))
ba.extend(op_bytes)
processor_bytes = message['processor'].encode("utf-8")
ba.extend(self.int_pack(len(processor_bytes)))
ba.extend(processor_bytes)
args = message["args"]
ba.extend(self.int_pack(len(args)))
for k, v in args.items():
self._graphbinary_writer.toDict(k, ba)
# processor_obj.get_op_args in serialize_message() seems to already handle bytecode. in python 3
# because bytearray isn't bound to a type in graphbinary it falls through the writeObject() and
# just works but python 2 bytearray is bound to ByteBufferType so it writes DataType.bytebuffer
# rather than DataType.bytecode and the server gets confused. special casing this for now until
# it can be refactored
if k == "gremlin" or k == "sideEffect":
ba.extend(v)
else:
self._graphbinary_writer.toDict(v, ba)
return bytes(ba)
def deserialize_message(self, message):
b = io.BytesIO(message)
b.read(1) # version
request_id = str(self._graphbinary_reader.toObject(b, graphbinaryV1.DataType.uuid))
status_code = self.int32_unpack(b.read(4))[0]
status_msg = self._graphbinary_reader.toObject(b, graphbinaryV1.DataType.string)
status_attrs = self._graphbinary_reader.toObject(b, graphbinaryV1.DataType.map, nullable=False)
meta_attrs = self._graphbinary_reader.toObject(b, graphbinaryV1.DataType.map, nullable=False)
result = self._graphbinary_reader.toObject(b)
b.close()
msg = {'requestId': request_id,
'status': {'code': status_code,
'message': status_msg,
'attributes': status_attrs},
'result': {'meta': meta_attrs,
'data': result}}
return msg