import base64 import logging from amqp.adapter.amq_message_factory import AMQMessageFactory from amqp.adapter.trace_info_adapter import TraceInfoAdapter from amqp.model.model import CleverMicroServiceMessage from amqp.model.service_message_type import ServiceMessageType from amqp.model.snowflake_id import SnowflakeId from amqp.router.serializer import parse_map_string, map_as_string RS = "|" SPLIT_RS = r"\|" INVALID_MESSAGE = CleverMicroServiceMessage() # default values mean invalid message class CleverMicroServiceMessageFactory: """ build/serialize/deserialize CleverMicro Service Message class """ def __init__(self, name: str): self.process_name = name def to_string(self, message: CleverMicroServiceMessage) -> str: """ Convert CleverMicroServiceMessage to human-readable format :param message: CleverMicroServiceMessage object :return: as string """ if message is not None: return ( f"CleverMicroServiceMessage{{id={message.id.as_string()}|type={self.format_message_type(message)}|" f"recipient={message.recipient_name}|replyTo={message.reply_to}|" f"traceInfo={message.trace_info}|body={base64.b64encode(message.message.encode()).decode() if message.payload_type & 0x80 else message.message}}}" ) return "null" def of(self, message_type: ServiceMessageType, payload: str, recipient_name: str) -> CleverMicroServiceMessage: """ Builds CleverMicroServiceMessage object from relevant values. :param message_type: enum Message type :param payload: Message content, stored as-is :param recipient_name: Recipient name (from the routing expression) :return: CleverMicroServiceMessage object """ _id = AMQMessageFactory.get_instance(1).generate_snowflake_id() return CleverMicroServiceMessage( id=_id, payload_type=0, message_type=message_type, recipient_name=recipient_name, reply_to=self.process_name, trace_info=TraceInfoAdapter.create_produce_span(_id), message=payload ) def as_base64(self, message_type: ServiceMessageType, payload: str, recipient_name: str) -> CleverMicroServiceMessage: """ Builds CleverMicroServiceMessage object from relevant values, stores content Base64 encoded. :param message_type: enum Message type :param payload: Message content, stored as Base64 string :param recipient_name: Recipient name (from the routing expression) :return: CleverMicroServiceMessage object """ _id = AMQMessageFactory.get_instance(1).generate_snowflake_id() return CleverMicroServiceMessage( id=_id, payload_type=0x80, message_type=message_type, recipient_name=recipient_name, reply_to=self.process_name, trace_info=TraceInfoAdapter.create_produce_span(_id.as_string()), message=payload ) def from_bytes(self, input_bytes: bytes) -> CleverMicroServiceMessage: """ Deserialize CleverMicroServiceMessage from byte array serialized form. :param input_bytes: serialized CleverMicroServiceMessage :return: CleverMicroServiceMessage object represented by the input. """ logging.info(f"CleverMicroServiceMessage >>> [{input_bytes.decode()}]") input_str_array = input_bytes.decode().split(RS) try: i = 0 _id = input_str_array[i] i += 1 payload_type = int(input_str_array[i], 16) i += 1 message_type = ServiceMessageType(payload_type + 1 & 0x7F) recipient_name = input_str_array[i] if i < len(input_str_array) else "" i += 1 reply_to = input_str_array[i] if i < len(input_str_array) else "" i += 1 trace_info = parse_map_string(input_str_array[i]) if i < len(input_str_array) else {} i += 1 message = input_str_array[i] if i < len(input_str_array) else "" return CleverMicroServiceMessage( id=SnowflakeId.from_hex(_id), payload_type=payload_type, message_type=message_type, recipient_name=recipient_name, reply_to=reply_to, trace_info=trace_info, message=base64.b64decode(message).decode() if payload_type & 0x80 else message ) except Exception as e: logging.error( "Unexpected ServiceMessage format. Expected 6 tokens separated by '|' in format: " "[ID(64bit number)|MessageType(8bit number)|RecipientName(String)|" "ReplyTo(String)|TraceInfo|Payload(String or Base64)], got: " "[%s], reported problem: %s", input_bytes.decode(), str(e) ) return INVALID_MESSAGE def serialize(self, message: CleverMicroServiceMessage) -> bytes: """ Serialize CleverMicroServiceMessage to byte array. :param message: CleverMicroServiceMessage to serialize. :return: serialized object. """ return ( f"{message.id.as_string()}{RS}{self.format_message_type(message)}{RS}{message.recipient_name}{RS}" f"{message.reply_to}{RS}{map_as_string(message.trace_info)}{RS}" f"{base64.b64encode(message.message.encode()).decode() if message.payload_type != 0 else message.message}" ).encode('utf-8') def format_message_type(self, message: CleverMicroServiceMessage) -> str: """ Formats the Enum message type and decorates it with base64 flag at highest bit. For compatibility with 0-based enum ordinals in Java subtract 1 from ordinal value. :param message: Service message to send :return: formatted message type """ num_type = (message.message_type.value if message.message_type else ServiceMessageType.UNKNOWN.value) - 1 fmt = ((num_type & 0x7F) | (message.payload_type & 0x80)) return f"{fmt:02X}"