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

com.azure.messaging.eventhubs.EventHubProducerAsyncClient Maven / Gradle / Ivy

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

package com.azure.messaging.eventhubs;

import com.azure.core.amqp.AmqpRetryOptions;
import com.azure.core.amqp.exception.AmqpErrorCondition;
import com.azure.core.amqp.exception.AmqpException;
import com.azure.core.amqp.implementation.AmqpConstants;
import com.azure.core.amqp.implementation.AmqpSendLink;
import com.azure.core.amqp.implementation.ErrorContextProvider;
import com.azure.core.amqp.implementation.MessageSerializer;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
import com.azure.messaging.eventhubs.implementation.EventHubManagementNode;
import com.azure.messaging.eventhubs.models.CreateBatchOptions;
import com.azure.messaging.eventhubs.models.SendOptions;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.message.Message;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

import static com.azure.core.amqp.implementation.RetryUtil.withRetry;
import static com.azure.core.util.FluxUtil.monoError;
import static com.azure.messaging.eventhubs.implementation.ClientConstants.MAX_MESSAGE_LENGTH_BYTES;
import static com.azure.messaging.eventhubs.implementation.ClientConstants.PARTITION_ID_KEY;
import static com.azure.messaging.eventhubs.implementation.ClientConstants.PARTITION_KEY_KEY;
import static com.azure.messaging.eventhubs.implementation.instrumentation.OperationName.GET_EVENT_HUB_PROPERTIES;
import static com.azure.messaging.eventhubs.implementation.instrumentation.OperationName.GET_PARTITION_PROPERTIES;

