156 lines
6.1 KiB
Python
156 lines
6.1 KiB
Python
import base64
|
|
|
|
from amqp.adapter.data_message_factory import DataMessageFactory
|
|
from amqp.adapter.logging_utils import logging_error, logging_info
|
|
from amqp.adapter.serializer import map_as_string, parse_map_string
|
|
from amqp.adapter.trace_info_adapter import TraceInfoAdapter
|
|
from amqp.model.model import ServiceMessage
|
|
from amqp.model.service_message_type import ServiceMessageType
|
|
from amqp.model.snowflake_id import SnowflakeId
|
|
|
|
RS = "|"
|
|
SPLIT_RS = r"\|"
|
|
|
|
|
|
INVALID_MESSAGE = ServiceMessage() # default values mean invalid message
|
|
|
|
|
|
class ServiceMessageFactory:
|
|
"""
|
|
build/serialize/deserialize CleverMicro Service Message class
|
|
"""
|
|
|
|
def __init__(self, name: str):
|
|
self.process_name = name
|
|
|
|
def to_string(self, message: ServiceMessage) -> str:
|
|
"""
|
|
Convert ServiceMessage to human-readable format
|
|
:param message: ServiceMessage object
|
|
:return: as string
|
|
"""
|
|
if message is not None:
|
|
return (
|
|
f"ServiceMessage{{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
|
|
) -> ServiceMessage:
|
|
"""
|
|
Builds ServiceMessage 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: ServiceMessage object
|
|
"""
|
|
_id = DataMessageFactory.get_instance(1).generate_snowflake_id()
|
|
return ServiceMessage(
|
|
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
|
|
) -> ServiceMessage:
|
|
"""
|
|
Builds ServiceMessage 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: ServiceMessage object
|
|
"""
|
|
_id = DataMessageFactory.get_instance(1).generate_snowflake_id()
|
|
return ServiceMessage(
|
|
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) -> ServiceMessage:
|
|
"""
|
|
Deserialize ServiceMessage from byte array serialized form.
|
|
:param input_bytes: serialized ServiceMessage
|
|
:return: ServiceMessage object represented by the input.
|
|
"""
|
|
logging_info(f"ServiceMessage >>> [{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 ServiceMessage(
|
|
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: ServiceMessage) -> bytes:
|
|
"""
|
|
Serialize ServiceMessage to byte array.
|
|
:param message: ServiceMessage 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: ServiceMessage) -> 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}"
|