# # 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. # from TProtocol import TType, TProtocolBase, TProtocolException, \ checkIntegerLimits import base64 import json import math __all__ = ['TJSONProtocol', 'TJSONProtocolFactory', 'TSimpleJSONProtocol', 'TSimpleJSONProtocolFactory'] VERSION = 1 COMMA = ',' COLON = ':' LBRACE = '{' RBRACE = '}' LBRACKET = '[' RBRACKET = ']' QUOTE = '"' BACKSLASH = '\\' ZERO = '0' ESCSEQ = '\\u00' ESCAPE_CHAR = '"\\bfnrt' ESCAPE_CHAR_VALS = ['"', '\\', '\b', '\f', '\n', '\r', '\t'] NUMERIC_CHAR = '+-.0123456789Ee' CTYPES = {TType.BOOL: 'tf', TType.BYTE: 'i8', TType.I16: 'i16', TType.I32: 'i32', TType.I64: 'i64', TType.DOUBLE: 'dbl', TType.STRING: 'str', TType.STRUCT: 'rec', TType.LIST: 'lst', TType.SET: 'set', TType.MAP: 'map'} JTYPES = {} for key in CTYPES.keys(): JTYPES[CTYPES[key]] = key class JSONBaseContext(object): def __init__(self, protocol): self.protocol = protocol self.first = True def doIO(self, function): pass def write(self): pass def read(self): pass def escapeNum(self): return False def __str__(self): return self.__class__.__name__ class JSONListContext(JSONBaseContext): def doIO(self, function): if self.first is True: self.first = False else: function(COMMA) def write(self): self.doIO(self.protocol.trans.write) def read(self): self.doIO(self.protocol.readJSONSyntaxChar) class JSONPairContext(JSONBaseContext): def __init__(self, protocol): super(JSONPairContext, self).__init__(protocol) self.colon = True def doIO(self, function): if self.first: self.first = False self.colon = True else: function(COLON if self.colon else COMMA) self.colon = not self.colon def write(self): self.doIO(self.protocol.trans.write) def read(self): self.doIO(self.protocol.readJSONSyntaxChar) def escapeNum(self): return self.colon def __str__(self): return '%s, colon=%s' % (self.__class__.__name__, self.colon) class LookaheadReader(): hasData = False data = '' def __init__(self, protocol): self.protocol = protocol def read(self): if self.hasData is True: self.hasData = False else: self.data = self.protocol.trans.read(1) return self.data def peek(self): if self.hasData is False: self.data = self.protocol.trans.read(1) self.hasData = True return self.data class TJSONProtocolBase(TProtocolBase): def __init__(self, trans): TProtocolBase.__init__(self, trans) self.resetWriteContext() self.resetReadContext() def resetWriteContext(self): self.context = JSONBaseContext(self) self.contextStack = [self.context] def resetReadContext(self): self.resetWriteContext() self.reader = LookaheadReader(self) def pushContext(self, ctx): self.contextStack.append(ctx) self.context = ctx def popContext(self): self.contextStack.pop() if self.contextStack: self.context = self.contextStack[-1] else: self.context = JSONBaseContext(self) def writeJSONString(self, string): self.context.write() self.trans.write(json.dumps(string)) def writeJSONNumber(self, number): self.context.write() jsNumber = str(number) if self.context.escapeNum(): jsNumber = "%s%s%s" % (QUOTE, jsNumber, QUOTE) self.trans.write(jsNumber) def writeJSONBase64(self, binary): self.context.write() self.trans.write(QUOTE) self.trans.write(base64.b64encode(binary)) self.trans.write(QUOTE) def writeJSONObjectStart(self): self.context.write() self.trans.write(LBRACE) self.pushContext(JSONPairContext(self)) def writeJSONObjectEnd(self): self.popContext() self.trans.write(RBRACE) def writeJSONArrayStart(self): self.context.write() self.trans.write(LBRACKET) self.pushContext(JSONListContext(self)) def writeJSONArrayEnd(self): self.popContext() self.trans.write(RBRACKET) def readJSONSyntaxChar(self, character): current = self.reader.read() if character != current: raise TProtocolException(TProtocolException.INVALID_DATA, "Unexpected character: %s" % current) def readJSONString(self, skipContext): string = [] if skipContext is False: self.context.read() self.readJSONSyntaxChar(QUOTE) while True: character = self.reader.read() if character == QUOTE: break if character == ESCSEQ[0]: character = self.reader.read() if character == ESCSEQ[1]: self.readJSONSyntaxChar(ZERO) self.readJSONSyntaxChar(ZERO) character = json.JSONDecoder().decode('"\u00%s"' % self.trans.read(2)) else: off = ESCAPE_CHAR.find(character) if off == -1: raise TProtocolException(TProtocolException.INVALID_DATA, "Expected control char") character = ESCAPE_CHAR_VALS[off] string.append(character) return ''.join(string) def isJSONNumeric(self, character): return (True if NUMERIC_CHAR.find(character) != - 1 else False) def readJSONQuotes(self): if (self.context.escapeNum()): self.readJSONSyntaxChar(QUOTE) def readJSONNumericChars(self): numeric = [] while True: character = self.reader.peek() if self.isJSONNumeric(character) is False: break numeric.append(self.reader.read()) return ''.join(numeric) def readJSONInteger(self): self.context.read() self.readJSONQuotes() numeric = self.readJSONNumericChars() self.readJSONQuotes() try: return int(numeric) except ValueError: raise TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data") def readJSONDouble(self): self.context.read() if self.reader.peek() == QUOTE: string = self.readJSONString(True) try: double = float(string) if (self.context.escapeNum is False and not math.isinf(double) and not math.isnan(double)): raise TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted") return double except ValueError: raise TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data") else: if self.context.escapeNum() is True: self.readJSONSyntaxChar(QUOTE) try: return float(self.readJSONNumericChars()) except ValueError: raise TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data") def readJSONBase64(self): string = self.readJSONString(False) return base64.b64decode(string) def readJSONObjectStart(self): self.context.read() self.readJSONSyntaxChar(LBRACE) self.pushContext(JSONPairContext(self)) def readJSONObjectEnd(self): self.readJSONSyntaxChar(RBRACE) self.popContext() def readJSONArrayStart(self): self.context.read() self.readJSONSyntaxChar(LBRACKET) self.pushContext(JSONListContext(self)) def readJSONArrayEnd(self): self.readJSONSyntaxChar(RBRACKET) self.popContext() class TJSONProtocol(TJSONProtocolBase): def readMessageBegin(self): self.resetReadContext() self.readJSONArrayStart() if self.readJSONInteger() != VERSION: raise TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.") name = self.readJSONString(False) typen = self.readJSONInteger() seqid = self.readJSONInteger() return (name, typen, seqid) def readMessageEnd(self): self.readJSONArrayEnd() def readStructBegin(self): self.readJSONObjectStart() def readStructEnd(self): self.readJSONObjectEnd() def readFieldBegin(self): character = self.reader.peek() ttype = 0 id = 0 if character == RBRACE: ttype = TType.STOP else: id = self.readJSONInteger() self.readJSONObjectStart() ttype = JTYPES[self.readJSONString(False)] return (None, ttype, id) def readFieldEnd(self): self.readJSONObjectEnd() def readMapBegin(self): self.readJSONArrayStart() keyType = JTYPES[self.readJSONString(False)] valueType = JTYPES[self.readJSONString(False)] size = self.readJSONInteger() self.readJSONObjectStart() return (keyType, valueType, size) def readMapEnd(self): self.readJSONObjectEnd() self.readJSONArrayEnd() def readCollectionBegin(self): self.readJSONArrayStart() elemType = JTYPES[self.readJSONString(False)] size = self.readJSONInteger() return (elemType, size) readListBegin = readCollectionBegin readSetBegin = readCollectionBegin def readCollectionEnd(self): self.readJSONArrayEnd() readSetEnd = readCollectionEnd readListEnd = readCollectionEnd def readBool(self): return (False if self.readJSONInteger() == 0 else True) def readNumber(self): return self.readJSONInteger() readByte = readNumber readI16 = readNumber readI32 = readNumber readI64 = readNumber def readDouble(self): return self.readJSONDouble() def readString(self): return self.readJSONString(False) def readBinary(self): return self.readJSONBase64() def writeMessageBegin(self, name, request_type, seqid): self.resetWriteContext() self.writeJSONArrayStart() self.writeJSONNumber(VERSION) self.writeJSONString(name) self.writeJSONNumber(request_type) self.writeJSONNumber(seqid) def writeMessageEnd(self): self.writeJSONArrayEnd() def writeStructBegin(self, name): self.writeJSONObjectStart() def writeStructEnd(self): self.writeJSONObjectEnd() def writeFieldBegin(self, name, ttype, id): self.writeJSONNumber(id) self.writeJSONObjectStart() self.writeJSONString(CTYPES[ttype]) def writeFieldEnd(self): self.writeJSONObjectEnd() def writeFieldStop(self): pass def writeMapBegin(self, ktype, vtype, size): self.writeJSONArrayStart() self.writeJSONString(CTYPES[ktype]) self.writeJSONString(CTYPES[vtype]) self.writeJSONNumber(size) self.writeJSONObjectStart() def writeMapEnd(self): self.writeJSONObjectEnd() self.writeJSONArrayEnd() def writeListBegin(self, etype, size): self.writeJSONArrayStart() self.writeJSONString(CTYPES[etype]) self.writeJSONNumber(size) def writeListEnd(self): self.writeJSONArrayEnd() def writeSetBegin(self, etype, size): self.writeJSONArrayStart() self.writeJSONString(CTYPES[etype]) self.writeJSONNumber(size) def writeSetEnd(self): self.writeJSONArrayEnd() def writeBool(self, boolean): self.writeJSONNumber(1 if boolean is True else 0) def writeByte(self, byte): checkIntegerLimits(byte, 8) self.writeJSONNumber(byte) def writeI16(self, i16): checkIntegerLimits(i16, 16) self.writeJSONNumber(i16) def writeI32(self, i32): checkIntegerLimits(i32, 32) self.writeJSONNumber(i32) def writeI64(self, i64): checkIntegerLimits(i64, 64) self.writeJSONNumber(i64) def writeDouble(self, dbl): self.writeJSONNumber(dbl) def writeString(self, string): self.writeJSONString(string) def writeBinary(self, binary): self.writeJSONBase64(binary) class TJSONProtocolFactory: def getProtocol(self, trans): return TJSONProtocol(trans) class TSimpleJSONProtocol(TJSONProtocolBase): """Simple, readable, write-only JSON protocol. Useful for interacting with scripting languages. """ def readMessageBegin(self): raise NotImplementedError() def readMessageEnd(self): raise NotImplementedError() def readStructBegin(self): raise NotImplementedError() def readStructEnd(self): raise NotImplementedError() def writeMessageBegin(self, name, request_type, seqid): self.resetWriteContext() def writeMessageEnd(self): pass def writeStructBegin(self, name): self.writeJSONObjectStart() def writeStructEnd(self): self.writeJSONObjectEnd() def writeFieldBegin(self, name, ttype, fid): self.writeJSONString(name) def writeFieldEnd(self): pass def writeMapBegin(self, ktype, vtype, size): self.writeJSONObjectStart() def writeMapEnd(self): self.writeJSONObjectEnd() def _writeCollectionBegin(self, etype, size): self.writeJSONArrayStart() def _writeCollectionEnd(self): self.writeJSONArrayEnd() writeListBegin = _writeCollectionBegin writeListEnd = _writeCollectionEnd writeSetBegin = _writeCollectionBegin writeSetEnd = _writeCollectionEnd def writeByte(self, byte): checkIntegerLimits(byte, 8) self.writeJSONNumber(byte) def writeI16(self, i16): checkIntegerLimits(i16, 16) self.writeJSONNumber(i16) def writeI32(self, i32): checkIntegerLimits(i32, 32) self.writeJSONNumber(i32) def writeI64(self, i64): checkIntegerLimits(i64, 64) self.writeJSONNumber(i64) def writeBool(self, boolean): self.writeJSONNumber(1 if boolean is True else 0) def writeDouble(self, dbl): self.writeJSONNumber(dbl) def writeString(self, string): self.writeJSONString(string) def writeBinary(self, binary): self.writeJSONBase64(binary) class TSimpleJSONProtocolFactory(object): def getProtocol(self, trans): return TSimpleJSONProtocol(trans)