/**
 * 

An asynchronous producer responsible for transmitting {@link EventData} to a specific Event Hub, grouped * together in batches. Depending on the {@link CreateBatchOptions options} specified when creating an * {@link EventDataBatch}, the events may be automatically routed to an available partition or specific to a partition. * More information and specific recommendations for strategies to use when publishing events is in: * * Distribute events to partitions

* *

Allowing automatic routing of partitions is recommended when:

*
    *
  • The sending of events needs to be highly available.
  • *
  • The event data should be evenly distributed among all available partitions.
  • *
* *

If no partition id is specified, the following rules are used for automatically selecting one:

*
    *
  1. Distribute the events equally amongst all available partitions using a round-robin approach.
  2. *
  3. If a partition becomes unavailable, the Event Hubs service will automatically detect it and forward the * message to another available partition.
  4. *
* *

The examples shown in this document use a credential object named DefaultAzureCredential for authentication, * which is appropriate for most scenarios, including local development and production environments. Additionally, we * recommend using * managed identity * for authentication in production environments. You can find more information on different ways of authenticating and * their corresponding credential types in the * Azure Identity documentation". *

* *

Sample: Construct a {@link EventHubProducerAsyncClient}

* *

The following code sample demonstrates the creation of the asynchronous client * {@link EventHubProducerAsyncClient}. The {@code fullyQualifiedNamespace} is the Event Hubs Namespace's host name. * It is listed under the "Essentials" panel after navigating to the Event Hubs Namespace via Azure Portal.

* * *
 * TokenCredential credential = new DefaultAzureCredentialBuilder().build();
 *
 * // "<<fully-qualified-namespace>>" will look similar to "{your-namespace}.servicebus.windows.net"
 * // "<<event-hub-name>>" will be the name of the Event Hub instance you created inside the Event Hubs namespace.
 * EventHubProducerAsyncClient producer = new EventHubClientBuilder()
 *     .credential("<<fully-qualified-namespace>>", "<<event-hub-name>>",
 *         credential)
 *     .buildAsyncProducerClient();
 * 
* * *

Sample: Create a producer and publish events to any partition

* *

The following code sample demonstrates how to send a set of events to Event Hub. Events are distributed via * automatic routing because no options were set when creating the {@link EventDataBatch} via {@link #createBatch()}. * Using {@link EventDataBatch} is recommended because multiple events can be sent across the underlying connection * with a single message.

* *

{@link #createBatch()} and {@link #send(EventDataBatch)} are non-blocking calls. After setting up the operation, * its async representation is returned. The {@code Mono} must be subscribed to, like the sample below, to start * publishing the event batch.

* * *
 * // Creating a batch without options set, will allow for automatic routing of events to any partition.
 * producer.createBatch().flatMap(batch -> {
 *     batch.tryAdd(new EventData("test-event-1"));
 *     batch.tryAdd(new EventData("test-event-2"));
 *     return producer.send(batch);
 * }).subscribe(unused -> {
 * },
 *     error -> System.err.println("Error occurred while sending batch:" + error),
 *     () -> System.out.println("Send complete."));
 * 
* * *

Sample: Publish events to partition "1"

* *

The following code sample demonstrates how to send a set of events to Event Hub to partition "1". * {@link EventDataBatch} via {@link #createBatch(CreateBatchOptions)}. Partition identifiers can be obtained using * {@link #getPartitionIds()}. Using {@link EventDataBatch} is recommended because multiple events can be sent across * the underlying connection with a single message.

* *

{@link #createBatch(CreateBatchOptions)} and {@link #send(EventDataBatch)} are non-blocking calls. After setting * up the operation, its async representation is returned. The {@code Mono} must be subscribed to, like the * sample below, to start publishing the event batch.

* * *
 * CreateBatchOptions options = new CreateBatchOptions().setPartitionId("1");
 * producer.createBatch(options).flatMap(batch -> {
 *     batch.tryAdd(new EventData("test-event-1"));
 *     batch.tryAdd(new EventData("test-event-2"));
 *     return producer.send(batch);
 * }).subscribe(unused -> {
 * },
 *     error -> System.err.println("Error occurred while sending batch to partition 1:" + error),
 *     () -> System.out.println("Send to partition 1 complete."));
 * 
* * *

Sample: Publish events to the same partition, grouped together using partition key

* *

In the code sample below, all events with the same partition key, "bread" are sent to the same partition. When * {@link CreateBatchOptions#setPartitionId(String)} is specified, it tells the Event Hubs service that these events * belong to the same group and should belong to the same partition. Useful in the case where developers want events * to end up in the same partition, but do not care which partition it ends up in.

* *

{@link #createBatch(CreateBatchOptions)} and {@link #send(EventDataBatch)} are non-blocking calls. After setting * up the operation, its async representation is returned. The {@code Mono} must be subscribed to, like the * sample below, to start publishing the event batch.

* * *
 * CreateBatchOptions options = new CreateBatchOptions().setPartitionKey("bread");
 *
 * producer.createBatch(options).flatMap(batch -> {
 *     batch.tryAdd(new EventData("sourdough"));
 *     batch.tryAdd(new EventData("rye"));
 *     return producer.send(batch);
 * }).subscribe(unused -> {
 * },
 *     error -> System.err.println("Error occurred while sending batch:" + error),
 *     () -> System.out.println("Send complete."));
 * 
* * *

Sample: Publish events using a size-limited {@link EventDataBatch}

* *

In the code sample below, all batches are created with a max size of 256 bytes using * {@link CreateBatchOptions#setMaximumSizeInBytes(int)} is specified. Events inside the batch are automatically * routed because no partition id or partition key are specified.

* *

{@link #createBatch(CreateBatchOptions)} and {@link #send(EventDataBatch)} are non-blocking calls. After setting * up the operation, its async representation is returned. The {@code Mono} must be subscribed to, like the * sample below, to start publishing the event batch.

* * *
 * Flux<EventData> telemetryEvents = Flux.just(firstEvent, secondEvent);
 *
 * // Setting `setMaximumSizeInBytes` when creating a batch, limits the size of that batch.
 * // In this case, all the batches created with these options are limited to 256 bytes.
 * CreateBatchOptions options = new CreateBatchOptions().setMaximumSizeInBytes(256);
 * AtomicReference<EventDataBatch> currentBatch = new AtomicReference<>(
 *     producer.createBatch(options).block());
 *
 * // The sample Flux contains two events, but it could be an infinite stream of telemetry events.
 * Disposable publishingOperation = telemetryEvents.flatMap(event -> {
 *     EventDataBatch batch = currentBatch.get();
 *
 *     if (batch.tryAdd(event)) {
 *         return Mono.empty();
 *     }
 *
 *     // Send the current batch then create another size-limited EventDataBatch and try to fit the event into
 *     // this new batch.
 *     return producer.send(batch).then(
 *         producer.createBatch(options).map(newBatch -> {
 *             currentBatch.set(newBatch);
 *
 *             // Add the event that did not fit in the previous batch.
 *             if (!newBatch.tryAdd(event)) {
 *                 return Mono.error(new IllegalArgumentException(
 *                     "Event was too large to fit in an empty batch. Max size: "
 *                         + newBatch.getMaxSizeInBytes()));
 *             }
 *
 *             return Mono.empty();
 *         }));
 * }).subscribe(unused -> {
 * }, error -> {
 *     System.out.println("Error occurred publishing events: " + error);
 * }, () -> {
 *     System.out.println("Completed publishing operation.");
 * });
 * 
* * * @see com.azure.messaging.eventhubs * @see EventHubClientBuilder * @see EventHubProducerClient To synchronously publish events to an Event Hub, see EventHubProducerClient. */ @ServiceClient(builder = EventHubClientBuilder.class, isAsync = true) public class EventHubProducerAsyncClient implements Closeable { private static final int MAX_PARTITION_KEY_LENGTH = 128; private static final String SENDER_ENTITY_PATH_FORMAT = "%s/Partitions/%s"; private static final SendOptions DEFAULT_SEND_OPTIONS = new SendOptions(); private static final CreateBatchOptions DEFAULT_BATCH_OPTIONS = new CreateBatchOptions(); private static final ClientLogger LOGGER = new ClientLogger(EventHubProducerAsyncClient.class); private final AtomicBoolean isDisposed = new AtomicBoolean(); private final String fullyQualifiedNamespace; private final String eventHubName; private final ConnectionCacheWrapper connectionProcessor; private final AmqpRetryOptions retryOptions; private final EventHubsProducerInstrumentation instrumentation; private final MessageSerializer messageSerializer; private final Scheduler scheduler; private final boolean isSharedConnection; private final Runnable onClientClose; private final String identifier; /** * Creates a new instance of this {@link EventHubProducerAsyncClient} that can send messages to a single partition * when {@link CreateBatchOptions#getPartitionId()} is not null or an empty string. Otherwise, allows the service to * load balance the messages amongst available partitions. */ EventHubProducerAsyncClient(String fullyQualifiedNamespace, String eventHubName, ConnectionCacheWrapper connectionProcessor, AmqpRetryOptions retryOptions, MessageSerializer messageSerializer, Scheduler scheduler, boolean isSharedConnection, Runnable onClientClose, String identifier, EventHubsProducerInstrumentation instrumentation) { this.fullyQualifiedNamespace = Objects.requireNonNull(fullyQualifiedNamespace, "'fullyQualifiedNamespace' cannot be null."); this.eventHubName = Objects.requireNonNull(eventHubName, "'eventHubName' cannot be null."); this.connectionProcessor = Objects.requireNonNull(connectionProcessor, "'connectionProcessor' cannot be null."); this.retryOptions = Objects.requireNonNull(retryOptions, "'retryOptions' cannot be null."); this.messageSerializer = Objects.requireNonNull(messageSerializer, "'messageSerializer' cannot be null."); this.onClientClose = Objects.requireNonNull(onClientClose, "'onClientClose' cannot be null."); this.scheduler = scheduler; this.isSharedConnection = isSharedConnection; this.identifier = identifier; this.instrumentation = Objects.requireNonNull(instrumentation, "'instrumentation' cannot be null."); } /** * Gets the fully qualified Event Hubs namespace that the connection is associated with. This is likely similar to * {@code {yournamespace}.servicebus.windows.net}. * * @return The fully qualified Event Hubs namespace that the connection is associated with. */ public String getFullyQualifiedNamespace() { return fullyQualifiedNamespace; } /** * Gets the Event Hub name this client interacts with. * * @return The Event Hub name this client interacts with. */ public String getEventHubName() { return eventHubName; } /** * Retrieves information about an Event Hub, including the number of partitions present and their identifiers. * * @return The set of information for the Event Hub that this client is associated with. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono getEventHubProperties() { return instrumentation.instrumentMono( connectionProcessor.getManagementNodeWithRetries().flatMap(EventHubManagementNode::getEventHubProperties), GET_EVENT_HUB_PROPERTIES, null); } /** * Retrieves the identifiers for the partitions of an Event Hub. * * @return A Flux of identifiers for the partitions of an Event Hub. */ @ServiceMethod(returns = ReturnType.COLLECTION) public Flux getPartitionIds() { return getEventHubProperties().flatMapMany(properties -> Flux.fromIterable(properties.getPartitionIds())); } /** * Retrieves information about a specific partition for an Event Hub, including elements that describe the available * events in the partition event stream. * * @param partitionId The unique identifier of a partition associated with the Event Hub. * @return The set of information for the requested partition under the Event Hub this client is associated with. * @throws NullPointerException if {@code partitionId} is null. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono getPartitionProperties(String partitionId) { return instrumentation.instrumentMono( connectionProcessor.getManagementNodeWithRetries().flatMap(node -> node.getPartitionProperties(partitionId)), GET_PARTITION_PROPERTIES, partitionId); } /** * Creates an {@link EventDataBatch} that can fit as many events as the transport allows. * * @return A new {@link EventDataBatch} that can fit as many events as the transport allows. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono createBatch() { return createBatch(DEFAULT_BATCH_OPTIONS); } /** * Creates an {@link EventDataBatch} configured with the options specified. * * @param options A set of options used to configure the {@link EventDataBatch}. * @return A new {@link EventDataBatch} that can fit as many events as the transport allows. * @throws NullPointerException if {@code options} is null. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono createBatch(CreateBatchOptions options) { if (options == null) { return monoError(LOGGER, new NullPointerException("'options' cannot be null.")); } final String partitionKey = options.getPartitionKey(); final String partitionId = options.getPartitionId(); final int batchMaxSize = options.getMaximumSizeInBytes(); if (!CoreUtils.isNullOrEmpty(partitionKey) && !CoreUtils.isNullOrEmpty(partitionId)) { return monoError(LOGGER, new IllegalArgumentException(String.format(Locale.US, "CreateBatchOptions.getPartitionKey() and CreateBatchOptions.getPartitionId() are both set. " + "Only one or the other can be used. partitionKey: '%s'. partitionId: '%s'", partitionKey, partitionId))); } else if (!CoreUtils.isNullOrEmpty(partitionKey) && partitionKey.length() > MAX_PARTITION_KEY_LENGTH) { return monoError(LOGGER, new IllegalArgumentException(String.format(Locale.US, "Partition key '%s' exceeds the maximum allowed length: '%s'.", partitionKey, MAX_PARTITION_KEY_LENGTH))); } final Mono createBatch = getSendLink(partitionId) .flatMap(link -> link.getLinkSize() .flatMap(size -> { final int maximumLinkSize = size > 0 ? size : MAX_MESSAGE_LENGTH_BYTES; if (batchMaxSize > maximumLinkSize) { return monoError(LOGGER, new IllegalArgumentException(String.format(Locale.US, "BatchOptions.maximumSizeInBytes (%s bytes) is larger than the link size (%s bytes).", batchMaxSize, maximumLinkSize))); } final int batchSize = batchMaxSize > 0 ? batchMaxSize : maximumLinkSize; return Mono.just(new EventDataBatch(batchSize, partitionId, partitionKey, link::getErrorContext, instrumentation)); })); // Similar to the companion API 'send', the 'create-batch' can also make network calls, so retry in case // of transient errors. return withRetry(createBatch, retryOptions, String.format("partitionId[%s]: Creating batch timed out.", partitionId)) .publishOn(scheduler); } /** *

Sends a single event to the associated Event Hub. If the size of the single event exceeds the maximum size * allowed, an exception will be triggered and the send will fail. For high throughput publishing scenarios, using * {@link EventDataBatch} to publish events is recommended. Batches are created using {@link #createBatch()} and * {@link #createBatch(CreateBatchOptions)}.

* *

For more information regarding the maximum event size allowed, see * Azure Event Hubs Quotas and Limits. *

* * @param event Event to send to the service. * @return A {@link Mono} that completes when the event is pushed to the service. */ Mono send(EventData event) { if (event == null) { return monoError(LOGGER, new NullPointerException("'event' cannot be null.")); } return send(Flux.just(event)); } /** * Sends a single event to the associated Event Hub with the send options. If the size of the single event exceeds * the maximum size allowed, an exception will be triggered and the send will fail. * *

* For more information regarding the maximum event size allowed, see * Azure Event Hubs Quotas and * Limits. *

* * @param event Event to send to the service. * @param options The set of options to consider when sending this event. * @return A {@link Mono} that completes when the event is pushed to the service. */ Mono send(EventData event, SendOptions options) { if (event == null) { return monoError(LOGGER, new NullPointerException("'event' cannot be null.")); } else if (options == null) { return monoError(LOGGER, new NullPointerException("'options' cannot be null.")); } return send(Flux.just(event), options); } /** *

Sends a set of events to the associated Event Hub using a batched approach. If the size of events exceed the * maximum size of a single batch, an exception will be triggered and the send will fail. By default, the message * size is the max amount allowed on the link.

* * *
     * List<EventData> events = Arrays.asList(new EventData("maple"), new EventData("aspen"),
     *     new EventData("oak"));
     *
     * producer.send(events)
     *     .subscribe(unused -> {
     *     },
     *         error -> System.err.println("Error occurred while sending events:" + error),
     *         () -> System.out.println("Send complete."));
     * 
* * *

* For more information regarding the maximum event size allowed, see * Azure Event Hubs Quotas and * Limits. *

* * @param events Events to send to the service. * @return A {@link Mono} that completes when all events are pushed to the service. * @throws AmqpException if the size of {@code events} exceed the maximum size of a single batch. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono send(Iterable events) { if (events == null) { return monoError(LOGGER, new NullPointerException("'events' cannot be null.")); } return send(Flux.fromIterable(events)); } /** *

Sends a set of events to the associated Event Hub using a batched approach. If the size of events exceed the * maximum size of a single batch, an exception will be triggered and the send will fail. By default, the message * size is the max amount allowed on the link.

* * *
     * List<EventData> events = Arrays.asList(new EventData("Melbourne"), new EventData("London"),
     *     new EventData("New York"));
     *
     * SendOptions sendOptions = new SendOptions().setPartitionKey("cities");
     * producer.send(events, sendOptions)
     *     .subscribe(unused -> {
     *     },
     *         error -> System.err.println("Error occurred while sending events:" + error),
     *         () -> System.out.println("Send complete."));
     * 
* * *

* For more information regarding the maximum event size allowed, see * Azure Event Hubs Quotas and * Limits. *

* * @param events Events to send to the service. * @param options The set of options to consider when sending this batch. * @return A {@link Mono} that completes when all events are pushed to the service. * @throws AmqpException if the size of {@code events} exceed the maximum size of a single batch. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono send(Iterable events, SendOptions options) { if (events == null) { return monoError(LOGGER, new NullPointerException("'events' cannot be null.")); } else if (options == null) { return monoError(LOGGER, new NullPointerException("'options' cannot be null.")); } return send(Flux.fromIterable(events), options); } /** * Sends a set of events to the associated Event Hub using a batched approach. If the size of events exceed the * maximum size of a single batch, an exception will be triggered and the send will fail. By default, the message * size is the max amount allowed on the link. * * @param events Events to send to the service. * @return A {@link Mono} that completes when all events are pushed to the service. */ Mono send(Flux events) { if (events == null) { return monoError(LOGGER, new NullPointerException("'events' cannot be null.")); } return send(events, DEFAULT_SEND_OPTIONS); } /** * Sends a set of events to the associated Event Hub using a batched approach. If the size of events exceed the * maximum size of a single batch, an exception will be triggered and the send will fail. By default, the message * size is the max amount allowed on the link. * * @param events Events to send to the service. * @param options The set of options to consider when sending this batch. * @return A {@link Mono} that completes when all events are pushed to the service. */ Mono send(Flux events, SendOptions options) { if (events == null) { return monoError(LOGGER, new NullPointerException("'events' cannot be null.")); } else if (options == null) { return monoError(LOGGER, new NullPointerException("'options' cannot be null.")); } return sendInternal(events, options).publishOn(scheduler); } /** * Sends the batch to the associated Event Hub. * * @param batch The batch to send to the service. * @return A {@link Mono} that completes when the batch is pushed to the service. * @throws NullPointerException if {@code batch} is {@code null}. * @see EventHubProducerAsyncClient#createBatch() * @see EventHubProducerAsyncClient#createBatch(CreateBatchOptions) */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono send(EventDataBatch batch) { if (batch == null) { return monoError(LOGGER, new NullPointerException("'batch' cannot be null.")); } else if (batch.getEvents().isEmpty()) { LOGGER.warning(Messages.CANNOT_SEND_EVENT_BATCH_EMPTY); return Mono.empty(); } if (!CoreUtils.isNullOrEmpty(batch.getPartitionId())) { LOGGER.atVerbose() .addKeyValue("size", batch.getCount()) .addKeyValue(PARTITION_ID_KEY, batch.getPartitionId()) .log("Sending batch."); } else if (!CoreUtils.isNullOrEmpty(batch.getPartitionKey())) { LOGGER.atVerbose() .addKeyValue("size", batch.getCount()) .addKeyValue(PARTITION_KEY_KEY, batch.getPartitionKey()) .log("Sending batch."); } else { LOGGER.atVerbose() .addKeyValue("size", batch.getCount()) .log("Sending batch to be distributed round-robin in service."); } final String partitionKey = batch.getPartitionKey(); final List messages = new ArrayList<>(); for (int i = 0; i < batch.getEvents().size(); i++) { final EventData event = batch.getEvents().get(i); final Message message = messageSerializer.serialize(event); if (!CoreUtils.isNullOrEmpty(partitionKey)) { final MessageAnnotations messageAnnotations = message.getMessageAnnotations() == null ? new MessageAnnotations(new HashMap<>()) : message.getMessageAnnotations(); messageAnnotations.getValue().put(AmqpConstants.PARTITION_KEY, partitionKey); message.setMessageAnnotations(messageAnnotations); } messages.add(message); } final Mono sendMessage = getSendLink(batch.getPartitionId()) .flatMap(link -> messages.size() == 1 ? link.send(messages.get(0)) : link.send(messages)); final Mono send = withRetry(sendMessage, retryOptions, String.format("partitionId[%s]: Sending messages timed out.", batch.getPartitionId())) .publishOn(scheduler); // important to end spans after metrics are reported so metrics get relevant context for exemplars. return instrumentation.sendBatch(send, batch); } private Mono sendInternal(Flux events, SendOptions options) { final String partitionKey = options.getPartitionKey(); final String partitionId = options.getPartitionId(); if (!CoreUtils.isNullOrEmpty(partitionKey) && !CoreUtils.isNullOrEmpty(partitionId)) { return monoError(LOGGER, new IllegalArgumentException(String.format(Locale.US, "SendOptions.getPartitionKey() and SendOptions.getPartitionId() are both set. Only one or the" + " other can be used. partitionKey: '%s'. partitionId: '%s'", partitionKey, partitionId))); } return getSendLink(options.getPartitionId()) .flatMap(link -> link.getLinkSize() .flatMap(size -> { final int batchSize = size > 0 ? size : MAX_MESSAGE_LENGTH_BYTES; final CreateBatchOptions batchOptions = new CreateBatchOptions() .setPartitionKey(options.getPartitionKey()) .setPartitionId(options.getPartitionId()) .setMaximumSizeInBytes(batchSize); return events.collect(new EventDataCollector(batchOptions, 1, link::getErrorContext, instrumentation)); }) .flatMap(list -> sendInternal(Flux.fromIterable(list)))); } private Mono sendInternal(Flux eventBatches) { return eventBatches .flatMap(this::send) .then() .doOnError(error -> { LOGGER.error(Messages.ERROR_SENDING_BATCH, error); }); } private String getEntityPath(String partitionId) { return CoreUtils.isNullOrEmpty(partitionId) ? eventHubName : String.format(Locale.US, SENDER_ENTITY_PATH_FORMAT, eventHubName, partitionId); } private Mono getSendLink(String partitionId) { final String entityPath = getEntityPath(partitionId); final String linkName = entityPath; return connectionProcessor.getConnection() .flatMap(connection -> connection.createSendLink(linkName, entityPath, retryOptions, identifier)); } /** * Disposes of the {@link EventHubProducerAsyncClient}. If the client had a dedicated connection, the underlying * connection is also closed. */ @Override public void close() { if (isDisposed.getAndSet(true)) { return; } if (isSharedConnection) { onClientClose.run(); } else { connectionProcessor.dispose(); } } /** * Gets the client identifier. * * @return The unique identifier string for current client. */ public String getIdentifier() { return identifier; } /** * Collects EventData into EventDataBatch to send to Event Hubs. If {@code maxNumberOfBatches} is {@code null} then * it'll collect as many batches as possible. Otherwise, if there are more events than can fit into {@code * maxNumberOfBatches}, then the collector throws a {@link AmqpException} with {@link * AmqpErrorCondition#LINK_PAYLOAD_SIZE_EXCEEDED}. */ private static class EventDataCollector implements Collector, List> { private final String partitionKey; private final String partitionId; private final int maxMessageSize; private final Integer maxNumberOfBatches; private final ErrorContextProvider contextProvider; private final EventHubsProducerInstrumentation instrumentation; private volatile EventDataBatch currentBatch; EventDataCollector(CreateBatchOptions options, Integer maxNumberOfBatches, ErrorContextProvider contextProvider, EventHubsProducerInstrumentation instrumentation) { this.maxNumberOfBatches = maxNumberOfBatches; this.maxMessageSize = options.getMaximumSizeInBytes() > 0 ? options.getMaximumSizeInBytes() : MAX_MESSAGE_LENGTH_BYTES; this.partitionKey = options.getPartitionKey(); this.partitionId = options.getPartitionId(); this.contextProvider = contextProvider; this.instrumentation = instrumentation; currentBatch = new EventDataBatch(maxMessageSize, partitionId, partitionKey, contextProvider, instrumentation); } @Override public Supplier> supplier() { return ArrayList::new; } @Override public BiConsumer, EventData> accumulator() { return (list, event) -> { EventDataBatch batch = currentBatch; if (batch.tryAdd(event)) { return; } if (maxNumberOfBatches != null && list.size() == maxNumberOfBatches) { final String message = String.format(Locale.US, Messages.EVENT_DATA_DOES_NOT_FIT, maxNumberOfBatches); throw new AmqpException(false, AmqpErrorCondition.LINK_PAYLOAD_SIZE_EXCEEDED, message, contextProvider.getErrorContext()); } currentBatch = new EventDataBatch(maxMessageSize, partitionId, partitionKey, contextProvider, instrumentation); currentBatch.tryAdd(event); list.add(batch); }; } @Override public BinaryOperator> combiner() { return (existing, another) -> { existing.addAll(another); return existing; }; } @Override public Function, List> finisher() { return list -> { EventDataBatch batch = currentBatch; currentBatch = null; if (batch != null) { list.add(batch); } return list; }; } @Override public Set characteristics() { return Collections.emptySet(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy