components-contrib/pubsub/azure/servicebus/message.go

283 lines
8.9 KiB
Go

/*
Copyright 2021 The Dapr Authors
Licensed 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 servicebus
import (
"encoding/base64"
"fmt"
"net/http"
"strconv"
"time"
azservicebus "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus"
"github.com/google/uuid"
contribMetadata "github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/pubsub"
)
const (
// MessageIDMetadataKey defines the metadata key for the message id.
MessageIDMetadataKey = "MessageId" // read, write.
// CorrelationIDMetadataKey defines the metadata key for the correlation id.
CorrelationIDMetadataKey = "CorrelationId" // read, write.
// SessionIDMetadataKey defines the metadata key for the session id.
SessionIDMetadataKey = "SessionId" // read, write.
// LabelMetadataKey defines the metadata key for the label.
LabelMetadataKey = "Label" // read, write.
// ReplyToMetadataKey defines the metadata key for the reply to value.
ReplyToMetadataKey = "ReplyTo" // read, write.
// ToMetadataKey defines the metadata key for the to value.
ToMetadataKey = "To" // read, write.
// PartitionKeyMetadataKey defines the metadata key for the partition key.
PartitionKeyMetadataKey = "PartitionKey" // read, write.
// ContentTypeMetadataKey defines the metadata key for the content type.
ContentTypeMetadataKey = "ContentType" // read, write.
// DeliveryCountMetadataKey defines the metadata key for the delivery count.
DeliveryCountMetadataKey = "DeliveryCount" // read.
// LockedUntilUtcMetadataKey defines the metadata key for the locked until utc value.
LockedUntilUtcMetadataKey = "LockedUntilUtc" // read.
// LockTokenMetadataKey defines the metadata key for the lock token.
LockTokenMetadataKey = "LockToken" // read.
// EnqueuedTimeUtcMetadataKey defines the metadata key for the enqueued time utc value.
EnqueuedTimeUtcMetadataKey = "EnqueuedTimeUtc" // read.
// SequenceNumberMetadataKey defines the metadata key for the sequence number.
SequenceNumberMetadataKey = "SequenceNumber" // read.
// ScheduledEnqueueTimeUtcMetadataKey defines the metadata key for the scheduled enqueue time utc value.
ScheduledEnqueueTimeUtcMetadataKey = "ScheduledEnqueueTimeUtc" // read, write.
// ReplyToSessionID defines the metadata key for the reply to session id.
ReplyToSessionID = "ReplyToSessionId" // read, write.
)
func NewPubsubMessageFromASBMessage(asbMsg *azservicebus.ReceivedMessage, topic string) (*pubsub.NewMessage, error) {
pubsubMsg := &pubsub.NewMessage{
Topic: topic,
Data: asbMsg.Body,
}
pubsubMsg.Metadata = addMessageAttributesToMetadata(pubsubMsg.Metadata, asbMsg)
return pubsubMsg, nil
}
func NewBulkMessageEntryFromASBMessage(asbMsg *azservicebus.ReceivedMessage) (pubsub.BulkMessageEntry, error) {
entryId, err := uuid.NewRandom() //nolint:stylecheck
if err != nil {
return pubsub.BulkMessageEntry{}, err
}
bulkMsgEntry := pubsub.BulkMessageEntry{
EntryId: entryId.String(),
Event: asbMsg.Body,
}
bulkMsgEntry.Metadata = addMessageAttributesToMetadata(bulkMsgEntry.Metadata, asbMsg)
return bulkMsgEntry, nil
}
func addMessageAttributesToMetadata(metadata map[string]string, asbMsg *azservicebus.ReceivedMessage) map[string]string {
if metadata == nil {
metadata = map[string]string{}
}
addToMetadata := func(metadata map[string]string, key, value string) {
metadata["metadata."+key] = value
}
if asbMsg.MessageID != "" {
addToMetadata(metadata, MessageIDMetadataKey, asbMsg.MessageID)
}
if asbMsg.SessionID != nil {
addToMetadata(metadata, SessionIDMetadataKey, *asbMsg.SessionID)
}
if asbMsg.CorrelationID != nil && *asbMsg.CorrelationID != "" {
addToMetadata(metadata, CorrelationIDMetadataKey, *asbMsg.CorrelationID)
}
if asbMsg.Subject != nil && *asbMsg.Subject != "" {
addToMetadata(metadata, LabelMetadataKey, *asbMsg.Subject)
}
if asbMsg.ReplyTo != nil && *asbMsg.ReplyTo != "" {
addToMetadata(metadata, ReplyToMetadataKey, *asbMsg.ReplyTo)
}
if asbMsg.To != nil && *asbMsg.To != "" {
addToMetadata(metadata, ToMetadataKey, *asbMsg.To)
}
if asbMsg.ContentType != nil && *asbMsg.ContentType != "" {
addToMetadata(metadata, ContentTypeMetadataKey, *asbMsg.ContentType)
}
if asbMsg.LockToken != [16]byte{} {
addToMetadata(metadata, LockTokenMetadataKey, base64.StdEncoding.EncodeToString(asbMsg.LockToken[:]))
}
// Always set delivery count.
addToMetadata(metadata, DeliveryCountMetadataKey, strconv.FormatInt(int64(asbMsg.DeliveryCount), 10))
if asbMsg.EnqueuedTime != nil {
// Preserve RFC2616 time format.
addToMetadata(metadata, EnqueuedTimeUtcMetadataKey, asbMsg.EnqueuedTime.UTC().Format(http.TimeFormat))
}
if asbMsg.SequenceNumber != nil {
addToMetadata(metadata, SequenceNumberMetadataKey, strconv.FormatInt(*asbMsg.SequenceNumber, 10))
}
if asbMsg.ScheduledEnqueueTime != nil {
// Preserve RFC2616 time format.
addToMetadata(metadata, ScheduledEnqueueTimeUtcMetadataKey, asbMsg.ScheduledEnqueueTime.UTC().Format(http.TimeFormat))
}
if asbMsg.PartitionKey != nil {
addToMetadata(metadata, PartitionKeyMetadataKey, *asbMsg.PartitionKey)
}
if asbMsg.LockedUntil != nil {
// Preserve RFC2616 time format.
addToMetadata(metadata, LockedUntilUtcMetadataKey, asbMsg.LockedUntil.UTC().Format(http.TimeFormat))
}
return metadata
}
// NewASBMessageFromPubsubRequest builds a new Azure Service Bus message from a PublishRequest.
func NewASBMessageFromPubsubRequest(req *pubsub.PublishRequest) (*azservicebus.Message, error) {
asbMsg := &azservicebus.Message{
Body: req.Data,
}
err := addMetadataToMessage(asbMsg, req.Metadata)
return asbMsg, err
}
// NewASBMessageFromBulkMessageEntry builds a new Azure Service Bus message from a BulkMessageEntry.
func NewASBMessageFromBulkMessageEntry(entry pubsub.BulkMessageEntry) (*azservicebus.Message, error) {
asbMsg := &azservicebus.Message{
Body: entry.Event,
ContentType: &entry.ContentType,
}
err := addMetadataToMessage(asbMsg, entry.Metadata)
return asbMsg, err
}
func addMetadataToMessage(asbMsg *azservicebus.Message, metadata map[string]string) error {
// Common properties.
ttl, ok, _ := contribMetadata.TryGetTTL(metadata)
if ok {
asbMsg.TimeToLive = &ttl
}
// Azure Service Bus specific properties.
// reference: https://docs.microsoft.com/en-us/rest/api/servicebus/message-headers-and-properties#message-headers
msgID, ok, _ := tryGetString(metadata, MessageIDMetadataKey)
if ok {
asbMsg.MessageID = &msgID
}
correlationID, ok, _ := tryGetString(metadata, CorrelationIDMetadataKey)
if ok {
asbMsg.CorrelationID = &correlationID
}
sessionID, okSessionID, _ := tryGetString(metadata, SessionIDMetadataKey)
if okSessionID {
asbMsg.SessionID = &sessionID
}
label, ok, _ := tryGetString(metadata, LabelMetadataKey)
if ok {
asbMsg.Subject = &label
}
replyTo, ok, _ := tryGetString(metadata, ReplyToMetadataKey)
if ok {
asbMsg.ReplyTo = &replyTo
}
to, ok, _ := tryGetString(metadata, ToMetadataKey)
if ok {
asbMsg.To = &to
}
partitionKey, ok, _ := tryGetString(metadata, PartitionKeyMetadataKey)
if ok {
if okSessionID && partitionKey != sessionID {
return fmt.Errorf("session id %s and partition key %s should be equal when both present", sessionID, partitionKey)
}
asbMsg.PartitionKey = &partitionKey
}
contentType, ok, _ := tryGetString(metadata, ContentTypeMetadataKey)
if ok {
asbMsg.ContentType = &contentType
}
scheduledEnqueueTime, ok, _ := tryGetScheduledEnqueueTime(metadata)
if ok {
asbMsg.ScheduledEnqueueTime = scheduledEnqueueTime
}
return nil
}
// UpdateASBBatchMessageWithBulkPublishRequest updates the batch message with messages from the bulk publish request.
func UpdateASBBatchMessageWithBulkPublishRequest(asbMsgBatch *azservicebus.MessageBatch, req *pubsub.BulkPublishRequest) error {
// Add entries from bulk request to batch.
for _, entry := range req.Entries {
asbMsg, err := NewASBMessageFromBulkMessageEntry(entry)
if err != nil {
return err
}
err = asbMsgBatch.AddMessage(asbMsg, nil)
if err != nil {
return err
}
}
return nil
}
func tryGetString(props map[string]string, key string) (string, bool, error) {
if val, ok := props[key]; ok && val != "" {
return val, true, nil
}
return "", false, nil
}
func tryGetScheduledEnqueueTime(props map[string]string) (*time.Time, bool, error) {
if val, ok := props[ScheduledEnqueueTimeUtcMetadataKey]; ok && val != "" {
timeVal, err := time.Parse(http.TimeFormat, val)
if err != nil {
return nil, false, err
}
return &timeVal, true, nil
}
return nil, false, nil
}