All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.messaging.servicebus.ServiceBusReceivedMessage Maven / Gradle / Ivy

There is a newer version: 7.18.0-beta.1
Show newest version
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.messaging.servicebus;

import com.azure.core.amqp.AmqpMessageConstant;
import com.azure.core.amqp.models.AmqpAddress;
import com.azure.core.amqp.models.AmqpAnnotatedMessage;
import com.azure.core.amqp.models.AmqpMessageBody;
import com.azure.core.amqp.models.AmqpMessageBodyType;
import com.azure.core.amqp.models.AmqpMessageId;
import com.azure.core.util.BinaryData;
import com.azure.core.util.Context;
import com.azure.core.util.logging.ClientLogger;
import com.azure.messaging.servicebus.models.ServiceBusReceiveMode;

import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import static com.azure.core.amqp.AmqpMessageConstant.DEAD_LETTER_DESCRIPTION_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.DEAD_LETTER_REASON_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.DEAD_LETTER_SOURCE_KEY_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.ENQUEUED_SEQUENCE_NUMBER_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.ENQUEUED_TIME_UTC_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.LOCKED_UNTIL_KEY_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.PARTITION_KEY_ANNOTATION_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.SCHEDULED_ENQUEUE_UTC_TIME_NAME;
import static com.azure.core.amqp.AmqpMessageConstant.SEQUENCE_NUMBER_ANNOTATION_NAME;

/**
 * The data structure encapsulating the message received from Service Bus. The message structure is discussed in detail
 * in the product
 * documentation.
 *
 * @see ServiceBusReceiverAsyncClient#receiveMessages()
 * @see ServiceBusReceiverClient#receiveMessages(int)
 * @see Service Bus
 *      message payloads
 * @see AMQP 1.0 specification
 *     
 */
public final class ServiceBusReceivedMessage {
    private final ClientLogger logger = new ClientLogger(ServiceBusReceivedMessage.class);

    private final AmqpAnnotatedMessage amqpAnnotatedMessage;
    private UUID lockToken;
    private boolean isSettled = false;
    private Context context;

    ServiceBusReceivedMessage(BinaryData body) {
        Objects.requireNonNull(body, "'body' cannot be null.");
        amqpAnnotatedMessage = new AmqpAnnotatedMessage(AmqpMessageBody.fromData(body.toBytes()));
        context = Context.NONE;
    }

    /**
     * Gets the set of free-form {@link ServiceBusReceivedMessage} properties which may be used for passing metadata
     * associated with the {@link ServiceBusReceivedMessage} during Service Bus operations. A common use-case for
     * {@code properties()} is to associate serialization hints for the {@link #getBody()} as an aid to consumers
     * who wish to deserialize the binary data.
     *
     * @return Application properties associated with this {@link ServiceBusReceivedMessage}.
     * @see ServiceBusMessage#getApplicationProperties()
     */
    public Map getApplicationProperties() {
        return amqpAnnotatedMessage.getApplicationProperties();
    }

    /**
     * Gets the payload wrapped by the {@link ServiceBusReceivedMessage}.
     *
     * 

The {@link BinaryData} wraps byte array and is an abstraction over many different ways it can be represented. * It provides convenience APIs to serialize/deserialize the object.

* *

* If the means for deserializing the raw data is not apparent to consumers, a common technique is to make use of * {@link #getApplicationProperties()} when creating the event, to associate serialization hints as an aid to * consumers who wish to deserialize the binary data. *

* * @return Binary data representing the payload. * @see ServiceBusMessage#getBody() */ public BinaryData getBody() { final AmqpMessageBodyType bodyType = amqpAnnotatedMessage.getBody().getBodyType(); switch (bodyType) { case DATA: final byte[] payload = amqpAnnotatedMessage.getBody().getFirstData(); return BinaryData.fromBytes(payload); case SEQUENCE: case VALUE: throw logger.logExceptionAsError(new UnsupportedOperationException( "This body type not is supported: " + bodyType)); default: throw logger.logExceptionAsError(new IllegalStateException("Body type not valid: " + bodyType)); } } /** * Gets the content type of the message. * *

* Optionally describes the payload of the message, with a descriptor following the format of RFC2045, Section 5, * for example "application/json". *

* @return The contentType of the {@link ServiceBusReceivedMessage}. * @see ServiceBusMessage#getContentType() */ public String getContentType() { return amqpAnnotatedMessage.getProperties().getContentType(); } /** * Gets a correlation identifier. *

* Allows an application to specify a context for the message for the purposes of correlation, for example * reflecting the MessageId of a message that is being replied to. *

* * @return The correlation id of this message. * * @see ServiceBusMessage#getCorrelationId() * @see Message * Routing and Correlation */ public String getCorrelationId() { String correlationId = null; AmqpMessageId amqpCorrelationId = amqpAnnotatedMessage.getProperties().getCorrelationId(); if (amqpCorrelationId != null) { correlationId = amqpCorrelationId.toString(); } return correlationId; } /** * Gets the description for a message that has been dead-lettered. * * @return The description for a message that has been dead-lettered; {@code null} otherwise. * * @see * Dead-letter queues */ public String getDeadLetterErrorDescription() { return getStringValue(amqpAnnotatedMessage.getApplicationProperties(), DEAD_LETTER_DESCRIPTION_ANNOTATION_NAME.getValue()); } /** * Gets the reason a message was dead-lettered. * * @return The reason a message was dead-lettered; {@code null} otherwise. * * @see * Dead-letter queues */ public String getDeadLetterReason() { return getStringValue(amqpAnnotatedMessage.getApplicationProperties(), DEAD_LETTER_REASON_ANNOTATION_NAME.getValue()); } /** * Gets the name of the queue or subscription that this message was enqueued on, before it was dead-lettered. * *

This value is only set in messages that have been dead-lettered and subsequently auto-forwarded from the * dead-letter queue to another entity.

* * @return The entity in which the message was dead-lettered; {@code null} otherwise. * * @see * Dead-letter queues */ public String getDeadLetterSource() { return getStringValue(amqpAnnotatedMessage.getMessageAnnotations(), DEAD_LETTER_SOURCE_KEY_ANNOTATION_NAME.getValue()); } /** * Gets the number of the times this message was delivered to clients. * *

The count is incremented when a message lock expires, or the message is explicitly abandoned by the receiver. *

* * @return delivery count of this message. * * @see Message * transfers, locks, and settlement */ public long getDeliveryCount() { return amqpAnnotatedMessage.getHeader().getDeliveryCount(); } /** * Gets the enqueued sequence number assigned to a message by Service Bus. *

* The sequence number is a unique 64-bit integer first assigned to a message as it is accepted at its original * point of submission. *

* * @return The enqueued sequence number of this message * * @see Message Sequencing and * Timestamps */ public long getEnqueuedSequenceNumber() { return getLongValue(amqpAnnotatedMessage.getMessageAnnotations(), ENQUEUED_SEQUENCE_NUMBER_ANNOTATION_NAME.getValue()); } /** * Gets the datetime at which this message was enqueued in Azure Service Bus. *

* The UTC datetime at which the message has been accepted and stored in the entity. For scheduled messages, this * reflects the time when the message was activated. This value can be used as an authoritative and neutral arrival * time indicator when the receiver does not want to trust the sender's clock. *

* * @return The datetime at which the message was enqueued in Azure Service Bus. * * @see Message Sequencing and * Timestamps */ public OffsetDateTime getEnqueuedTime() { return getOffsetDateTimeValue(amqpAnnotatedMessage.getMessageAnnotations(), ENQUEUED_TIME_UTC_ANNOTATION_NAME.getValue()); } /** * Gets the datetime at which this message will expire. *

* The value is the UTC datetime for when the message is scheduled for removal and will no longer available for * retrieval from the entity. Expiry is controlled by the {@link #getTimeToLive() time-to-live} property. This * property is computed from {@link #getEnqueuedTime() enqueued time} plus {@link #getTimeToLive() time-to-live}. *

* * @return The {@link OffsetDateTime} at which this message expires. * * @see Message Expiration */ public OffsetDateTime getExpiresAt() { final Duration timeToLive = getTimeToLive(); final OffsetDateTime enqueuedTime = getEnqueuedTime(); return enqueuedTime != null && timeToLive != null ? enqueuedTime.plus(timeToLive) : null; } /** * Gets the lock token for the current message. * *

* The lock token is a reference to the lock that is being held by the broker in * {@link ServiceBusReceiveMode#PEEK_LOCK} mode. Locks are used to explicitly settle messages as explained in the * product * documentation. The token can also be used to pin the lock permanently through the * Deferral API and take the * message out of the regular delivery state flow. This property is read-only. * * @return The lock-token for this message. {@code null} for messages retrieved via * {@link ServiceBusReceiveMode#RECEIVE_AND_DELETE} mode. * * @see Message * transfers, locks, and settlement */ public String getLockToken() { return lockToken != null ? lockToken.toString() : null; } /** * Gets the datetime at which the lock of this message expires. * *

* For messages retrieved under a lock (peek-lock receive mode, not pre-settled) this property reflects the UTC * datetime until which the message is held locked in the queue/subscription. When the lock expires, the * {@link #getDeliveryCount() delivery count} is incremented and the message is again available for retrieval. * This property is read-only. *

* * @return the datetime at which the lock of this message expires if the message is received using {@link * ServiceBusReceiveMode#PEEK_LOCK} mode. Otherwise it returns null. * * @see Message * transfers, locks, and settlement */ public OffsetDateTime getLockedUntil() { return getOffsetDateTimeValue(amqpAnnotatedMessage.getMessageAnnotations(), LOCKED_UNTIL_KEY_ANNOTATION_NAME.getValue()); } /** * Gets the identifier for the message. * *

* The message identifier is an application-defined value that uniquely identifies the message and its payload. The * identifier is a free-form string and can reflect a GUID or an identifier derived from the application context. If * enabled, the * duplicate detection * feature identifies and removes second and further submissions of messages with the same {@code messageId}. *

* * @return Id of the {@link ServiceBusReceivedMessage}. * @see ServiceBusMessage#getMessageId() */ public String getMessageId() { String messageId = null; AmqpMessageId amqpMessageId = amqpAnnotatedMessage.getProperties().getMessageId(); if (amqpMessageId != null) { messageId = amqpMessageId.toString(); } return messageId; } /** * Gets the partition key for sending a message to a partitioned entity. *

* For partitioned * entities, setting this value enables assigning related messages to the same internal partition, so that * submission sequence order is correctly recorded. The partition is chosen by a hash function over this value and * cannot be chosen directly. For session-aware entities, the {@link #getSessionId() sessionId} property overrides * this value. * * @return The partition key of this message. * * @see Partitioned * entities * @see ServiceBusMessage#getPartitionKey() */ public String getPartitionKey() { return getStringValue(amqpAnnotatedMessage.getMessageAnnotations(), PARTITION_KEY_ANNOTATION_NAME.getValue()); } /** * The representation of message as defined by AMQP protocol. * * @see * Amqp Message Format. * * @return the {@link AmqpAnnotatedMessage} representing AMQP message. */ public AmqpAnnotatedMessage getRawAmqpMessage() { return amqpAnnotatedMessage; } /** * Gets the address of an entity to send replies to. *

* This optional and application-defined value is a standard way to express a reply path to the receiver of the * message. When a sender expects a reply, it sets the value to the absolute or relative path of the queue or topic * it expects the reply to be sent to. * * @return ReplyTo property value of this message * * @see Message * Routing and Correlation */ public String getReplyTo() { String replyTo = null; AmqpAddress amqpAddress = amqpAnnotatedMessage.getProperties().getReplyTo(); if (amqpAddress != null) { replyTo = amqpAddress.toString(); } return replyTo; } /** * Gets or sets a session identifier augmenting the {@link #getReplyTo() ReplyTo} address. *

* This value augments the ReplyTo information and specifies which SessionId should be set for the reply when sent * to the reply entity. * * @return ReplyToSessionId property value of this message * * @see Message * Routing and Correlation */ public String getReplyToSessionId() { return amqpAnnotatedMessage.getProperties().getReplyToGroupId(); } /** * Gets the scheduled enqueue time of this message. *

* This value is used for delayed message availability. The message is safely added to the queue, but is not * considered active and therefore not retrievable until the scheduled enqueue time. Mind that the message may not * be activated (enqueued) at the exact given datetime; the actual activation time depends on the queue's workload * and its state. *

* * @return the datetime at which the message will be enqueued in Azure Service Bus * * @see Message Sequencing and * Timestamps */ public OffsetDateTime getScheduledEnqueueTime() { return getOffsetDateTimeValue(amqpAnnotatedMessage.getMessageAnnotations(), SCHEDULED_ENQUEUE_UTC_TIME_NAME.getValue()); } /** * Gets the unique number assigned to a message by Service Bus. *

* The sequence number is a unique 64-bit integer assigned to a message as it is accepted and stored by the broker * and functions as its true identifier. For partitioned entities, the topmost 16 bits reflect the partition * identifier. Sequence numbers monotonically increase and are gapless. They roll over to 0 when the 48-64 bit range * is exhausted. This property is read-only. * * @return sequence number of this message * * @see Message Sequencing and * Timestamps */ public long getSequenceNumber() { return getLongValue(amqpAnnotatedMessage.getMessageAnnotations(), SEQUENCE_NUMBER_ANNOTATION_NAME.getValue()); } /** * Gets the session id of the message. * *

* For session-aware entities, this application-defined value specifies the session affiliation of the message. * Messages with the same session identifier are subject to summary locking and enable exact in-order processing and * demultiplexing. For session-unaware entities, this value is ignored. See Message Sessions. *

* * @return The session id of the {@link ServiceBusReceivedMessage}. * @see ServiceBusMessage#getSessionId() */ public String getSessionId() { return getRawAmqpMessage().getProperties().getGroupId(); } /** * Gets the subject for the message. * *

* This property enables the application to indicate the purpose of the message to the receiver in a standardized * fashion, similar to an email subject line. The mapped AMQP property is "subject". *

* * @return The subject for the message. * @see ServiceBusMessage#getSubject() */ public String getSubject() { return amqpAnnotatedMessage.getProperties().getSubject(); } /** * Gets the duration before this message expires. *

* This value is the relative duration after which the message expires, starting from the datetime the message has * been accepted and stored by the broker, as captured in {@link #getScheduledEnqueueTime()}. When not set * explicitly, the assumed value is the DefaultTimeToLive set for the respective queue or topic. A message-level * TimeToLive value cannot be longer than the entity's DefaultTimeToLive setting and it is silently adjusted if it * does. * * @return Time to live duration of this message * * @see Message Expiration */ public Duration getTimeToLive() { return amqpAnnotatedMessage.getHeader().getTimeToLive(); } /** * Gets the "to" address. *

* This property is reserved for future use in routing scenarios and presently ignored by the broker itself. * Applications can use this value in rule-driven * auto-forward * chaining scenarios to indicate the intended logical destination of the message. *

* * @return "To" property value of this message * @see ServiceBusMessage#getTo() */ public String getTo() { String to = null; AmqpAddress amqpAddress = amqpAnnotatedMessage.getProperties().getTo(); if (amqpAddress != null) { to = amqpAddress.toString(); } return to; } /** * Adds a new key value pair to the existing context on Message. * * @param key The key for this context object * @param value The value for this context object. * * @return The updated {@link ServiceBusMessage}. * @throws NullPointerException if {@code key} or {@code value} is null. */ ServiceBusReceivedMessage addContext(String key, Object value) { Objects.requireNonNull(key, "The 'key' parameter cannot be null."); Objects.requireNonNull(value, "The 'value' parameter cannot be null."); this.context = context.addData(key, value); return this; } /** * Gets whether the message has been settled. * * @return True if the message has been settled, false otherwise. */ boolean isSettled() { return this.isSettled; } /** * Sets a correlation identifier. * * @param correlationId correlation id of this message * * @see #getCorrelationId() */ void setCorrelationId(String correlationId) { AmqpMessageId id = null; if (correlationId != null) { id = new AmqpMessageId(correlationId); } amqpAnnotatedMessage.getProperties().setCorrelationId(id); } /** * Sets the content type of the {@link ServiceBusReceivedMessage}. * * @param contentType of the message. */ void setContentType(String contentType) { amqpAnnotatedMessage.getProperties().setContentType(contentType); } /** * Sets the dead letter description. * * @param deadLetterErrorDescription Dead letter description. */ void setDeadLetterErrorDescription(String deadLetterErrorDescription) { amqpAnnotatedMessage.getApplicationProperties().put(DEAD_LETTER_DESCRIPTION_ANNOTATION_NAME.getValue(), deadLetterErrorDescription); } /** * Sets the dead letter reason. * * @param deadLetterReason Dead letter reason. */ void setDeadLetterReason(String deadLetterReason) { amqpAnnotatedMessage.getApplicationProperties().put(DEAD_LETTER_REASON_ANNOTATION_NAME.getValue(), deadLetterReason); } /** * Sets the name of the queue or subscription that this message was enqueued on, before it was * deadlettered. * * @param deadLetterSource the name of the queue or subscription that this message was enqueued on, * before it was deadlettered. */ void setDeadLetterSource(String deadLetterSource) { amqpAnnotatedMessage.getMessageAnnotations().put(DEAD_LETTER_SOURCE_KEY_ANNOTATION_NAME.getValue(), deadLetterSource); } /** * Sets the number of the times this message was delivered to clients. * * @param deliveryCount the number of the times this message was delivered to clients. */ void setDeliveryCount(long deliveryCount) { amqpAnnotatedMessage.getHeader().setDeliveryCount(deliveryCount); } /** * Sets the message's sequence number. * * @param enqueuedSequenceNumber The message's sequence number. */ void setEnqueuedSequenceNumber(long enqueuedSequenceNumber) { amqpAnnotatedMessage.getMessageAnnotations().put(ENQUEUED_SEQUENCE_NUMBER_ANNOTATION_NAME.getValue(), enqueuedSequenceNumber); } /** * Sets the datetime at which this message was enqueued in Azure Service Bus. * * @param enqueuedTime the datetime at which this message was enqueued in Azure Service Bus. */ void setEnqueuedTime(OffsetDateTime enqueuedTime) { setValue(amqpAnnotatedMessage.getMessageAnnotations(), ENQUEUED_TIME_UTC_ANNOTATION_NAME, enqueuedTime); } /** * Sets whether the message has been settled. * */ void setIsSettled() { this.isSettled = true; } /** * Sets the lock token for the current message. * * @param lockToken the lock token for the current message. */ void setLockToken(UUID lockToken) { this.lockToken = lockToken; } /** * Sets the datetime at which the lock of this message expires. * * @param lockedUntil the datetime at which the lock of this message expires. */ void setLockedUntil(OffsetDateTime lockedUntil) { setValue(amqpAnnotatedMessage.getMessageAnnotations(), LOCKED_UNTIL_KEY_ANNOTATION_NAME, lockedUntil); } /** * Sets the message id. * * @param messageId to be set. */ void setMessageId(String messageId) { AmqpMessageId id = null; if (messageId != null) { id = new AmqpMessageId(messageId); } amqpAnnotatedMessage.getProperties().setMessageId(id); } /** * Sets a partition key for sending a message to a partitioned entity * * @param partitionKey partition key of this message * * @see #getPartitionKey() */ void setPartitionKey(String partitionKey) { amqpAnnotatedMessage.getMessageAnnotations().put(PARTITION_KEY_ANNOTATION_NAME.getValue(), partitionKey); } /** * Sets the scheduled enqueue time of this message. * * @param scheduledEnqueueTime the datetime at which this message should be enqueued in Azure Service Bus. * * @see #getScheduledEnqueueTime() */ void setScheduledEnqueueTime(OffsetDateTime scheduledEnqueueTime) { setValue(amqpAnnotatedMessage.getMessageAnnotations(), SCHEDULED_ENQUEUE_UTC_TIME_NAME, scheduledEnqueueTime); } /** * Sets the unique number assigned to a message by Service Bus. * * @param sequenceNumber the unique number assigned to a message by Service Bus. */ void setSequenceNumber(long sequenceNumber) { amqpAnnotatedMessage.getMessageAnnotations().put(SEQUENCE_NUMBER_ANNOTATION_NAME.getValue(), sequenceNumber); } /** * Sets the session id. * * @param sessionId to be set. */ void setSessionId(String sessionId) { amqpAnnotatedMessage.getProperties().setGroupId(sessionId); } /** * Sets the subject for the message. * * @param subject The subject to set. */ void setSubject(String subject) { amqpAnnotatedMessage.getProperties().setSubject(subject); } /** * Sets the duration of time before this message expires. * * @param timeToLive Time to Live duration of this message * * @see #getTimeToLive() */ void setTimeToLive(Duration timeToLive) { amqpAnnotatedMessage.getHeader().setTimeToLive(timeToLive); } /** * Sets the address of an entity to send replies to. * * @param replyTo ReplyTo property value of this message * * @see #getReplyTo() */ void setReplyTo(String replyTo) { AmqpAddress replyToAddress = null; if (replyTo != null) { replyToAddress = new AmqpAddress(replyTo); } amqpAnnotatedMessage.getProperties().setReplyTo(replyToAddress); } /** * Gets or sets a session identifier augmenting the {@link #getReplyTo() ReplyTo} address. * * @param replyToSessionId ReplyToSessionId property value of this message */ void setReplyToSessionId(String replyToSessionId) { amqpAnnotatedMessage.getProperties().setReplyToGroupId(replyToSessionId); } /** * Sets the "to" address. *

* This property is reserved for future use in routing scenarios and presently ignored by the broker itself. * Applications can use this value in rule-driven * auto-forward * chaining scenarios to indicate the intended logical destination of the message. * * @param to To property value of this message */ void setTo(String to) { AmqpAddress toAddress = null; if (to != null) { toAddress = new AmqpAddress(to); } amqpAnnotatedMessage.getProperties().setTo(toAddress); } /* * Gets String value from given map and null if key does not exists. */ private String getStringValue(Map dataMap, String key) { return (String) dataMap.get(key); } /* * Gets long value from given map and 0 if key does not exists. */ private long getLongValue(Map dataMap, String key) { return dataMap.containsKey(key) ? (long) dataMap.get(key) : 0; } /* * Gets OffsetDateTime value from given map and null if key does not exists. */ private OffsetDateTime getOffsetDateTimeValue(Map dataMap, String key) { return dataMap.containsKey(key) ? ((Date) dataMap.get(key)).toInstant().atOffset(ZoneOffset.UTC) : null; } private void setValue(Map dataMap, AmqpMessageConstant key, OffsetDateTime value) { if (value != null) { amqpAnnotatedMessage.getMessageAnnotations().put(key.getValue(), new Date(value.toInstant().toEpochMilli())); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy