com.microsoft.azure.eventhubs.EventData Maven / Gradle / Ivy
Show all versions of azure-eventhubs Show documentation
/*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
*/
package com.microsoft.azure.eventhubs;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.message.Message;
import com.microsoft.azure.eventhubs.amqp.AmqpConstants;
/**
* The data structure encapsulating the Event being sent-to and received-from EventHubs.
* Each EventHubs partition can be visualized as a Stream of {@link EventData}.
*
* Serializing a received {@link EventData} with AMQP sections other than ApplicationProperties (with primitive java types) and Data section is not supported.
*
* Here's how AMQP message sections map to {@link EventData}. Here's the reference used for AMQP 1.0 specification: http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-complete-v1.0-os.pdf
*
* i. {@link #getProperties()} - AMQPMessage.ApplicationProperties section
* ii. {@link #getBytes()} - if AMQPMessage.Body has Data section
* iii. {@link #getObject()} - if AMQPMessage.Body has AMQPValue or AMQPSequence sections
*
* While using client libraries released by Microsoft Azure EventHubs, sections (i) and (ii) alone are sufficient.
* Section (iii) is used for advanced scenarios, where the sending application uses third-party AMQP library to send the message to EventHubs and the receiving application
* uses this client library to receive {@link EventData}.
*/
public class EventData implements Serializable {
private static final long serialVersionUID = -5631628195600014255L;
private static final int BODY_DATA_NULL = -1;
transient private Binary bodyData;
transient private Object amqpBody;
private Map properties;
private SystemProperties systemProperties;
private EventData() {
}
/**
* Internal Constructor - intended to be used only by the {@link PartitionReceiver} to Create #EventData out of #Message
*/
@SuppressWarnings("unchecked")
EventData(Message amqpMessage) {
if (amqpMessage == null) {
throw new IllegalArgumentException("amqpMessage cannot be null");
}
final Map messageAnnotations = amqpMessage.getMessageAnnotations().getValue();
final HashMap receiveProperties = new HashMap<>();
for (Map.Entry annotation : messageAnnotations.entrySet()) {
receiveProperties.put(annotation.getKey().toString(), annotation.getValue() != null ? annotation.getValue() : null);
}
if (amqpMessage.getProperties() != null) {
if (amqpMessage.getMessageId() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_MESSAGE_ID, amqpMessage.getMessageId());
if (amqpMessage.getUserId() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_USER_ID, amqpMessage.getUserId());
if (amqpMessage.getAddress() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_TO, amqpMessage.getAddress());
if (amqpMessage.getSubject() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_SUBJECT, amqpMessage.getSubject());
if (amqpMessage.getReplyTo() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO, amqpMessage.getReplyTo());
if (amqpMessage.getCorrelationId() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CORRELATION_ID, amqpMessage.getCorrelationId());
if (amqpMessage.getContentType() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE, amqpMessage.getContentType());
if (amqpMessage.getContentEncoding() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING, amqpMessage.getContentEncoding());
if (amqpMessage.getProperties().getAbsoluteExpiryTime() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME, amqpMessage.getExpiryTime());
if (amqpMessage.getProperties().getCreationTime() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CREATION_TIME, amqpMessage.getCreationTime());
if (amqpMessage.getGroupId() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_ID, amqpMessage.getGroupId());
if (amqpMessage.getProperties().getGroupSequence() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE, amqpMessage.getGroupSequence());
if (amqpMessage.getReplyToGroupId() != null)
receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID, amqpMessage.getReplyToGroupId());
}
this.systemProperties = new SystemProperties(receiveProperties);
this.properties = amqpMessage.getApplicationProperties() == null ? null
: ((Map) (amqpMessage.getApplicationProperties().getValue()));
Section bodySection = amqpMessage.getBody();
if (bodySection != null) {
if (bodySection instanceof Data) {
this.bodyData = ((Data) bodySection).getValue();
this.amqpBody = this.bodyData;
} else if (bodySection instanceof AmqpValue) {
this.amqpBody = ((AmqpValue) bodySection).getValue();
} else if (bodySection instanceof AmqpSequence) {
this.amqpBody = ((AmqpSequence) bodySection).getValue();
}
}
amqpMessage.clear();
}
/**
* Construct EventData to Send to EventHubs.
* Typical pattern to create a Sending EventData is:
*
* i. Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
* ii. If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
*
* Sample Code:
*
* EventData eventData = new EventData(telemetryEventBytes);
* eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
* partitionSender.Send(eventData);
*
*
* @param data the actual payload of data in bytes to be Sent to EventHubs.
* @see EventHubClient#createFromConnectionString(String)
*/
public EventData(byte[] data) {
this();
if (data == null) {
throw new IllegalArgumentException("data cannot be null");
}
this.bodyData = new Binary(data);
}
/**
* Construct EventData to Send to EventHubs.
* Typical pattern to create a Sending EventData is:
*
* i. Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
* ii. If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
*
* Illustration:
*
{@code
* EventData eventData = new EventData(telemetryEventBytes, offset, length);
* eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
* partitionSender.Send(eventData);
* }
*
* @param data the byte[] where the payload of the Event to be sent to EventHubs is present
* @param offset Offset in the byte[] to read from ; inclusive index
* @param length length of the byte[] to be read, starting from offset
* @see EventHubClient#createFromConnectionString(String)
*/
public EventData(byte[] data, final int offset, final int length) {
this();
if (data == null) {
throw new IllegalArgumentException("data cannot be null");
}
this.bodyData = new Binary(data, offset, length);
}
/**
* Construct EventData to Send to EventHubs.
* Typical pattern to create a Sending EventData is:
*
* i. Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
* ii. If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
*
* Illustration:
*
{@code
* EventData eventData = new EventData(telemetryEventByteBuffer);
* eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
* partitionSender.Send(eventData);
* }
*
* @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs
* @see EventHubClient#createFromConnectionString(String)
*/
public EventData(ByteBuffer buffer) {
this();
if (buffer == null) {
throw new IllegalArgumentException("data cannot be null");
}
this.bodyData = Binary.create(buffer);
}
/**
* Use this method only if, the sender could be sending messages using third-party AMQP libraries.
* If all the senders of EventHub use client libraries released and maintained by Microsoft Azure EventHubs, use {@link #getBytes()} method.
*
Get the value of AMQP messages' Body section on the received {@link EventData}.
*
If the AMQP message Body is always guaranteed to have Data section, use {@link #getBytes()} method.
*
* @return returns the Object which could represent either Data or AmqpValue or AmqpSequence.
*
{@link Binary} if the Body is Data section
*
{@link List} if the Body is AmqpSequence
*
package org.apache.qpid.proton.amqp contains various AMQP types that could be returned.
*/
public Object getObject() {
return this.amqpBody;
}
/**
* Get Actual Payload/Data wrapped by EventData.
* This is the underlying array and should be used in conjunction with {@link #getBodyOffset()} and {@link #getBodyLength()}.
*
* @return byte[] of the actual data
null if the body of the AMQP message doesn't have Data section
* @deprecated use {@link #getBytes()}
*/
@Deprecated
public byte[] getBody() {
return this.bodyData == null ? null : this.bodyData.getArray();
}
/**
* Get the offset of the current Payload/Data in the byte array returned by {@link #getBody()}.
*
* @return returns the byte[] of the actual data
* @see #getBodyLength()
* @see #getBody()
* @deprecated use {@link #getBytes()}
*/
@Deprecated
public int getBodyOffset() {
return this.bodyData == null ? 0 : this.bodyData.getArrayOffset();
}
/**
* Get the length of the Actual Payload/Data in the byte array returned by {@link #getBody()}.
*
* @return returns the byte[] of the actual data
* @see #getBody()
* @see #getBodyOffset()
* @deprecated use {@link #getBytes()}
*/
@Deprecated
public int getBodyLength() {
return this.bodyData == null ? 0 : this.bodyData.getLength();
}
/**
* Get Actual Payload/Data wrapped by EventData.
*
* @return byte[] of the actual data
*
null if the body of the message has other inter-operable AMQP messages, whose body does not represent byte[].
* In that case use {@link #getObject()}.
*/
public byte[] getBytes() {
if (this.bodyData == null)
return null;
return this.bodyData.getArray();
}
/**
* Application property bag
*
* @return returns Application properties
*/
public Map getProperties() {
if (this.properties == null) {
this.properties = new HashMap<>();
}
return this.properties;
}
/**
* Set Application Properties
*
* @param applicationProperties the Application Properties bag
* @deprecated use {@link #getProperties()} and add properties to the bag.
*/
@Deprecated
public void setProperties(final Map applicationProperties) {
this.properties = applicationProperties;
}
/**
* SystemProperties that are populated by EventHubService.
* As these are populated by Service, they are only present on a Received EventData.
*
Usage:
*
* final String offset = eventData.getSystemProperties().getOffset();
*
*
* @return an encapsulation of all SystemProperties appended by EventHubs service into EventData.
* null
if the {@link #EventData()} is not received and is created by the public constructors.
* @see SystemProperties#getOffset
* @see SystemProperties#getSequenceNumber
* @see SystemProperties#getPartitionKey
* @see SystemProperties#getEnqueuedTime
*/
public SystemProperties getSystemProperties() {
return this.systemProperties;
}
// This is intended to be used while sending EventData - so EventData.SystemProperties will not be copied over to the AmqpMessage
Message toAmqpMessage() {
final Message amqpMessage = Proton.message();
if (this.properties != null && !this.properties.isEmpty()) {
final ApplicationProperties applicationProperties = new ApplicationProperties(this.properties);
amqpMessage.setApplicationProperties(applicationProperties);
}
if (this.systemProperties != null && !this.systemProperties.isEmpty()) {
for (Map.Entry systemProperty : this.systemProperties.entrySet()) {
final String propertyName = systemProperty.getKey();
if (!EventDataUtil.RESERVED_SYSTEM_PROPERTIES.contains(propertyName)) {
if (AmqpConstants.RESERVED_PROPERTY_NAMES.contains(propertyName))
switch (propertyName) {
case AmqpConstants.AMQP_PROPERTY_MESSAGE_ID:
amqpMessage.setMessageId(systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_USER_ID:
amqpMessage.setUserId((byte[]) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_TO:
amqpMessage.setAddress((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_SUBJECT:
amqpMessage.setSubject((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_REPLY_TO:
amqpMessage.setReplyTo((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_CORRELATION_ID:
amqpMessage.setCorrelationId(systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE:
amqpMessage.setContentType((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING:
amqpMessage.setContentEncoding((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME:
amqpMessage.setExpiryTime((long) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_CREATION_TIME:
amqpMessage.setCreationTime((long) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_GROUP_ID:
amqpMessage.setGroupId((String) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE:
amqpMessage.setGroupSequence((long) systemProperty.getValue());
break;
case AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID:
amqpMessage.setReplyToGroupId((String) systemProperty.getValue());
break;
default:
throw new RuntimeException("unreachable");
}
else {
final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null)
? new MessageAnnotations(new HashMap<>())
: amqpMessage.getMessageAnnotations();
messageAnnotations.getValue().put(Symbol.getSymbol(systemProperty.getKey()), systemProperty.getValue());
amqpMessage.setMessageAnnotations(messageAnnotations);
}
}
}
}
if (this.bodyData != null) {
amqpMessage.setBody(new Data(this.bodyData));
} else if (this.amqpBody != null) {
if (this.amqpBody instanceof List) {
amqpMessage.setBody(new AmqpSequence((List) this.amqpBody));
} else {
amqpMessage.setBody(new AmqpValue(this.amqpBody));
}
}
return amqpMessage;
}
Message toAmqpMessage(final String partitionKey) {
final Message amqpMessage = this.toAmqpMessage();
final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null)
? new MessageAnnotations(new HashMap<>())
: amqpMessage.getMessageAnnotations();
messageAnnotations.getValue().put(AmqpConstants.PARTITION_KEY, partitionKey);
amqpMessage.setMessageAnnotations(messageAnnotations);
return amqpMessage;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(this.bodyData == null ? BODY_DATA_NULL : this.bodyData.getLength());
if (this.bodyData != null)
out.write(this.bodyData.getArray(), this.bodyData.getArrayOffset(), this.bodyData.getLength());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
final int length = in.readInt();
if (length != BODY_DATA_NULL) {
final byte[] data = new byte[length];
in.readFully(data, 0, length);
this.bodyData = new Binary(data, 0, length);
}
}
public static class SystemProperties extends HashMap {
private static final long serialVersionUID = -2827050124966993723L;
public SystemProperties(final HashMap map) {
super(Collections.unmodifiableMap(map));
}
public String getOffset() {
return this.getSystemProperty(AmqpConstants.OFFSET_ANNOTATION_NAME);
}
public String getPartitionKey() {
return this.getSystemProperty(AmqpConstants.PARTITION_KEY_ANNOTATION_NAME);
}
public Instant getEnqueuedTime() {
final Date enqueuedTimeValue = this.getSystemProperty(AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME);
return enqueuedTimeValue != null ? enqueuedTimeValue.toInstant() : null;
}
public long getSequenceNumber() {
return this.getSystemProperty(AmqpConstants.SEQUENCE_NUMBER_ANNOTATION_NAME);
}
public String getPublisher() {
return this.getSystemProperty(AmqpConstants.PUBLISHER_ANNOTATION_NAME);
}
@SuppressWarnings("unchecked")
private T getSystemProperty(final String key) {
if (this.containsKey(key)) {
return (T) (this.get(key));
}
return null;
}
}
}