From aee166331fc10f91ff86edab8f8a56e511b79d68 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 21 Oct 2020 22:38:38 -0700 Subject: [PATCH 01/23] Add ServiceBus Session Receiver Client --- .../messaging/servicebus/ReceiverOptions.java | 7 +- .../servicebus/ServiceBusClientBuilder.java | 77 ++--------- .../ServiceBusReceiverAsyncClient.java | 33 +++-- .../ServiceBusSessionReceiverAsyncClient.java | 124 ++++++++++++++++++ .../ServiceBusSessionReceiverClient.java | 68 ++++++++++ .../servicebus/UnnamedSessionManager.java | 30 ++++- .../azure-messaging-servicebus.properties | 1 + .../messaging/servicebus/ReadmeSamples.java | 8 +- .../ReceiveMultipleSessionsAsyncSample.java | 4 +- .../ReceiveNamedSessionAsyncSample.java | 15 ++- .../servicebus/ReceiveNamedSessionSample.java | 7 +- .../ReceiveSingleSessionAsyncSample.java | 9 +- .../SendAndReceiveSessionMessageSample.java | 9 +- ...ReceiverAsyncClientJavaDocCodeSamples.java | 18 +-- ...BusReceiverAsyncClientIntegrationTest.java | 7 +- .../ServiceBusReceiverAsyncClientTest.java | 2 +- ...rviceBusReceiverClientIntegrationTest.java | 6 +- ...ceBusSenderAsyncClientIntegrationTest.java | 3 +- .../UnnamedSessionManagerIntegrationTest.java | 9 +- .../servicebus/UnnamedSessionManagerTest.java | 6 +- 20 files changed, 301 insertions(+), 142 deletions(-) create mode 100644 sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java create mode 100644 sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java index 94a9364fd151..9ac21725048a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java @@ -12,7 +12,6 @@ class ReceiverOptions { private final ReceiveMode receiveMode; private final int prefetchCount; private final String sessionId; - private final boolean isRollingSessionReceiver; private final Integer maxConcurrentSessions; private final boolean isSessionReceiver; @@ -20,17 +19,15 @@ class ReceiverOptions { this.receiveMode = receiveMode; this.prefetchCount = prefetchCount; this.sessionId = null; - this.isRollingSessionReceiver = false; this.maxConcurrentSessions = null; this.isSessionReceiver = false; } ReceiverOptions(ReceiveMode receiveMode, int prefetchCount, - String sessionId, boolean isRollingSessionReceiver, Integer maxConcurrentSessions) { + String sessionId, Integer maxConcurrentSessions) { this.receiveMode = receiveMode; this.prefetchCount = prefetchCount; this.sessionId = sessionId; - this.isRollingSessionReceiver = isRollingSessionReceiver; this.maxConcurrentSessions = maxConcurrentSessions; this.isSessionReceiver = true; } @@ -78,7 +75,7 @@ boolean isSessionReceiver() { * false} otherwise. */ public boolean isRollingSessionReceiver() { - return isRollingSessionReceiver; + return maxConcurrentSessions != null && maxConcurrentSessions > 0 && sessionId == null; } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java index b847c3d746ce..d8df4eed6c56 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java @@ -621,35 +621,15 @@ public ServiceBusSenderClient buildClient() { @ServiceClientBuilder(serviceClients = {ServiceBusReceiverClient.class, ServiceBusReceiverAsyncClient.class}) public final class ServiceBusSessionReceiverClientBuilder { - private Integer maxConcurrentSessions = null; private int prefetchCount = DEFAULT_PREFETCH_COUNT; private String queueName; private ReceiveMode receiveMode = ReceiveMode.PEEK_LOCK; - private String sessionId; private String subscriptionName; private String topicName; private ServiceBusSessionReceiverClientBuilder() { } - /** - * Enables session processing roll-over by processing at most {@code maxConcurrentSessions}. - * - * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. - * - * @return The modified {@link ServiceBusSessionReceiverClientBuilder} object. - * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. - */ - public ServiceBusSessionReceiverClientBuilder maxConcurrentSessions(int maxConcurrentSessions) { - if (maxConcurrentSessions < 1) { - throw logger.logExceptionAsError(new IllegalArgumentException( - "maxConcurrentSessions cannot be less than 1.")); - } - - this.maxConcurrentSessions = maxConcurrentSessions; - return this; - } - /** * Sets the prefetch count of the receiver. For both {@link ReceiveMode#PEEK_LOCK PEEK_LOCK} and {@link * ReceiveMode#RECEIVE_AND_DELETE RECEIVE_AND_DELETE} modes the default value is 1. @@ -691,18 +671,6 @@ public ServiceBusSessionReceiverClientBuilder receiveMode(ReceiveMode receiveMod return this; } - /** - * Sets the session id. - * - * @param sessionId session id. - * - * @return The modified {@link ServiceBusSessionReceiverClientBuilder} object. - */ - public ServiceBusSessionReceiverClientBuilder sessionId(String sessionId) { - this.sessionId = sessionId; - return this; - } - /** * Sets the name of the subscription in the topic to listen to. {@link #topicName(String)} must also be set. * @@ -743,7 +711,7 @@ public ServiceBusSessionReceiverClientBuilder topicName(String topicName) { * @throws IllegalArgumentException Queue or topic name are not set via {@link #queueName(String) * queueName()} or {@link #topicName(String) topicName()}, respectively. */ - public ServiceBusReceiverAsyncClient buildAsyncClient() { + public ServiceBusSessionReceiverAsyncClient buildAsyncClient() { final MessagingEntityType entityType = validateEntityPaths(logger, connectionStringEntityName, topicName, queueName); final String entityPath = getEntityPath(logger, entityType, queueName, topicName, subscriptionName, @@ -753,28 +721,18 @@ public ServiceBusReceiverAsyncClient buildAsyncClient() { final ServiceBusConnectionProcessor connectionProcessor = getOrCreateConnectionProcessor(messageSerializer); final ReceiverOptions receiverOptions = new ReceiverOptions(receiveMode, prefetchCount, - sessionId, isRollingSessionReceiver(), maxConcurrentSessions); - - if (CoreUtils.isNullOrEmpty(sessionId)) { - final UnnamedSessionManager sessionManager = new UnnamedSessionManager(entityPath, entityType, - connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, - messageSerializer, receiverOptions); + null, null); - return new ServiceBusReceiverAsyncClient(connectionProcessor.getFullyQualifiedNamespace(), entityPath, - entityType, receiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, - tracerProvider, messageSerializer, ServiceBusClientBuilder.this::onClientClose, sessionManager); - } else { - return new ServiceBusReceiverAsyncClient(connectionProcessor.getFullyQualifiedNamespace(), entityPath, - entityType, receiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, - tracerProvider, messageSerializer, ServiceBusClientBuilder.this::onClientClose); - } + return new ServiceBusSessionReceiverAsyncClient(connectionProcessor.getFullyQualifiedNamespace(), + entityPath, entityType, receiverOptions, connectionProcessor, tracerProvider, messageSerializer, + ServiceBusClientBuilder.this::onClientClose); } /** * Creates a synchronous, session-aware Service Bus receiver responsible for reading * {@link ServiceBusMessage messages} from a specific queue or topic. * - * @return An new {@link ServiceBusReceiverClient} that receives messages from a queue or topic. + * @return An new {@link ServiceBusSessionReceiverClient} that receives messages from a queue or topic. * @throws IllegalStateException if {@link #queueName(String) queueName} or {@link #topicName(String) * topicName} are not set or, both of these fields are set. It is also thrown if the Service Bus {@link * #connectionString(String) connectionString} contains an {@code EntityPath} that does not match one set in @@ -783,27 +741,8 @@ public ServiceBusReceiverAsyncClient buildAsyncClient() { * @throws IllegalArgumentException Queue or topic name are not set via {@link #queueName(String) * queueName()} or {@link #topicName(String) topicName()}, respectively. */ - public ServiceBusReceiverClient buildClient() { - return new ServiceBusReceiverClient(buildAsyncClient(), retryOptions.getTryTimeout()); - } - - /** - * This is a rolling session receiver only if maxConcurrentSessions is > 0 AND sessionId is null or empty. If - * there is a sessionId, this is going to be a single, named session receiver. - * - * @return {@code true} if this is an unnamed rolling session receiver; {@code false} otherwise. - */ - private boolean isRollingSessionReceiver() { - if (maxConcurrentSessions == null) { - return false; - } - - if (maxConcurrentSessions < 1) { - throw logger.logExceptionAsError( - new IllegalArgumentException("Maximum number of concurrent sessions must be positive.")); - } - - return CoreUtils.isNullOrEmpty(sessionId); + public ServiceBusSessionReceiverClient buildClient() { + return new ServiceBusSessionReceiverClient(this.buildAsyncClient(), retryOptions.getTryTimeout()); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index 6360dfc7e5aa..a0652889b45d 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -62,22 +62,26 @@ * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.receiveWithReceiveAndDeleteMode} * *

Receive messages from a specific session

- *

To fetch messages from a specific session, set {@link ServiceBusSessionReceiverClientBuilder#sessionId(String)}. + *

To fetch messages from a specific session, switch to {@link ServiceBusSessionReceiverClientBuilder} and + * build the session receiver client. Use {@link ServiceBusSessionReceiverAsyncClient#acceptSession(String)} to create a + * session-bound {@link ServiceBusReceiverAsyncClient}. *

* {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} * - *

Process messages from multiple sessions

- *

To process messages from multiple sessions, set - * {@link ServiceBusSessionReceiverClientBuilder#maxConcurrentSessions(int)}. This will process in parallel at most - * {@code maxConcurrentSessions}. In addition, when all the messages in a session have been consumed, it will find the - * next available session to process.

- * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions} - * *

Process messages from the first available session

*

To process messages from the first available session, switch to {@link ServiceBusSessionReceiverClientBuilder} and - * build the receiver client. It will find the first available session to process messages from.

+ * build the session receiver client. Use {@link ServiceBusSessionReceiverAsyncClient#acceptNextSession()} to + * find the first available session to process messages from.

* {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession} * + *

Process messages from multiple sessions

+ *

To process messages from multiple sessions, switch to {@link ServiceBusSessionReceiverClientBuilder} and + * build the session receiver client, then use {@link ServiceBusSessionReceiverAsyncClient#getReceiverClient(int)} + * to create an receiver client that processes events from multiple sessions in parallel. + * In addition, when all the messages in a session have been consumed, it will find the + * next available session to process.

+ * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions} + * *

Rate limiting consumption of messages from Service Bus resource

*

For message receivers that need to limit the number of messages they receive at a given time, they can use * {@link BaseSubscriber#request(long)}.

@@ -1097,4 +1101,15 @@ private String getLinkName(String sessionId) { return existing != null ? existing.getLinkName() : null; } } + + private Mono validateSession(String sessionId) { + String optionSessionId = receiverOptions.getSessionId(); + if (optionSessionId != null && !optionSessionId.equals(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another %s.", + optionSessionId, sessionId))); + } else { + return Mono.just(true); + } + } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java new file mode 100644 index 000000000000..1f8feaf1b22a --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus; + +import com.azure.core.amqp.implementation.MessageSerializer; +import com.azure.core.amqp.implementation.TracerProvider; +import com.azure.core.util.CoreUtils; +import com.azure.core.util.logging.ClientLogger; +import com.azure.messaging.servicebus.implementation.MessagingEntityType; +import com.azure.messaging.servicebus.implementation.ServiceBusConnectionProcessor; +import com.azure.messaging.servicebus.implementation.ServiceBusConstants; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +import static com.azure.core.util.FluxUtil.monoError; + +/** + * The builder that creates session-related {@link ServiceBusReceiverAsyncClient} instances. + * Use {@link #acceptSession(String)} to create a {@link ServiceBusReceiverAsyncClient} that is tied to a known specific + * session id. + * Use {@link #acceptNextSession()} to create a {@link ServiceBusReceiverAsyncClient} that is tied to an unknown + * available session. + * Use {@link #getReceiverClient(int)} to create a {@link ServiceBusReceiverAsyncClient} that + * process events from up to maxConcurrentSessions number of sessions. + */ +public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { + private final String fullyQualifiedNamespace; + private final String entityPath; + private final MessagingEntityType entityType; + private final ReceiverOptions receiverOptions; + private final ServiceBusConnectionProcessor connectionProcessor; + private final TracerProvider tracerProvider; + private final MessageSerializer messageSerializer; + private final Runnable onClientClose; + private final UnnamedSessionManager unNamedSessionManager; // for acceptNextSession() + private final ClientLogger logger = new ClientLogger(ServiceBusSessionReceiverAsyncClient.class); + + ServiceBusSessionReceiverAsyncClient(String fullyQualifiedNamespace, String entityPath, + MessagingEntityType entityType, ReceiverOptions receiverOptions, + ServiceBusConnectionProcessor connectionProcessor, TracerProvider tracerProvider, + MessageSerializer messageSerializer, Runnable onClientClose) { + this.fullyQualifiedNamespace = Objects.requireNonNull(fullyQualifiedNamespace, + "'fullyQualifiedNamespace' cannot be null."); + this.entityPath = Objects.requireNonNull(entityPath, "'entityPath' cannot be null."); + this.entityType = Objects.requireNonNull(entityType, "'entityType' cannot be null."); + this.receiverOptions = Objects.requireNonNull(receiverOptions, "'receiveOptions cannot be null.'"); + this.connectionProcessor = Objects.requireNonNull(connectionProcessor, "'connectionProcessor' cannot be null."); + this.tracerProvider = Objects.requireNonNull(tracerProvider, "'tracerProvider' cannot be null."); + this.messageSerializer = Objects.requireNonNull(messageSerializer, "'messageSerializer' cannot be null."); + this.onClientClose = Objects.requireNonNull(onClientClose, "'onClientClose' cannot be null."); + this.unNamedSessionManager = new UnnamedSessionManager(entityPath, entityType, + connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, + messageSerializer, receiverOptions); + } + + /** + * Create a link for the next available session and use the link to create a {@link ServiceBusReceiverAsyncClient} + * to receive messages from that session. + * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the available session. + */ + public Mono acceptNextSession() { + return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() + .map(sessionId -> { + ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), + this.receiverOptions.getPrefetchCount(), sessionId, null); + final UnnamedSessionManager sessionSpecificManager = new UnnamedSessionManager(entityPath, entityType, + connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, + messageSerializer, newReceiverOptions, receiveLink); + return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, + entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, + tracerProvider, messageSerializer, onClientClose, sessionSpecificManager); + })); + } + + /** + * Create a link for the "sessionId" and use the link to create a {@link ServiceBusReceiverAsyncClient} + * to receive messages from the session. + * @param sessionId The session Id. + * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the specified session. + * @throws IllegalArgumentException if {@code sessionId} is null or empty. + */ + public Mono acceptSession(String sessionId) { + if (CoreUtils.isNullOrEmpty(sessionId)) { + return monoError(logger, new IllegalArgumentException("sessionId can not be null or empty")); + } + ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), + this.receiverOptions.getPrefetchCount(), sessionId, null); + final UnnamedSessionManager sessionSpecificManager = new UnnamedSessionManager(entityPath, entityType, + connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, + messageSerializer, newReceiverOptions); + + return sessionSpecificManager.getActiveLink().thenReturn(new ServiceBusReceiverAsyncClient( + fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, + ServiceBusConstants.OPERATION_TIMEOUT, tracerProvider, messageSerializer, onClientClose, + sessionSpecificManager)); + } + + /** + * Create a {@link ServiceBusReceiverAsyncClient} that processes at most {@code maxConcurrentSessions} sessions. + * + * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. + * + * @return The {@link ServiceBusReceiverAsyncClient} object that will be used to receive messages. + * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. + */ + public ServiceBusReceiverAsyncClient getReceiverClient(int maxConcurrentSessions) { + if (maxConcurrentSessions < 1) { + throw logger.logExceptionAsError( + new IllegalArgumentException("Maximum number of concurrent sessions must be positive.")); + } + ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), + this.receiverOptions.getPrefetchCount(), null, maxConcurrentSessions); + return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, + entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, + tracerProvider, messageSerializer, onClientClose); + } + + @Override + public void close() { + this.onClientClose.run(); + } +} diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java new file mode 100644 index 000000000000..9605393e860d --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus; + +import java.time.Duration; +import java.util.Objects; + +/** + * The builder that creates session-related {@link ServiceBusReceiverClient} instances. + * Use {@link #acceptSession(String)} to create a {@link ServiceBusReceiverClient} that is tied to a known specific + * session id. + * Use {@link #acceptNextSession()} to create a {@link ServiceBusReceiverClient} that is tied to an unknown + * available session. + * Use {@link #getReceiverClient(int)} to create a {@link ServiceBusReceiverClient} that + * process events from up to maxConcurrentSessions number of sessions. + */ +public class ServiceBusSessionReceiverClient implements AutoCloseable { + private final ServiceBusSessionReceiverAsyncClient sessionAsyncClient; + private final Duration operationTimeout; + + ServiceBusSessionReceiverClient(ServiceBusSessionReceiverAsyncClient asyncClient, Duration operationTimeout) { + this.sessionAsyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); + this.operationTimeout = Objects.requireNonNull(operationTimeout, "'operationTimeout' cannot be null."); + } + + /** + * Create a link for the next available session and use the link to create a {@link ServiceBusReceiverClient} + * to receive messages from that session. + * @return A {@link ServiceBusReceiverClient} that is tied to the available session. + */ + public ServiceBusReceiverClient acceptNextSession() { + return sessionAsyncClient.acceptNextSession() + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) + .block(operationTimeout); + } + + /** + * Create a link for the "sessionId" and use the link to create a {@link ServiceBusReceiverClient} + * to receive messages from the session. + * @param sessionId The session Id. + * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. + * @throws IllegalArgumentException if {@code sessionId} is null or empty. + */ + public ServiceBusReceiverClient acceptSession(String sessionId) { + return sessionAsyncClient.acceptSession(sessionId) + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) + .block(operationTimeout); + } + + /** + * Create a {@link ServiceBusReceiverClient} that processes at most {@code maxConcurrentSessions} sessions. + * + * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. + * + * @return The {@link ServiceBusReceiverClient} object that will be used to receive messages. + * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. + */ + public ServiceBusReceiverClient getReceiverClient(int maxConcurrentSessions) { + return new ServiceBusReceiverClient(sessionAsyncClient.getReceiverClient(maxConcurrentSessions), + operationTimeout); + } + + @Override + public void close() { + sessionAsyncClient.close(); + } +} diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/UnnamedSessionManager.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/UnnamedSessionManager.java index c3d74cd03825..8517801a1b11 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/UnnamedSessionManager.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/UnnamedSessionManager.java @@ -55,6 +55,7 @@ class UnnamedSessionManager implements AutoCloseable { private final String entityPath; private final MessagingEntityType entityType; private final ReceiverOptions receiverOptions; + private final ServiceBusReceiveLink receiveLink; private final ServiceBusConnectionProcessor connectionProcessor; private final Duration operationTimeout; private final TracerProvider tracerProvider; @@ -76,7 +77,7 @@ class UnnamedSessionManager implements AutoCloseable { UnnamedSessionManager(String entityPath, MessagingEntityType entityType, ServiceBusConnectionProcessor connectionProcessor, Duration operationTimeout, TracerProvider tracerProvider, - MessageSerializer messageSerializer, ReceiverOptions receiverOptions) { + MessageSerializer messageSerializer, ReceiverOptions receiverOptions, ServiceBusReceiveLink receiveLink) { this.entityPath = entityPath; this.entityType = entityType; this.receiverOptions = receiverOptions; @@ -101,6 +102,14 @@ class UnnamedSessionManager implements AutoCloseable { this.processor = EmitterProcessor.create(numberOfSchedulers, false); this.sessionReceiveSink = processor.sink(); + this.receiveLink = receiveLink; + } + + UnnamedSessionManager(String entityPath, MessagingEntityType entityType, + ServiceBusConnectionProcessor connectionProcessor, Duration operationTimeout, TracerProvider tracerProvider, + MessageSerializer messageSerializer, ReceiverOptions receiverOptions) { + this(entityPath, entityType, connectionProcessor, operationTimeout, tracerProvider, + messageSerializer, receiverOptions, null); } /** @@ -224,16 +233,20 @@ private AmqpErrorContext getErrorContext() { } /** - * Creates an unnamed session receive link. + * Creates an session receive link. * * @return A Mono that completes with an unnamed session receive link. */ private Mono createSessionReceiveLink() { - final String linkName = StringUtil.getRandomString("session-"); + final String sessionId = receiverOptions.getSessionId(); + + final String linkName = (sessionId != null) + ? sessionId + : StringUtil.getRandomString("session-"); return connectionProcessor .flatMap(connection -> connection.createReceiveLink(linkName, entityPath, receiverOptions.getReceiveMode(), - null, entityType, null)); + null, entityType, sessionId)); } /** @@ -242,7 +255,10 @@ private Mono createSessionReceiveLink() { * @return A Mono that completes when an unnamed session becomes available. * @throws AmqpException if the session manager is already disposed. */ - private Mono getActiveLink() { + Mono getActiveLink() { + if (this.receiveLink != null) { + return Mono.just(this.receiveLink); + } return Mono.defer(() -> createSessionReceiveLink() .flatMap(link -> link.getEndpointStates() .takeUntil(e -> e == AmqpEndpointState.ACTIVE) @@ -277,7 +293,7 @@ private Mono getActiveLink() { */ private Flux getSession(Scheduler scheduler, boolean disposeOnIdle) { return getActiveLink().flatMap(link -> link.getSessionId() - .map(linkName -> sessionReceivers.compute(linkName, (key, existing) -> { + .map(sessionId -> sessionReceivers.compute(sessionId, (key, existing) -> { if (existing != null) { return existing; } @@ -285,7 +301,7 @@ private Flux getSession(Scheduler scheduler, b return new UnnamedSessionReceiver(link, messageSerializer, connectionProcessor.getRetryOptions(), receiverOptions.getPrefetchCount(), disposeOnIdle, scheduler, this::renewSessionLock); }))) - .flatMapMany(session -> session.receive().doFinally(signalType -> { + .flatMapMany(sessionReceiver -> sessionReceiver.receive().doFinally(signalType -> { logger.verbose("Adding scheduler back to pool."); availableSchedulers.push(scheduler); if (receiverOptions.isRollingSessionReceiver()) { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties b/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties index 0600b337ca0e..291f6ee5831a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties @@ -5,3 +5,4 @@ MESSAGE_NOT_OF_TYPE=Message body type is not of type Data, but type: %s. Not set REQUEST_VALUE_NOT_VALID=Back pressure request value not valid. It must be between {} and {}. INVALID_OPERATION_DISPOSED_RECEIVER=Cannot perform operation '%s' on a disposed receiver. INVALID_LOCK_TOKEN_STRING=Invalid lock token '%s'. +SESSION_BOUND_RECEIVER=This receiver client accepts session %s. It can't be used for another session %s. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReadmeSamples.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReadmeSamples.java index 9dc191505b66..40433c6e00a1 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReadmeSamples.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReadmeSamples.java @@ -9,6 +9,7 @@ import com.azure.messaging.servicebus.models.ReceiveMode; import com.azure.messaging.servicebus.models.SubQueue; import reactor.core.Disposable; +import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -173,12 +174,12 @@ public void createSessionMessage() { */ public void namedSessionReceiver() { // Creates a session-enabled receiver that gets messages from the session "greetings". - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") .sessionReceiver() .queueName("<< QUEUE NAME >>") - .sessionId("greetings") .buildAsyncClient(); + Mono receiverAsyncClient = sessionReceiver.acceptSession("greetings"); } /** @@ -186,11 +187,12 @@ public void namedSessionReceiver() { */ public void unnamedSessionReceiver() { // Creates a session-enabled receiver that gets messages from the first available session. - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") .sessionReceiver() .queueName("<< QUEUE NAME >>") .buildAsyncClient(); + Mono receiverAsyncClient = sessionReceiver.acceptNextSession(); } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java index 87f06985bbe1..b7b5c0ee3702 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java @@ -36,13 +36,13 @@ public static void main(String[] args) throws InterruptedException { // Create a receiver. // "<>" will be the name of the Service Bus session-enabled queue instance you created inside the // Service Bus namespace. - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString(connectionString) .sessionReceiver() - .maxConcurrentSessions(3) .queueName("<>") .buildAsyncClient(); + ServiceBusReceiverAsyncClient receiver = sessionReceiver.getReceiverClient(3); Disposable subscription = receiver.receiveMessages() .flatMap(context -> { if (context.hasError()) { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionAsyncSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionAsyncSample.java index 0c764c0774c8..cec638475786 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionAsyncSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionAsyncSample.java @@ -33,15 +33,15 @@ public static void main(String[] args) throws InterruptedException { // Create a receiver. // "<>" will be the name of the Service Bus session-enabled queue instance you created inside the // Service Bus namespace. - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString(connectionString) .sessionReceiver() - .sessionId("greetings") .receiveMode(ReceiveMode.PEEK_LOCK) .queueName("<>") .buildAsyncClient(); - Disposable subscription = receiver.receiveMessages() + Mono receiverMono = sessionReceiver.acceptSession("greetings"); + Disposable subscription = receiverMono.flatMapMany(receiver -> receiver.receiveMessages() .flatMap(context -> { if (context.hasError()) { System.out.printf("An error occurred in session %s. Error: %s%n", @@ -52,18 +52,19 @@ public static void main(String[] args) throws InterruptedException { System.out.println("Processing message from session: " + context.getSessionId()); // Process message then complete it. - return receiver.complete(context.getMessage()); - }) + //return receiver.complete(context.getMessage()); + return Mono.empty(); + })) .subscribe(aVoid -> { }, error -> System.err.println("Error occurred: " + error)); // Subscribe is not a blocking call so we sleep here so the program does not end. - TimeUnit.SECONDS.sleep(60); + TimeUnit.SECONDS.sleep(10); // Disposing of the subscription will cancel the receive() operation. subscription.dispose(); // Close the receiver. - receiver.close(); + sessionReceiver.close(); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionSample.java index 7e71405c23bf..4d2a824c8782 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveNamedSessionSample.java @@ -39,13 +39,12 @@ public static void main(String[] args) { // Create a receiver. // "<>" will be the name of the Service Bus session-enabled queue instance you created inside the // Service Bus namespace. - ServiceBusReceiverClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder() .connectionString(connectionString) .sessionReceiver() - .sessionId("greetings") .queueName("<>") .buildClient(); - + ServiceBusReceiverClient receiver = sessionReceiver.acceptSession("greetings"); while (isRunning.get()) { IterableStream messages = receiver.receiveMessages(10, Duration.ofSeconds(30)); @@ -64,7 +63,7 @@ public static void main(String[] args) { } // Close the receiver. - receiver.close(); + sessionReceiver.close(); } private static boolean processMessage(ServiceBusReceivedMessage message) { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveSingleSessionAsyncSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveSingleSessionAsyncSample.java index c7e178ca2327..fa9d8ef9cc6e 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveSingleSessionAsyncSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveSingleSessionAsyncSample.java @@ -32,7 +32,7 @@ public static void main(String[] args) throws InterruptedException { // Create a receiver. // "<>" will be the name of the Service Bus topic you created inside the Service Bus namespace. // "<>" will be the name of the session-enabled subscription. - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString(connectionString) .sessionReceiver() .receiveMode(ReceiveMode.PEEK_LOCK) @@ -40,7 +40,8 @@ public static void main(String[] args) throws InterruptedException { .subscriptionName("<>") .buildAsyncClient(); - Disposable subscription = receiver.receiveMessages() + Mono receiverMono = sessionReceiver.acceptNextSession(); + Disposable subscription = receiverMono.flatMapMany(receiver -> receiver.receiveMessages() .flatMap(context -> { if (context.hasError()) { System.out.printf("An error occurred in session %s. Error: %s%n", @@ -52,7 +53,7 @@ public static void main(String[] args) throws InterruptedException { // Process message return receiver.complete(context.getMessage()); - }).subscribe(aVoid -> { + })).subscribe(aVoid -> { }, error -> System.err.println("Error occurred: " + error)); // Subscribe is not a blocking call so we sleep here so the program does not end. @@ -62,6 +63,6 @@ public static void main(String[] args) throws InterruptedException { subscription.dispose(); // Close the receiver. - receiver.close(); + sessionReceiver.close(); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/SendAndReceiveSessionMessageSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/SendAndReceiveSessionMessageSample.java index a6cbef6fb3bb..6df37b22bf6f 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/SendAndReceiveSessionMessageSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/SendAndReceiveSessionMessageSample.java @@ -50,10 +50,9 @@ public static void main(String[] args) throws InterruptedException { .buildAsyncClient(); // Instantiate a client that will be used to receive messages from the session. - ServiceBusReceiverAsyncClient receiver = builder.sessionReceiver() + ServiceBusSessionReceiverAsyncClient sessionReceiver = builder.sessionReceiver() .receiveMode(ReceiveMode.PEEK_LOCK) .queueName(queueName) - .sessionId(sessionId) .buildAsyncClient(); List messages = Arrays.asList( @@ -82,7 +81,7 @@ public static void main(String[] args) throws InterruptedException { () -> System.out.println("Batch send complete.")); // After sending that message, we receive the messages for that sessionId. - receiver.receiveMessages().flatMap(context -> { + sessionReceiver.acceptSession(sessionId).flatMapMany(receiver -> receiver.receiveMessages().flatMap(context -> { ServiceBusReceivedMessage message = context.getMessage(); System.out.println("Received Message Id: " + message.getMessageId()); @@ -90,13 +89,13 @@ public static void main(String[] args) throws InterruptedException { System.out.println("Received Message: " + new String(message.getBody())); return receiver.complete(message); - }).subscribe(); + })).subscribe(); // subscribe() is not a blocking call. We sleep here so the program does not end before the send is complete. TimeUnit.SECONDS.sleep(10); // Close the sender and receiver. sender.close(); - receiver.close(); + sessionReceiver.close(); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java index ceef613e1d9f..292e5e0d8f6b 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java @@ -146,15 +146,16 @@ public void receiveAll() { */ public void sessionReceiverSingleInstantiation() { // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession - ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + "SharedAccessKey={key};EntityPath={eh-name}") .sessionReceiver() .queueName("<< QUEUE NAME >>") .buildAsyncClient(); + Mono receiverMono = sessionReceiver.acceptNextSession(); // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession - receiver.close(); + sessionReceiver.close(); } /** @@ -162,17 +163,18 @@ public void sessionReceiverSingleInstantiation() { */ public void sessionReceiverNamedInstantiation() { // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId - ServiceBusReceiverAsyncClient consumer = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + "SharedAccessKey={key};EntityPath={eh-name}") .sessionReceiver() .topicName("<< TOPIC NAME >>") .subscriptionName("<< SUBSCRIPTION NAME >>") - .sessionId("<< my-session-id >>") + //.sessionId("<< my-session-id >>") .buildAsyncClient(); + Mono receiverMono = sessionReceiver.acceptSession("<< my-session-id >>"); // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId - consumer.close(); + sessionReceiver.close(); } /** @@ -180,17 +182,17 @@ public void sessionReceiverNamedInstantiation() { */ public void sessionReceiverMultipleInstantiation() { // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions - ServiceBusReceiverAsyncClient consumer = new ServiceBusClientBuilder() + ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + "SharedAccessKey={key};EntityPath={eh-name}") .sessionReceiver() .topicName("<< TOPIC NAME >>") .subscriptionName("<< SUBSCRIPTION NAME >>") - .maxConcurrentSessions(3) .buildAsyncClient(); + ServiceBusReceiverAsyncClient receiver = sessionReceiver.getReceiverClient(3); // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions - consumer.close(); + sessionReceiver.close(); } public void createTransaction() { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java index b1a64a04c1da..ae435a33e565 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java @@ -1143,14 +1143,11 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(false, entityType, entityIndex, Function.identity(), shareConnection) - .sessionId(sessionId) - .buildAsyncClient(); + .buildAsyncClient().acceptSession(sessionId).block(); this.receiveAndDeleteReceiver = getSessionReceiverBuilder(false, entityType, entityIndex, Function.identity(), shareConnection) - - .sessionId(sessionId) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient(); + .buildAsyncClient().acceptSession(sessionId).block(); } else { this.receiver = getReceiverBuilder(false, entityType, entityIndex, Function.identity(), shareConnection) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java index a5a7f04a9aaa..95026af34386 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java @@ -173,7 +173,7 @@ CbsAuthorizationType.SHARED_ACCESS_SIGNATURE, AmqpTransportType.AMQP, new AmqpRe tracerProvider, messageSerializer, onClientClose); sessionReceiver = new ServiceBusReceiverAsyncClient(NAMESPACE, ENTITY_PATH, MessagingEntityType.QUEUE, - new ReceiverOptions(ReceiveMode.PEEK_LOCK, PREFETCH, "Some-Session", false, null), + new ReceiverOptions(ReceiveMode.PEEK_LOCK, PREFETCH, "Some-Session", null), connectionProcessor, CLEANUP_INTERVAL, tracerProvider, messageSerializer, onClientClose); } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java index 7e4dba5640ca..be5ca2db9ed6 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java @@ -816,13 +816,11 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde if (isSessionEnabled) { assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(false, entityType, entityIndex, Function.identity(), sharedConnection) - .sessionId(sessionId) - .buildClient(); + .buildClient().acceptSession(sessionId); this.receiveAndDeleteReceiver = getSessionReceiverBuilder(false, entityType, entityIndex, Function.identity(), sharedConnection) - .sessionId(sessionId) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildClient(); + .buildClient().acceptSession(sessionId); } else { this.receiver = getReceiverBuilder(false, entityType, entityIndex, Function.identity(), sharedConnection) .buildClient(); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java index 26b212e843ad..5a9731f5a22a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java @@ -512,8 +512,7 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, Function.identity(), shareConnection) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .sessionId(sessionId) - .buildAsyncClient(); + .buildAsyncClient().acceptSession(sessionId).block(); } else { this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, Function.identity(), shareConnection) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerIntegrationTest.java index 74c011eca441..e86b17919ea2 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerIntegrationTest.java @@ -72,7 +72,8 @@ void singleUnnamedSession(MessagingEntityType entityType) { final int numberToSend = 5; final List receivedMessages = new ArrayList<>(); - setSenderAndReceiver(entityType, entityIndex, TIMEOUT, builder -> builder); + setSenderAndReceiver(entityType, entityIndex, TIMEOUT, + builder -> builder.buildAsyncClient().acceptSession(sessionId).block()); final Disposable subscription = Flux.interval(Duration.ofMillis(500)) .take(numberToSend) @@ -129,7 +130,7 @@ void multipleSessions() { final Set set = new HashSet<>(); setSenderAndReceiver(MessagingEntityType.SUBSCRIPTION, entityIndex, Duration.ofSeconds(20), - builder -> builder.maxConcurrentSessions(maxConcurrency)); + builder -> builder.buildAsyncClient().getReceiverClient(maxConcurrency)); final Disposable subscription = Flux.interval(Duration.ofMillis(500)) .take(maxMessages) @@ -187,14 +188,14 @@ private void assertFromSession(List sessionIds, Set currentSessi * Sets the sender and receiver. If session is enabled, then a single-named session receiver is created. */ private void setSenderAndReceiver(MessagingEntityType entityType, int entityIndex, Duration operationTimeout, - Function onBuild) { + Function onBuild) { this.sender = getSenderBuilder(false, entityType, entityIndex, true, false) .buildAsyncClient(); ServiceBusSessionReceiverClientBuilder sessionBuilder = getSessionReceiverBuilder(false, entityType, entityIndex, builder -> builder.retryOptions(new AmqpRetryOptions().setTryTimeout(operationTimeout)), false); - this.receiver = onBuild.apply(sessionBuilder).buildAsyncClient(); + this.receiver = onBuild.apply(sessionBuilder); } private static void assertMessageEquals(String sessionId, String messageId, String contents, diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerTest.java index c3282c83e3cd..d3db06c9b907 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/UnnamedSessionManagerTest.java @@ -144,7 +144,7 @@ void afterEach(TestInfo testInfo) { @Test void receiveNull() { // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, true, 5); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 5); sessionManager = new UnnamedSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, TIMEOUT, tracerProvider, messageSerializer, receiverOptions); @@ -160,7 +160,7 @@ void receiveNull() { @Test void singleUnnamedSession() { // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false, null); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, null); sessionManager = new UnnamedSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, TIMEOUT, tracerProvider, messageSerializer, receiverOptions); @@ -212,7 +212,7 @@ void singleUnnamedSession() { @Test void multipleSessions() { // Arrange - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, true, 1); + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1); sessionManager = new UnnamedSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, TIMEOUT, tracerProvider, messageSerializer, receiverOptions); From b4aeb731aed3cddfaca7b8bf0a3b2561db053fc3 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 22 Oct 2020 11:06:10 -0700 Subject: [PATCH 02/23] More small fixes after merging from master --- .../ServiceBusReceiverAsyncClient.java | 21 ++++++++-------- .../servicebus/ServiceBusSessionManager.java | 4 ++-- .../ServiceBusSessionReceiverAsyncClient.java | 24 +++++++++---------- .../ServiceBusAsyncConsumerTest.java | 6 ++--- .../ServiceBusSessionManagerTest.java | 3 +-- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index 4530660eb51a..d5695dc7a55a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -1124,14 +1124,15 @@ private String getLinkName(String sessionId) { } } - private Mono validateSession(String sessionId) { - String optionSessionId = receiverOptions.getSessionId(); - if (optionSessionId != null && !optionSessionId.equals(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another %s.", - optionSessionId, sessionId))); - } else { - return Mono.just(true); - } - } + //TODO: discuss whether we should disallow session id call on peek, receiveDeferred. +// private Mono validateSession(String sessionId) { +// String optionSessionId = receiverOptions.getSessionId(); +// if (optionSessionId != null && !optionSessionId.equals(sessionId)) { +// return monoError(logger, new IllegalArgumentException(String.format( +// "This receiver client is tied to session %s. It can't be used for another %s.", +// optionSessionId, sessionId))); +// } else { +// return Mono.just(true); +// } +// } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionManager.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionManager.java index 8e269ab2c724..d0ed4b2c3d2c 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionManager.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionManager.java @@ -108,9 +108,9 @@ class ServiceBusSessionManager implements AutoCloseable { } ServiceBusSessionManager(String entityPath, MessagingEntityType entityType, - ServiceBusConnectionProcessor connectionProcessor, Duration operationTimeout, TracerProvider tracerProvider, + ServiceBusConnectionProcessor connectionProcessor, TracerProvider tracerProvider, MessageSerializer messageSerializer, ReceiverOptions receiverOptions) { - this(entityPath, entityType, connectionProcessor, operationTimeout, tracerProvider, + this(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, receiverOptions, null); } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 1f8feaf1b22a..1d685e676e96 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -34,7 +34,7 @@ public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { private final TracerProvider tracerProvider; private final MessageSerializer messageSerializer; private final Runnable onClientClose; - private final UnnamedSessionManager unNamedSessionManager; // for acceptNextSession() + private final ServiceBusSessionManager unNamedSessionManager; // for acceptNextSession() private final ClientLogger logger = new ClientLogger(ServiceBusSessionReceiverAsyncClient.class); ServiceBusSessionReceiverAsyncClient(String fullyQualifiedNamespace, String entityPath, @@ -50,8 +50,8 @@ public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { this.tracerProvider = Objects.requireNonNull(tracerProvider, "'tracerProvider' cannot be null."); this.messageSerializer = Objects.requireNonNull(messageSerializer, "'messageSerializer' cannot be null."); this.onClientClose = Objects.requireNonNull(onClientClose, "'onClientClose' cannot be null."); - this.unNamedSessionManager = new UnnamedSessionManager(entityPath, entityType, - connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, + this.unNamedSessionManager = new ServiceBusSessionManager(entityPath, entityType, + connectionProcessor, tracerProvider, messageSerializer, receiverOptions); } @@ -64,10 +64,10 @@ public Mono acceptNextSession() { return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() .map(sessionId -> { ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), sessionId, null); - final UnnamedSessionManager sessionSpecificManager = new UnnamedSessionManager(entityPath, entityType, - connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, - messageSerializer, newReceiverOptions, receiveLink); + this.receiverOptions.getPrefetchCount(), null); + final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, + entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions, + receiveLink); return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, tracerProvider, messageSerializer, onClientClose, sessionSpecificManager); @@ -86,10 +86,9 @@ public Mono acceptSession(String sessionId) { return monoError(logger, new IllegalArgumentException("sessionId can not be null or empty")); } ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), sessionId, null); - final UnnamedSessionManager sessionSpecificManager = new UnnamedSessionManager(entityPath, entityType, - connectionProcessor, connectionProcessor.getRetryOptions().getTryTimeout(), tracerProvider, - messageSerializer, newReceiverOptions); + this.receiverOptions.getPrefetchCount(), null); + final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, + connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); return sessionSpecificManager.getActiveLink().thenReturn(new ServiceBusReceiverAsyncClient( fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, @@ -111,7 +110,8 @@ public ServiceBusReceiverAsyncClient getReceiverClient(int maxConcurrentSessions new IllegalArgumentException("Maximum number of concurrent sessions must be positive.")); } ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), null, maxConcurrentSessions); + this.receiverOptions.getPrefetchCount(), null, maxConcurrentSessions, + this.receiverOptions.getMaxLockRenewDuration()); return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, tracerProvider, messageSerializer, onClientClose); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusAsyncConsumerTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusAsyncConsumerTest.java index e6a64333345f..1e67efa978cd 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusAsyncConsumerTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusAsyncConsumerTest.java @@ -115,7 +115,7 @@ void receiveNoAutoComplete() { final int prefetch = 10; final Duration maxAutoLockRenewDuration = Duration.ofSeconds(0); final OffsetDateTime lockedUntil = OffsetDateTime.now().plusSeconds(3); - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", false, 1, maxAutoLockRenewDuration); + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", 1, maxAutoLockRenewDuration); final ServiceBusAsyncConsumer consumer = new ServiceBusAsyncConsumer(LINK_NAME, linkProcessor, serializer, receiverOptions); @@ -161,7 +161,7 @@ void canDispose() { final Duration maxAutoLockRenewDuration = Duration.ofSeconds(40); final OffsetDateTime lockedUntil = OffsetDateTime.now().plusSeconds(3); final String lockToken = UUID.randomUUID().toString(); - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", false, 1, maxAutoLockRenewDuration); + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", 1, maxAutoLockRenewDuration); final ServiceBusAsyncConsumer consumer = new ServiceBusAsyncConsumer(LINK_NAME, linkProcessor, serializer, receiverOptions); @@ -200,7 +200,7 @@ void onError() { final Duration maxAutoLockRenewDuration = Duration.ofSeconds(40); final OffsetDateTime lockedUntil = OffsetDateTime.now().plusSeconds(3); final String lockToken = UUID.randomUUID().toString(); - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", false, 1, maxAutoLockRenewDuration); + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.RECEIVE_AND_DELETE, prefetch, "sessionId", 1, maxAutoLockRenewDuration); final ServiceBusAsyncConsumer consumer = new ServiceBusAsyncConsumer(LINK_NAME, linkProcessor, serializer, receiverOptions); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java index 40a6cbe1ab58..18c08ea819a3 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java @@ -345,8 +345,7 @@ void multipleSessions() { void multipleReceiveUnnamedSession() { // Arrange final int expectedLinksCreated = 2; - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, - false, 1, Duration.ZERO); + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, Duration.ZERO); sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, tracerProvider, messageSerializer, receiverOptions); From b7d7ab16c4d121d234e81e247d038516c6676cc4 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 22 Oct 2020 16:56:18 -0700 Subject: [PATCH 03/23] More small fixes after merging from master --- .../servicebus/ServiceBusSessionReceiverAsyncClient.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 1d685e676e96..6821e96b255d 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -63,8 +63,8 @@ public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { public Mono acceptNextSession() { return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() .map(sessionId -> { - ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), null); + ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), + receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions, receiveLink); @@ -85,8 +85,8 @@ public Mono acceptSession(String sessionId) { if (CoreUtils.isNullOrEmpty(sessionId)) { return monoError(logger, new IllegalArgumentException("sessionId can not be null or empty")); } - ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), null); + ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), + receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); From d9af703b7660949d41fb7405fd4ac2963d9a4042 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 22 Oct 2020 17:39:04 -0700 Subject: [PATCH 04/23] create an unnamed session manager for getReceiverClient --- .../ServiceBusSessionReceiverAsyncClient.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 6821e96b255d..52ebcede347a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -109,12 +109,14 @@ public ServiceBusReceiverAsyncClient getReceiverClient(int maxConcurrentSessions throw logger.logExceptionAsError( new IllegalArgumentException("Maximum number of concurrent sessions must be positive.")); } - ReceiverOptions newReceiverOptions = new ReceiverOptions(this.receiverOptions.getReceiveMode(), - this.receiverOptions.getPrefetchCount(), null, maxConcurrentSessions, - this.receiverOptions.getMaxLockRenewDuration()); + ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), + receiverOptions.getPrefetchCount(), null, maxConcurrentSessions, + receiverOptions.getMaxLockRenewDuration()); + ServiceBusSessionManager newSessionManager = new ServiceBusSessionManager(entityPath, entityType, + connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, - tracerProvider, messageSerializer, onClientClose); + tracerProvider, messageSerializer, onClientClose, newSessionManager); } @Override From f59e84f58b26ee516ed71b62add29e268c47dec8 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Tue, 27 Oct 2020 16:35:04 -0700 Subject: [PATCH 05/23] Update code review comment --- .../messaging/servicebus/ReceiverOptions.java | 5 +- .../servicebus/ServiceBusClientBuilder.java | 2 +- .../ServiceBusReceiverAsyncClient.java | 83 ++-- .../servicebus/ServiceBusReceiverClient.java | 7 + .../ServiceBusSessionReceiverAsyncClient.java | 86 ++-- .../ServiceBusSessionReceiverClient.java | 88 ++-- .../ReceiveMultipleSessionsAsyncSample.java | 77 ---- ...ReceiverAsyncClientJavaDocCodeSamples.java | 30 +- ...iceBusReceiverClientJavaDocCodeSample.java | 33 ++ ...rviceBusSessionManagerIntegrationTest.java | 66 --- ...viceBusSessionReceiverAsyncClientTest.java | 405 ++++++++++++++++++ 11 files changed, 613 insertions(+), 269 deletions(-) delete mode 100644 sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java create mode 100644 sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java index 84297166673b..8fb863dce8ca 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java @@ -15,7 +15,6 @@ class ReceiverOptions { private final int prefetchCount; private final String sessionId; private final Integer maxConcurrentSessions; - private final boolean isSessionReceiver; private final Duration maxLockRenewDuration; ReceiverOptions(ReceiveMode receiveMode, int prefetchCount, Duration maxLockRenewDuration) { @@ -24,7 +23,6 @@ class ReceiverOptions { this.maxLockRenewDuration = maxLockRenewDuration; this.sessionId = null; this.maxConcurrentSessions = null; - this.isSessionReceiver = false; } ReceiverOptions(ReceiveMode receiveMode, int prefetchCount, String sessionId, @@ -34,7 +32,6 @@ class ReceiverOptions { this.sessionId = sessionId; this.maxConcurrentSessions = maxConcurrentSessions; this.maxLockRenewDuration = maxLockRenewDuration; - this.isSessionReceiver = true; } /** @@ -87,7 +84,7 @@ boolean isAutoLockRenewEnabled() { * @return true if it is a session-aware receiver; false otherwise. */ boolean isSessionReceiver() { - return isSessionReceiver; + return sessionId != null || maxConcurrentSessions != null; } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java index e4434125a006..326690c610db 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java @@ -767,7 +767,7 @@ public ServiceBusSessionReceiverAsyncClient buildAsyncClient() { * queueName()} or {@link #topicName(String) topicName()}, respectively. */ public ServiceBusSessionReceiverClient buildClient() { - return new ServiceBusSessionReceiverClient(this.buildAsyncClient(), retryOptions.getTryTimeout()); + return new ServiceBusSessionReceiverClient(this.buildAsyncClient()); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index d5695dc7a55a..532abc668604 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -72,15 +72,7 @@ *

To process messages from the first available session, switch to {@link ServiceBusSessionReceiverClientBuilder} and * build the session receiver client. Use {@link ServiceBusSessionReceiverAsyncClient#acceptNextSession()} to * find the first available session to process messages from.

- * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession} - * - *

Process messages from multiple sessions

- *

To process messages from multiple sessions, switch to {@link ServiceBusSessionReceiverClientBuilder} and - * build the session receiver client, then use {@link ServiceBusSessionReceiverAsyncClient#getReceiverClient(int)} - * to create an receiver client that processes events from multiple sessions in parallel. - * In addition, when all the messages in a session have been consumed, it will find the - * next available session to process.

- * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions} + * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} * *

Rate limiting consumption of messages from Service Bus resource

*

For message receivers that need to limit the number of messages they receive at a given time, they can use @@ -393,6 +385,11 @@ public Mono getSessionState(String sessionId) { } if (sessionManager != null) { + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return sessionManager.getSessionState(sessionId); } else { return connectionProcessor @@ -429,7 +426,11 @@ public Mono peekMessage(String sessionId) { return monoError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peek"))); } - + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(channel -> { @@ -475,7 +476,11 @@ public Mono peekMessageAt(long sequenceNumber, String return monoError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekAt"))); } - + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(node -> node.peek(sequenceNumber, sessionId, getLinkName(sessionId))); @@ -509,7 +514,11 @@ public Flux peekMessages(int maxMessages, String sess return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekBatch"))); } - + if (validateSessionId(sessionId)) { + return fluxError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> { @@ -572,7 +581,11 @@ public Flux peekMessagesAt(int maxMessages, long sequ return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekBatchAt"))); } - + if (validateSessionId(sessionId)) { + return fluxError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> node.peek(sequenceNumber, sessionId, getLinkName(sessionId), maxMessages)); @@ -632,6 +645,11 @@ public Mono receiveDeferredMessage(long sequenceNumbe * @return A deferred message with the matching {@code sequenceNumber}. */ public Mono receiveDeferredMessage(long sequenceNumber, String sessionId) { + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(node -> node.receiveDeferredMessages(receiverOptions.getReceiveMode(), @@ -677,7 +695,11 @@ public Flux receiveDeferredMessages(Iterable se return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "receiveDeferredMessageBatch"))); } - + if (validateSessionId(sessionId)) { + return fluxError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> node.receiveDeferredMessages(receiverOptions.getReceiveMode(), @@ -802,7 +824,11 @@ public Mono renewSessionLock(String sessionId) { } else if (!receiverOptions.isSessionReceiver()) { return monoError(logger, new IllegalStateException("Cannot renew session lock on a non-session receiver.")); } - + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } final String linkName = sessionManager != null ? sessionManager.getLinkName(sessionId) : null; @@ -840,7 +866,11 @@ public Mono renewSessionLock(String sessionId, Duration maxLockRenewalDura } else if (sessionId.isEmpty()) { return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty.")); } - + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } final LockRenewalOperation operation = new LockRenewalOperation(sessionId, maxLockRenewalDuration, true, this::renewSessionLock); @@ -864,7 +894,11 @@ public Mono setSessionState(String sessionId, byte[] sessionState) { } else if (!receiverOptions.isSessionReceiver()) { return monoError(logger, new IllegalStateException("Cannot set session state on a non-session receiver.")); } - + if (validateSessionId(sessionId)) { + return monoError(logger, new IllegalArgumentException(String.format( + "This receiver client is tied to session %s. It can't be used for another session(%s).", + receiverOptions.getSessionId(), sessionId))); + } final String linkName = sessionManager != null ? sessionManager.getLinkName(sessionId) : null; @@ -1124,15 +1158,8 @@ private String getLinkName(String sessionId) { } } - //TODO: discuss whether we should disallow session id call on peek, receiveDeferred. -// private Mono validateSession(String sessionId) { -// String optionSessionId = receiverOptions.getSessionId(); -// if (optionSessionId != null && !optionSessionId.equals(sessionId)) { -// return monoError(logger, new IllegalArgumentException(String.format( -// "This receiver client is tied to session %s. It can't be used for another %s.", -// optionSessionId, sessionId))); -// } else { -// return Mono.just(true); -// } -// } + private boolean validateSessionId(String sessionId) { + String optionSessionId = receiverOptions.getSessionId(); + return optionSessionId != null && !optionSessionId.equals(sessionId); + } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java index 38c550a96a15..851b2626acdd 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java @@ -53,6 +53,13 @@ public final class ServiceBusReceiverClient implements AutoCloseable { this.operationTimeout = Objects.requireNonNull(operationTimeout, "'operationTimeout' cannot be null."); } + // TODO: ServiceBusReceiverClient will remove the above constructor that accepts operationTimeout. + // This one is added because ServiceBusSessionReceiverClient need this constructor right now. + ServiceBusReceiverClient(ServiceBusReceiverAsyncClient asyncClient) { + this.asyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); + this.operationTimeout = Duration.ofDays(Long.MAX_VALUE); // TODO: remove after ServiceBusReceiverClient update + } + /** * Gets the fully qualified Service Bus namespace that the connection is associated with. This is likely similar to * {@code {yournamespace}.servicebus.windows.net}. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 52ebcede347a..bb52783ef908 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -17,15 +17,26 @@ import static com.azure.core.util.FluxUtil.monoError; /** - * The builder that creates session-related {@link ServiceBusReceiverAsyncClient} instances. - * Use {@link #acceptSession(String)} to create a {@link ServiceBusReceiverAsyncClient} that is tied to a known specific - * session id. - * Use {@link #acceptNextSession()} to create a {@link ServiceBusReceiverAsyncClient} that is tied to an unknown - * available session. - * Use {@link #getReceiverClient(int)} to create a {@link ServiceBusReceiverAsyncClient} that - * process events from up to maxConcurrentSessions number of sessions. + * This session receiver client is used to acquire session locks from a queue or topic and create + * {@link ServiceBusReceiverAsyncClient} instances that are tied to the locked sessions. + *

    + *
  • + * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. + *

    + * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} + *

    + *
  • + *
  • + * Use {@link #acceptNextSession()} to acquire the lock of the next availabe session + * without specifying the session id. + *

    + * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} + *

    + *
  • + *
+ * */ -public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { +public final class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { private final String fullyQualifiedNamespace; private final String entityPath; private final MessagingEntityType entityType; @@ -50,75 +61,62 @@ public class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { this.tracerProvider = Objects.requireNonNull(tracerProvider, "'tracerProvider' cannot be null."); this.messageSerializer = Objects.requireNonNull(messageSerializer, "'messageSerializer' cannot be null."); this.onClientClose = Objects.requireNonNull(onClientClose, "'onClientClose' cannot be null."); - this.unNamedSessionManager = new ServiceBusSessionManager(entityPath, entityType, - connectionProcessor, tracerProvider, - messageSerializer, receiverOptions); + this.unNamedSessionManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, + tracerProvider, messageSerializer, receiverOptions); } /** - * Create a link for the next available session and use the link to create a {@link ServiceBusReceiverAsyncClient} - * to receive messages from that session. + * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverAsyncClient} + * to receive messages from the session. + *

Accept next available session

+ * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} + *

* @return A {@link ServiceBusReceiverAsyncClient} that is tied to the available session. + * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. */ public Mono acceptNextSession() { return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() .map(sessionId -> { - ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), + final ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions, receiveLink); return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, - tracerProvider, messageSerializer, onClientClose, sessionSpecificManager); + tracerProvider, messageSerializer, () -> { }, sessionSpecificManager); })); } /** - * Create a link for the "sessionId" and use the link to create a {@link ServiceBusReceiverAsyncClient} + * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverAsyncClient} * to receive messages from the session. + *

+ * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} + *

* @param sessionId The session Id. * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the specified session. - * @throws IllegalArgumentException if {@code sessionId} is null or empty. + * @throws NullPointerException if {@code sessionId} is null. + * @throws IllegalArgumentException if {@code sessionId} is empty. + * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. + * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. */ public Mono acceptSession(String sessionId) { + sessionId = Objects.requireNonNull(sessionId, "'sessionId' cannot be null"); if (CoreUtils.isNullOrEmpty(sessionId)) { - return monoError(logger, new IllegalArgumentException("sessionId can not be null or empty")); + return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty")); } - ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), + final ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); - return sessionSpecificManager.getActiveLink().thenReturn(new ServiceBusReceiverAsyncClient( + return sessionSpecificManager.getActiveLink().map(receiveLink -> new ServiceBusReceiverAsyncClient( fullyQualifiedNamespace, entityPath, entityType, newReceiverOptions, connectionProcessor, - ServiceBusConstants.OPERATION_TIMEOUT, tracerProvider, messageSerializer, onClientClose, + ServiceBusConstants.OPERATION_TIMEOUT, tracerProvider, messageSerializer, () -> { }, sessionSpecificManager)); } - /** - * Create a {@link ServiceBusReceiverAsyncClient} that processes at most {@code maxConcurrentSessions} sessions. - * - * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. - * - * @return The {@link ServiceBusReceiverAsyncClient} object that will be used to receive messages. - * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. - */ - public ServiceBusReceiverAsyncClient getReceiverClient(int maxConcurrentSessions) { - if (maxConcurrentSessions < 1) { - throw logger.logExceptionAsError( - new IllegalArgumentException("Maximum number of concurrent sessions must be positive.")); - } - ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), - receiverOptions.getPrefetchCount(), null, maxConcurrentSessions, - receiverOptions.getMaxLockRenewDuration()); - ServiceBusSessionManager newSessionManager = new ServiceBusSessionManager(entityPath, entityType, - connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); - return new ServiceBusReceiverAsyncClient(fullyQualifiedNamespace, entityPath, - entityType, newReceiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, - tracerProvider, messageSerializer, onClientClose, newSessionManager); - } - @Override public void close() { this.onClientClose.run(); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index 9605393e860d..f333e623478a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -7,58 +7,98 @@ import java.util.Objects; /** - * The builder that creates session-related {@link ServiceBusReceiverClient} instances. - * Use {@link #acceptSession(String)} to create a {@link ServiceBusReceiverClient} that is tied to a known specific - * session id. - * Use {@link #acceptNextSession()} to create a {@link ServiceBusReceiverClient} that is tied to an unknown - * available session. - * Use {@link #getReceiverClient(int)} to create a {@link ServiceBusReceiverClient} that - * process events from up to maxConcurrentSessions number of sessions. + * This session receiver client is used to acquire session locks from a queue or topic and create + * {@link ServiceBusReceiverClient} instances that are tied to the locked sessions. + *
    + *
  • + * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. + *

    + * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} + *

    + *
  • + *
  • + * Use {@link #acceptNextSession()} to acquire the lock of the next availabe session + * without specifying the session id. + *

    + * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} + *

    + *
  • + *
+ * */ public class ServiceBusSessionReceiverClient implements AutoCloseable { private final ServiceBusSessionReceiverAsyncClient sessionAsyncClient; - private final Duration operationTimeout; - ServiceBusSessionReceiverClient(ServiceBusSessionReceiverAsyncClient asyncClient, Duration operationTimeout) { + ServiceBusSessionReceiverClient(ServiceBusSessionReceiverAsyncClient asyncClient) { this.sessionAsyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); - this.operationTimeout = Objects.requireNonNull(operationTimeout, "'operationTimeout' cannot be null."); + } + + /** + * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverClient} + * to receive messages from the session. It will wait if no session is available. + *

Accept next available session

+ * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} + *

+ * @return A {@link ServiceBusReceiverClient} that is tied to the available session. + * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. + */ + public ServiceBusReceiverClient acceptNextSession() { + return sessionAsyncClient.acceptNextSession() + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) + .block(); } /** * Create a link for the next available session and use the link to create a {@link ServiceBusReceiverClient} * to receive messages from that session. + * @param timeout the call is cancelled after this duration. * @return A {@link ServiceBusReceiverClient} that is tied to the available session. + * @throws IllegalStateException if the operation times out. */ - public ServiceBusReceiverClient acceptNextSession() { + public ServiceBusReceiverClient acceptNextSession(Duration timeout) { + Objects.requireNonNull(timeout, "'timeout' can not be null."); return sessionAsyncClient.acceptNextSession() - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) - .block(operationTimeout); + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) + .block(timeout); } /** - * Create a link for the "sessionId" and use the link to create a {@link ServiceBusReceiverClient} + * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverClient} * to receive messages from the session. + *

+ * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} + *

* @param sessionId The session Id. * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. - * @throws IllegalArgumentException if {@code sessionId} is null or empty. + * @throws NullPointerException if {@code sessionId} is null. + * @throws IllegalArgumentException if {@code sessionId} is empty. + * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. + * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. */ public ServiceBusReceiverClient acceptSession(String sessionId) { return sessionAsyncClient.acceptSession(sessionId) - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) - .block(operationTimeout); + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) + .block(); } /** - * Create a {@link ServiceBusReceiverClient} that processes at most {@code maxConcurrentSessions} sessions. + * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverClient} + * to receive messages from the session. * - * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. + * @param sessionId The session Id. + * @param timeout the call is cancelled after this duration. * - * @return The {@link ServiceBusReceiverClient} object that will be used to receive messages. - * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. + * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. + * @throws NullPointerException if {@code sessionId} is null. + * @throws IllegalArgumentException if {@code sessionId} is empty. + * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. + * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. */ - public ServiceBusReceiverClient getReceiverClient(int maxConcurrentSessions) { - return new ServiceBusReceiverClient(sessionAsyncClient.getReceiverClient(maxConcurrentSessions), - operationTimeout); + public ServiceBusReceiverClient acceptSession(String sessionId, Duration timeout) { + Objects.requireNonNull(timeout, "'timeout' can not be null."); + return sessionAsyncClient.acceptSession(sessionId) + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) + .block(timeout); } @Override diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java deleted file mode 100644 index b7b5c0ee3702..000000000000 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ReceiveMultipleSessionsAsyncSample.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.messaging.servicebus; - -import reactor.core.Disposable; -import reactor.core.publisher.Mono; - -import java.security.SecureRandom; -import java.util.concurrent.TimeUnit; - -/** - * Sample demonstrates how to receive and process multiple sessions. In the sample, at most 3 sessions are processed - * concurrently. When there are no more messages in a session, the receiver finds the next available session to - * process. - */ -public class ReceiveMultipleSessionsAsyncSample { - private static final SecureRandom RANDOM = new SecureRandom(); - - /** - * Main method to invoke this demo on how to receive messages from multiple sessions in an Azure Service Bus Queue. - * - * @param args Unused arguments to the program. - * - * @throws InterruptedException If the program is unable to sleep while waiting for the operations to complete. - */ - public static void main(String[] args) throws InterruptedException { - - // The connection string value can be obtained by: - // 1. Going to your Service Bus namespace in Azure Portal. - // 2. Go to "Shared access policies" - // 3. Copy the connection string for the "RootManageSharedAccessKey" policy. - String connectionString = "Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" - + "SharedAccessKey={key}"; - - // Create a receiver. - // "<>" will be the name of the Service Bus session-enabled queue instance you created inside the - // Service Bus namespace. - ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() - .connectionString(connectionString) - .sessionReceiver() - .queueName("<>") - .buildAsyncClient(); - - ServiceBusReceiverAsyncClient receiver = sessionReceiver.getReceiverClient(3); - Disposable subscription = receiver.receiveMessages() - .flatMap(context -> { - if (context.hasError()) { - System.out.printf("An error occurred in session %s. Error: %s%n", - context.getSessionId(), context.getThrowable()); - - return Mono.empty(); - } - - System.out.println("Processing message from session: " + context.getSessionId()); - - // Change the `messageProcessed` according to you business logic and if you are able to process the - // message successfully. In this case, we randomly get a boolean to determine if processing was - // successful or not. - boolean messageProcessed = RANDOM.nextBoolean(); - if (messageProcessed) { - return receiver.complete(context.getMessage()); - } else { - return receiver.abandon(context.getMessage()); - } - }).subscribe(); - - // Subscribe is not a blocking call so we sleep here so the program does not end. - TimeUnit.SECONDS.sleep(60); - - // Disposing of the subscription will cancel the receive() operation. - subscription.dispose(); - - // Close the receiver. - receiver.close(); - } -} diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java index 292e5e0d8f6b..2d4a63b83d71 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientJavaDocCodeSamples.java @@ -145,7 +145,7 @@ public void receiveAll() { * Demonstrates how to create a session receiver for a single, first available session. */ public void sessionReceiverSingleInstantiation() { - // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession + // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + "SharedAccessKey={key};EntityPath={eh-name}") @@ -153,23 +153,21 @@ public void sessionReceiverSingleInstantiation() { .queueName("<< QUEUE NAME >>") .buildAsyncClient(); Mono receiverMono = sessionReceiver.acceptNextSession(); - // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#singlesession + // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession sessionReceiver.close(); } /** - * Demonstrates how to create a session receiver for a specific session. + * Demonstrates how to create a session receiver for a single know session id. */ - public void sessionReceiverNamedInstantiation() { + public void sessionReceiverSessionIdInstantiation() { // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + "SharedAccessKey={key};EntityPath={eh-name}") .sessionReceiver() - .topicName("<< TOPIC NAME >>") - .subscriptionName("<< SUBSCRIPTION NAME >>") - //.sessionId("<< my-session-id >>") + .queueName("<< QUEUE NAME >>") .buildAsyncClient(); Mono receiverMono = sessionReceiver.acceptSession("<< my-session-id >>"); // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId @@ -177,24 +175,6 @@ public void sessionReceiverNamedInstantiation() { sessionReceiver.close(); } - /** - * Demonstrates how to create a session receiver for processing multiple sessions. - */ - public void sessionReceiverMultipleInstantiation() { - // BEGIN: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions - ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() - .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" - + "SharedAccessKey={key};EntityPath={eh-name}") - .sessionReceiver() - .topicName("<< TOPIC NAME >>") - .subscriptionName("<< SUBSCRIPTION NAME >>") - .buildAsyncClient(); - ServiceBusReceiverAsyncClient receiver = sessionReceiver.getReceiverClient(3); - // END: com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#multiplesessions - - sessionReceiver.close(); - } - public void createTransaction() { // The required parameters is connectionString, a way to authenticate with Service Bus using credentials. ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() diff --git a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverClientJavaDocCodeSample.java b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverClientJavaDocCodeSample.java index 57da95ac8ff8..9732f361355f 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverClientJavaDocCodeSample.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/samples/java/com/azure/messaging/servicebus/ServiceBusReceiverClientJavaDocCodeSample.java @@ -24,4 +24,37 @@ public void instantiate() { receiver.close(); } + + /** + * Demonstrates how to create a session receiver for a single, first available session. + */ + public void sessionReceiverSingleInstantiation() { + // BEGIN: com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession + ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder() + .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + + "SharedAccessKey={key};EntityPath={eh-name}") + .sessionReceiver() + .queueName("<< QUEUE NAME >>") + .buildClient(); + ServiceBusReceiverClient receiver = sessionReceiver.acceptNextSession(); + // END: com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession + sessionReceiver.close(); + } + + /** + * Demonstrates how to create a session receiver for a single know session id. + */ + public void sessionReceiverSessionIdInstantiation() { + // BEGIN: com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId + ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder() + .connectionString("Endpoint={fully-qualified-namespace};SharedAccessKeyName={policy-name};" + + "SharedAccessKey={key};EntityPath={eh-name}") + .sessionReceiver() + .queueName("<< QUEUE NAME >>") + .buildClient(); + ServiceBusReceiverClient receiver = sessionReceiver.acceptSession("<< my-session-id >>"); + // END: com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId + + sessionReceiver.close(); + } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java index ce875b02bdcd..5560403bf25a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java @@ -9,7 +9,6 @@ import com.azure.messaging.servicebus.ServiceBusClientBuilder.ServiceBusSessionReceiverClientBuilder; import com.azure.messaging.servicebus.implementation.MessagingEntityType; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import reactor.core.Disposable; @@ -21,14 +20,12 @@ import java.time.Duration; import java.time.OffsetDateTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static com.azure.messaging.servicebus.TestUtils.getServiceBusMessage; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -105,69 +102,6 @@ void singleUnnamedSession(MessagingEntityType entityType) { } } - /** - * Verifies that we can roll over to a next session. - */ - @Test - void multipleSessions() { - // Arrange - final int entityIndex = TestUtils.USE_CASE_MULTIPLE_SESSION; - final String messageId = "singleUnnamedSession"; - final String now = OffsetDateTime.now().toString(); - final List sessionIds = IntStream.range(0, 3) - .mapToObj(number -> String.join("-", String.valueOf(number), "singleUnnamedSession", now)) - .collect(Collectors.toList()); - - logger.info("------ Session ids ------"); - for (int i = 0; i < sessionIds.size(); i++) { - logger.info("[{}]: {}", i, sessionIds.get(i)); - } - - final String contents = "Some-contents"; - final int numberToSend = 3; - final int maxMessages = numberToSend * sessionIds.size(); - final int maxConcurrency = 2; - final Set set = new HashSet<>(); - - setSenderAndReceiver(MessagingEntityType.SUBSCRIPTION, entityIndex, Duration.ofSeconds(20), - builder -> builder.buildAsyncClient().getReceiverClient(maxConcurrency)); - - final Disposable subscription = Flux.interval(Duration.ofMillis(500)) - .take(maxMessages) - .flatMap(index -> { - final int i = (int) (index % sessionIds.size()); - final String id = sessionIds.get(i); - final ServiceBusMessage message = getServiceBusMessage(contents, messageId) - .setSessionId(id); - messagesPending.incrementAndGet(); - return sender.sendMessage(message).thenReturn( - String.format("sessionId[%s] sent[%s] Message sent.", id, index)); - }).subscribe( - message -> logger.info(message), - error -> logger.error("Error encountered.", error), - () -> logger.info("Finished sending.")); - - // Act & Assert - try { - StepVerifier.create(receiver.receiveMessages()) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency, messageId, contents, context)) - - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency + 1, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency + 1, messageId, contents, context)) - .assertNext(context -> assertFromSession(sessionIds, set, maxConcurrency + 1, messageId, contents, context)) - .thenCancel() - .verify(Duration.ofMinutes(2)); - } finally { - subscription.dispose(); - } - } - private void assertFromSession(List sessionIds, Set currentSessions, int maxSize, String messageId, String contents, ServiceBusReceivedMessageContext context) { logger.info("Verifying message: {}", context.getSessionId()); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java new file mode 100644 index 000000000000..83eef919b91c --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java @@ -0,0 +1,405 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus; + +import com.azure.core.amqp.AmqpEndpointState; +import com.azure.core.amqp.AmqpRetryOptions; +import com.azure.core.amqp.AmqpTransportType; +import com.azure.core.amqp.ProxyOptions; +import com.azure.core.amqp.implementation.CbsAuthorizationType; +import com.azure.core.amqp.implementation.ConnectionOptions; +import com.azure.core.amqp.implementation.MessageSerializer; +import com.azure.core.amqp.implementation.TracerProvider; +import com.azure.core.credential.TokenCredential; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.logging.ClientLogger; +import com.azure.messaging.servicebus.implementation.MessagingEntityType; +import com.azure.messaging.servicebus.implementation.ServiceBusAmqpConnection; +import com.azure.messaging.servicebus.implementation.ServiceBusConnectionProcessor; +import com.azure.messaging.servicebus.implementation.ServiceBusManagementNode; +import com.azure.messaging.servicebus.implementation.ServiceBusReceiveLink; +import com.azure.messaging.servicebus.models.ReceiveMode; +import org.apache.qpid.proton.amqp.messaging.Accepted; +import org.apache.qpid.proton.engine.SslDomain; +import org.apache.qpid.proton.message.Message; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import reactor.core.publisher.EmitterProcessor; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.publisher.Mono; +import reactor.core.publisher.ReplayProcessor; +import reactor.core.scheduler.Schedulers; +import reactor.test.StepVerifier; +import reactor.test.publisher.TestPublisher; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ServiceBusSessionReceiverAsyncClientTest { + private static final ClientOptions CLIENT_OPTIONS = new ClientOptions(); + private static final Duration TIMEOUT = Duration.ofSeconds(10); + private static final Duration MAX_LOCK_RENEWAL = Duration.ofSeconds(5); + + private static final String NAMESPACE = "my-namespace-foo.net"; + private static final String ENTITY_PATH = "queue-name"; + private static final MessagingEntityType ENTITY_TYPE = MessagingEntityType.QUEUE; + + private final ClientLogger logger = new ClientLogger(ServiceBusReceiverAsyncClientTest.class); + private final ReplayProcessor endpointProcessor = ReplayProcessor.cacheLast(); + private final FluxSink endpointSink = endpointProcessor.sink(FluxSink.OverflowStrategy.BUFFER); + private final EmitterProcessor messageProcessor = EmitterProcessor.create(); + private final FluxSink messageSink = messageProcessor.sink(FluxSink.OverflowStrategy.BUFFER); + private final TracerProvider tracerProvider = new TracerProvider(Collections.emptyList()); + + private ServiceBusConnectionProcessor connectionProcessor; + private ServiceBusSessionManager sessionManager; + + @Mock + private ServiceBusReceiveLink amqpReceiveLink; + @Mock + private ServiceBusAmqpConnection connection; + @Mock + private TokenCredential tokenCredential; + @Mock + private MessageSerializer messageSerializer; + @Mock + private ServiceBusManagementNode managementNode; + @Captor + private ArgumentCaptor linkNameCaptor; + + + @BeforeAll + static void beforeAll() { + StepVerifier.setDefaultTimeout(Duration.ofSeconds(60)); + } + + @AfterAll + static void afterAll() { + StepVerifier.resetDefaultTimeout(); + } + + @BeforeEach + void beforeEach(TestInfo testInfo) { + logger.info("===== [{}] Setting up. =====", testInfo.getDisplayName()); + + MockitoAnnotations.initMocks(this); + + // Forcing us to publish the messages we receive on the AMQP link on single. Similar to how it is done + // in ReactorExecutor. + when(amqpReceiveLink.receive()).thenReturn(messageProcessor.publishOn(Schedulers.single())); + + when(amqpReceiveLink.getHostname()).thenReturn(NAMESPACE); + when(amqpReceiveLink.getEntityPath()).thenReturn(ENTITY_PATH); + when(amqpReceiveLink.getEndpointStates()).thenReturn(endpointProcessor); + + ConnectionOptions connectionOptions = new ConnectionOptions(NAMESPACE, tokenCredential, + CbsAuthorizationType.SHARED_ACCESS_SIGNATURE, AmqpTransportType.AMQP, + new AmqpRetryOptions().setTryTimeout(TIMEOUT), ProxyOptions.SYSTEM_DEFAULTS, Schedulers.boundedElastic(), + CLIENT_OPTIONS, SslDomain.VerifyMode.VERIFY_PEER_NAME); + + when(connection.getEndpointStates()).thenReturn(endpointProcessor); + endpointSink.next(AmqpEndpointState.ACTIVE); + + when(connection.getManagementNode(ENTITY_PATH, ENTITY_TYPE)) + .thenReturn(Mono.just(managementNode)); + + connectionProcessor = + Flux.create(sink -> sink.next(connection)) + .subscribeWith(new ServiceBusConnectionProcessor(connectionOptions.getFullyQualifiedNamespace(), + connectionOptions.getRetry())); + } + + @AfterEach + void afterEach(TestInfo testInfo) { + logger.info("===== [{}] Tearing down. =====", testInfo.getDisplayName()); + + if (sessionManager != null) { + sessionManager.close(); + } + + if (connectionProcessor != null) { + connectionProcessor.dispose(); + } + + Mockito.framework().clearInlineMocks(); + } + + @Test + void receiveNull() { + // Arrange + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 5, MAX_LOCK_RENEWAL); + sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, + tracerProvider, messageSerializer, receiverOptions); + + // Act & Assert + StepVerifier.create(sessionManager.receive()) + .expectError(NullPointerException.class) + .verify(); + } + + @Test + void acceptSession() { + // Arrange + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, null, MAX_LOCK_RENEWAL); + final String sessionId = "session-1"; + final String lockToken = "a-lock-token"; + final String linkName = "my-link-name"; + final OffsetDateTime sessionLockedUntil = OffsetDateTime.now().plus(Duration.ofSeconds(30)); + + final Message message = mock(Message.class); + final ServiceBusReceivedMessage receivedMessage = mock(ServiceBusReceivedMessage.class); + + when(messageSerializer.deserialize(message, ServiceBusReceivedMessage.class)).thenReturn(receivedMessage); + when(receivedMessage.getSessionId()).thenReturn(sessionId); + when(receivedMessage.getLockToken()).thenReturn(lockToken); + + final int numberOfMessages = 5; + + when(amqpReceiveLink.getLinkName()).thenReturn(linkName); + when(amqpReceiveLink.getSessionId()).thenReturn(Mono.just(sessionId)); + when(amqpReceiveLink.getSessionLockedUntil()) + .thenAnswer(invocation -> Mono.just(sessionLockedUntil)); + when(amqpReceiveLink.updateDisposition(lockToken, Accepted.getInstance())).thenReturn(Mono.empty()); + + when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), + any(MessagingEntityType.class), isNull())).thenReturn(Mono.just(amqpReceiveLink)); + + ServiceBusSessionReceiverAsyncClient client = new ServiceBusSessionReceiverAsyncClient( + NAMESPACE, ENTITY_PATH, + MessagingEntityType.QUEUE, receiverOptions, + connectionProcessor, tracerProvider, + messageSerializer, () -> { } + ); + StepVerifier.create(client.acceptSession(sessionId)) + .assertNext(x -> + assertNotNull(x)) + .verifyComplete(); +// StepVerifier.create(client.acceptSession(sessionId).flatMapMany(ServiceBusReceiverAsyncClient::receiveMessages)) +// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) +// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) +// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) +// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) +// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) +// .verifyComplete(); + } + + @Test + void multipleSessions() { + // Arrange + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, MAX_LOCK_RENEWAL); + sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, + tracerProvider, messageSerializer, receiverOptions); + + final int numberOfMessages = 5; + final Callable onRenewal = () -> OffsetDateTime.now().plus(Duration.ofSeconds(5)); + + final String sessionId = "session-1"; + final String lockToken = "a-lock-token"; + final String linkName = "my-link-name"; + + final Message message = mock(Message.class); + final ServiceBusReceivedMessage receivedMessage = mock(ServiceBusReceivedMessage.class); + + when(receivedMessage.getSessionId()).thenReturn(sessionId); + when(receivedMessage.getLockToken()).thenReturn(lockToken); + + when(amqpReceiveLink.getLinkName()).thenReturn(linkName); + when(amqpReceiveLink.getSessionId()).thenReturn(Mono.just(sessionId)); + when(amqpReceiveLink.getSessionLockedUntil()).thenReturn(Mono.fromCallable(onRenewal)); + when(amqpReceiveLink.updateDisposition(lockToken, Accepted.getInstance())).thenReturn(Mono.empty()); + + // Session 2's messages + final ServiceBusReceiveLink amqpReceiveLink2 = mock(ServiceBusReceiveLink.class); + final Message message2 = mock(Message.class); + final ServiceBusReceivedMessage receivedMessage2 = mock(ServiceBusReceivedMessage.class); + final String sessionId2 = "session-2"; + final String lockToken2 = "a-lock-token-2"; + final String linkName2 = "my-link-name-2"; + final TestPublisher messagePublisher2 = TestPublisher.create(); + final Flux messageFlux2 = messagePublisher2.flux(); + + when(receivedMessage2.getSessionId()).thenReturn(sessionId2); + when(receivedMessage2.getLockToken()).thenReturn(lockToken2); + + when(amqpReceiveLink2.receive()).thenReturn(messageFlux2); + when(amqpReceiveLink2.getHostname()).thenReturn(NAMESPACE); + when(amqpReceiveLink2.getEntityPath()).thenReturn(ENTITY_PATH); + when(amqpReceiveLink2.getEndpointStates()).thenReturn(endpointProcessor); + when(amqpReceiveLink2.getLinkName()).thenReturn(linkName2); + when(amqpReceiveLink2.getSessionId()).thenReturn(Mono.just(sessionId2)); + when(amqpReceiveLink2.getSessionLockedUntil()).thenReturn(Mono.fromCallable(onRenewal)); + when(amqpReceiveLink2.updateDisposition(lockToken2, Accepted.getInstance())).thenReturn(Mono.empty()); + + final AtomicInteger count = new AtomicInteger(); + when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), + any(MessagingEntityType.class), isNull())).thenAnswer(invocation -> { + final int number = count.getAndIncrement(); + switch (number) { + case 0: + return Mono.just(amqpReceiveLink); + case 1: + return Mono.just(amqpReceiveLink2); + default: + return Mono.empty(); + } + }); + + when(messageSerializer.deserialize(message, ServiceBusReceivedMessage.class)).thenReturn(receivedMessage); + when(messageSerializer.deserialize(message2, ServiceBusReceivedMessage.class)).thenReturn(receivedMessage2); + + when(managementNode.renewSessionLock(sessionId, linkName)).thenReturn(Mono.fromCallable(onRenewal)); + when(managementNode.renewSessionLock(sessionId2, linkName2)).thenReturn(Mono.fromCallable(onRenewal)); + + // Act & Assert + StepVerifier.create(sessionManager.receive()) + .then(() -> { + for (int i = 0; i < numberOfMessages; i++) { + messageSink.next(message); + } + }) + .assertNext(context -> { + System.out.println("1"); + assertMessageEquals(sessionId, receivedMessage, context); + }) + .assertNext(context -> { + System.out.println("2"); + assertMessageEquals(sessionId, receivedMessage, context); + }) + .assertNext(context -> { + System.out.println("3"); + assertMessageEquals(sessionId, receivedMessage, context); + }) + .assertNext(context -> { + System.out.println("4"); + assertMessageEquals(sessionId, receivedMessage, context); + }) + .assertNext(context -> { + System.out.println("5"); + assertMessageEquals(sessionId, receivedMessage, context); + }) + .thenAwait(Duration.ofSeconds(13)) + .then(() -> { + for (int i = 0; i < 3; i++) { + messagePublisher2.next(message2); + } + }) + .assertNext(context -> { + System.out.println("6"); + assertMessageEquals(sessionId2, receivedMessage2, context); + }) + .assertNext(context -> { + System.out.println("7"); + assertMessageEquals(sessionId2, receivedMessage2, context); + }) + .assertNext(context -> { + System.out.println("8"); + assertMessageEquals(sessionId2, receivedMessage2, context); + }) + .thenAwait(Duration.ofSeconds(15)) + .thenCancel() + .verify(); + } + + + /** + * Verify that when we can call multiple receive, it'll create a new link. + */ + @Test + void multipleReceiveUnnamedSession() { + // Arrange + final int expectedLinksCreated = 2; + final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, Duration.ZERO); + + sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, + tracerProvider, messageSerializer, receiverOptions); + + final String sessionId = "session-1"; + final String linkName = "my-link-name"; + + when(amqpReceiveLink.getLinkName()).thenReturn(linkName); + when(amqpReceiveLink.getSessionId()).thenReturn(Mono.just(sessionId)); + + // Session 2's + final ServiceBusReceiveLink amqpReceiveLink2 = mock(ServiceBusReceiveLink.class); + final String sessionId2 = "session-2"; + final String linkName2 = "my-link-name-2"; + final TestPublisher messagePublisher2 = TestPublisher.create(); + final Flux messageFlux2 = messagePublisher2.flux(); + + when(amqpReceiveLink2.receive()).thenReturn(messageFlux2); + when(amqpReceiveLink2.getHostname()).thenReturn(NAMESPACE); + when(amqpReceiveLink2.getEntityPath()).thenReturn(ENTITY_PATH); + when(amqpReceiveLink2.getEndpointStates()).thenReturn(endpointProcessor); + when(amqpReceiveLink2.getLinkName()).thenReturn(linkName2); + when(amqpReceiveLink2.getSessionId()).thenReturn(Mono.just(sessionId2)); + + final AtomicInteger count = new AtomicInteger(); + when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), + any(MessagingEntityType.class), isNull())).thenAnswer(invocation -> { + final int number = count.getAndIncrement(); + switch (number) { + case 0: + return Mono.just(amqpReceiveLink); + case 1: + return Mono.just(amqpReceiveLink2); + default: + return Mono.empty(); + } + }); + + // Act & Assert + StepVerifier.create(sessionManager.receive()) + .thenAwait(Duration.ofSeconds(5)) + .thenCancel() + .verify(); + + StepVerifier.create(sessionManager.receive()) + .thenAwait(Duration.ofSeconds(5)) + .thenCancel() + .verify(); + + verify(connection, times(2)).createReceiveLink(linkNameCaptor.capture(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), + any(MessagingEntityType.class), isNull()); + + final List actualLinksCreated = linkNameCaptor.getAllValues(); + assertNotNull(actualLinksCreated); + assertEquals(expectedLinksCreated, actualLinksCreated.size()); + assertFalse(actualLinksCreated.get(0).equalsIgnoreCase(actualLinksCreated.get(1))); + } + + private static void assertMessageEquals(String sessionId, ServiceBusReceivedMessage expected, + ServiceBusReceivedMessageContext actual) { + assertEquals(sessionId, actual.getSessionId()); + assertNull(actual.getThrowable()); + + assertEquals(expected, actual.getMessage()); + } +} From 8b063c35984eccb8401a477cd3ef390c0ece2cd4 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Tue, 27 Oct 2020 20:47:12 -0700 Subject: [PATCH 06/23] Add test case for ServiceBusSessionReceiverAsyncClient and -Client --- .../servicebus/ServiceBusReceiverClient.java | 3 +- .../ServiceBusSessionReceiverClient.java | 1 + ...viceBusSessionReceiverAsyncClientTest.java | 141 ++++-------------- .../ServiceBusSessionReceiverClientTest.java | 76 ++++++++++ 4 files changed, 107 insertions(+), 114 deletions(-) create mode 100644 sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java index 851b2626acdd..c63ed6b527e4 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java @@ -55,9 +55,10 @@ public final class ServiceBusReceiverClient implements AutoCloseable { // TODO: ServiceBusReceiverClient will remove the above constructor that accepts operationTimeout. // This one is added because ServiceBusSessionReceiverClient need this constructor right now. + // this.operationTimeout won't be needed after timeout is moved to methods. ServiceBusReceiverClient(ServiceBusReceiverAsyncClient asyncClient) { this.asyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); - this.operationTimeout = Duration.ofDays(Long.MAX_VALUE); // TODO: remove after ServiceBusReceiverClient update + this.operationTimeout = Duration.ofDays(10000); // TODO: remove after moving timeout to methods. } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index f333e623478a..38bd8e4ba196 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -93,6 +93,7 @@ public ServiceBusReceiverClient acceptSession(String sessionId) { * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. + * @throws IllegalStateException if the operation times out. */ public ServiceBusReceiverClient acceptSession(String sessionId, Duration timeout) { Objects.requireNonNull(timeout, "'timeout' can not be null."); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java index 83eef919b91c..23b38c8b32c0 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java @@ -29,8 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -46,21 +44,16 @@ import java.time.Duration; import java.time.OffsetDateTime; import java.util.Collections; -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class ServiceBusSessionReceiverAsyncClientTest { @@ -92,8 +85,6 @@ class ServiceBusSessionReceiverAsyncClientTest { private MessageSerializer messageSerializer; @Mock private ServiceBusManagementNode managementNode; - @Captor - private ArgumentCaptor linkNameCaptor; @BeforeAll @@ -152,26 +143,13 @@ void afterEach(TestInfo testInfo) { Mockito.framework().clearInlineMocks(); } - @Test - void receiveNull() { - // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 5, MAX_LOCK_RENEWAL); - sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, - tracerProvider, messageSerializer, receiverOptions); - - // Act & Assert - StepVerifier.create(sessionManager.receive()) - .expectError(NullPointerException.class) - .verify(); - } - @Test void acceptSession() { // Arrange ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, null, MAX_LOCK_RENEWAL); - final String sessionId = "session-1"; final String lockToken = "a-lock-token"; final String linkName = "my-link-name"; + final String sessionId = linkName; final OffsetDateTime sessionLockedUntil = OffsetDateTime.now().plus(Duration.ofSeconds(30)); final Message message = mock(Message.class); @@ -190,7 +168,7 @@ void acceptSession() { when(amqpReceiveLink.updateDisposition(lockToken, Accepted.getInstance())).thenReturn(Mono.empty()); when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), - any(MessagingEntityType.class), isNull())).thenReturn(Mono.just(amqpReceiveLink)); + any(MessagingEntityType.class), eq(sessionId))).thenReturn(Mono.just(amqpReceiveLink)); ServiceBusSessionReceiverAsyncClient client = new ServiceBusSessionReceiverAsyncClient( NAMESPACE, ENTITY_PATH, @@ -198,21 +176,24 @@ void acceptSession() { connectionProcessor, tracerProvider, messageSerializer, () -> { } ); - StepVerifier.create(client.acceptSession(sessionId)) - .assertNext(x -> - assertNotNull(x)) + + // Act & Assert + StepVerifier.create(client.acceptSession(sessionId).flatMapMany(ServiceBusReceiverAsyncClient::receiveMessages)) + .then(() -> { + for (int i = 0; i < numberOfMessages; i++) { + messageSink.next(message); + } + }) + .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) + .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) + .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) + .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) + .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) .verifyComplete(); -// StepVerifier.create(client.acceptSession(sessionId).flatMapMany(ServiceBusReceiverAsyncClient::receiveMessages)) -// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) -// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) -// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) -// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) -// .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) -// .verifyComplete(); } @Test - void multipleSessions() { + void acceptNextSession() { // Arrange final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, MAX_LOCK_RENEWAL); sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, @@ -278,123 +259,57 @@ void multipleSessions() { when(managementNode.renewSessionLock(sessionId, linkName)).thenReturn(Mono.fromCallable(onRenewal)); when(managementNode.renewSessionLock(sessionId2, linkName2)).thenReturn(Mono.fromCallable(onRenewal)); + ServiceBusSessionReceiverAsyncClient client = new ServiceBusSessionReceiverAsyncClient( + NAMESPACE, ENTITY_PATH, + MessagingEntityType.QUEUE, receiverOptions, + connectionProcessor, tracerProvider, + messageSerializer, () -> { } + ); + // Act & Assert - StepVerifier.create(sessionManager.receive()) + StepVerifier.create(client.acceptNextSession().flatMapMany(ServiceBusReceiverAsyncClient::receiveMessages)) .then(() -> { for (int i = 0; i < numberOfMessages; i++) { messageSink.next(message); } }) .assertNext(context -> { - System.out.println("1"); assertMessageEquals(sessionId, receivedMessage, context); }) .assertNext(context -> { - System.out.println("2"); assertMessageEquals(sessionId, receivedMessage, context); }) .assertNext(context -> { - System.out.println("3"); assertMessageEquals(sessionId, receivedMessage, context); }) .assertNext(context -> { - System.out.println("4"); assertMessageEquals(sessionId, receivedMessage, context); }) .assertNext(context -> { - System.out.println("5"); assertMessageEquals(sessionId, receivedMessage, context); }) - .thenAwait(Duration.ofSeconds(13)) + .thenAwait(Duration.ofSeconds(1)).thenCancel().verify(); + + StepVerifier.create(client.acceptNextSession().flatMapMany(ServiceBusReceiverAsyncClient::receiveMessages)) .then(() -> { for (int i = 0; i < 3; i++) { messagePublisher2.next(message2); } }) .assertNext(context -> { - System.out.println("6"); assertMessageEquals(sessionId2, receivedMessage2, context); }) .assertNext(context -> { - System.out.println("7"); assertMessageEquals(sessionId2, receivedMessage2, context); }) .assertNext(context -> { - System.out.println("8"); assertMessageEquals(sessionId2, receivedMessage2, context); }) - .thenAwait(Duration.ofSeconds(15)) + .thenAwait(Duration.ofSeconds(1)) .thenCancel() .verify(); } - - /** - * Verify that when we can call multiple receive, it'll create a new link. - */ - @Test - void multipleReceiveUnnamedSession() { - // Arrange - final int expectedLinksCreated = 2; - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, Duration.ZERO); - - sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, - tracerProvider, messageSerializer, receiverOptions); - - final String sessionId = "session-1"; - final String linkName = "my-link-name"; - - when(amqpReceiveLink.getLinkName()).thenReturn(linkName); - when(amqpReceiveLink.getSessionId()).thenReturn(Mono.just(sessionId)); - - // Session 2's - final ServiceBusReceiveLink amqpReceiveLink2 = mock(ServiceBusReceiveLink.class); - final String sessionId2 = "session-2"; - final String linkName2 = "my-link-name-2"; - final TestPublisher messagePublisher2 = TestPublisher.create(); - final Flux messageFlux2 = messagePublisher2.flux(); - - when(amqpReceiveLink2.receive()).thenReturn(messageFlux2); - when(amqpReceiveLink2.getHostname()).thenReturn(NAMESPACE); - when(amqpReceiveLink2.getEntityPath()).thenReturn(ENTITY_PATH); - when(amqpReceiveLink2.getEndpointStates()).thenReturn(endpointProcessor); - when(amqpReceiveLink2.getLinkName()).thenReturn(linkName2); - when(amqpReceiveLink2.getSessionId()).thenReturn(Mono.just(sessionId2)); - - final AtomicInteger count = new AtomicInteger(); - when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), - any(MessagingEntityType.class), isNull())).thenAnswer(invocation -> { - final int number = count.getAndIncrement(); - switch (number) { - case 0: - return Mono.just(amqpReceiveLink); - case 1: - return Mono.just(amqpReceiveLink2); - default: - return Mono.empty(); - } - }); - - // Act & Assert - StepVerifier.create(sessionManager.receive()) - .thenAwait(Duration.ofSeconds(5)) - .thenCancel() - .verify(); - - StepVerifier.create(sessionManager.receive()) - .thenAwait(Duration.ofSeconds(5)) - .thenCancel() - .verify(); - - verify(connection, times(2)).createReceiveLink(linkNameCaptor.capture(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), - any(MessagingEntityType.class), isNull()); - - final List actualLinksCreated = linkNameCaptor.getAllValues(); - assertNotNull(actualLinksCreated); - assertEquals(expectedLinksCreated, actualLinksCreated.size()); - assertFalse(actualLinksCreated.get(0).equalsIgnoreCase(actualLinksCreated.get(1))); - } - private static void assertMessageEquals(String sessionId, ServiceBusReceivedMessage expected, ServiceBusReceivedMessageContext actual) { assertEquals(sessionId, actual.getSessionId()); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java new file mode 100644 index 000000000000..ce7f6a700ebe --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +class ServiceBusSessionReceiverClientTest { + + @Mock + private ServiceBusSessionReceiverAsyncClient sessionAsyncClient; + + @Mock + private ServiceBusReceiverAsyncClient asyncClient; + + @BeforeEach + void beforeEach(TestInfo testInfo) { + MockitoAnnotations.initMocks(this); + } + + @AfterEach + void afterEach(TestInfo testInfo) { + Mockito.framework().clearInlineMocks(); + } + + + @Test + void acceptSession() { + when(sessionAsyncClient.acceptSession(anyString())).thenReturn(Mono.just(asyncClient)); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + + assertNotNull(sessionClient.acceptSession("sessionId")); + } + + @Test + void acceptSessionTimeout() { + when(sessionAsyncClient.acceptSession(anyString())).thenReturn(Mono.just(asyncClient) + .delayElement(Duration.ofMillis(100))); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + + assertThrows(IllegalStateException.class, + () -> sessionClient.acceptSession("sessionId", Duration.ofMillis(50))); + } + + @Test + void acceptNextSession() { + when(sessionAsyncClient.acceptNextSession()).thenReturn(Mono.just(asyncClient)); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + + assertNotNull(sessionClient.acceptNextSession()); + } + + @Test + void acceptNextSessionTimeout() { + when(sessionAsyncClient.acceptNextSession()).thenReturn(Mono.just(asyncClient) + .delayElement(Duration.ofMillis(100))); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + + assertThrows(IllegalStateException.class, + () -> sessionClient.acceptNextSession(Duration.ofMillis(50))); + } +} From 2c2c7ae7296bef5de9472f65c9310dcd32c87b3c Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Tue, 27 Oct 2020 23:50:30 -0700 Subject: [PATCH 07/23] Fix merge problems and tests --- .../messaging/servicebus/ReceiverOptions.java | 6 ++-- .../ServiceBusSessionReceiverAsyncClient.java | 6 ++-- ...BusReceiverAsyncClientIntegrationTest.java | 33 ++++++++++--------- .../ServiceBusReceiverAsyncClientTest.java | 2 +- ...rviceBusReceiverClientIntegrationTest.java | 15 +++++++-- ...ceBusSenderAsyncClientIntegrationTest.java | 23 +++---------- ...rviceBusSessionManagerIntegrationTest.java | 2 +- ...viceBusSessionReceiverAsyncClientTest.java | 4 +-- 8 files changed, 44 insertions(+), 47 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java index 729d9f06c67c..a3748b714d98 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ReceiverOptions.java @@ -3,7 +3,6 @@ package com.azure.messaging.servicebus; -import com.azure.core.util.CoreUtils; import com.azure.messaging.servicebus.models.ReceiveMode; import java.time.Duration; @@ -21,12 +20,11 @@ class ReceiverOptions { ReceiverOptions(ReceiveMode receiveMode, int prefetchCount, Duration maxLockRenewDuration, boolean enableAutoComplete) { - this(receiveMode, prefetchCount, maxLockRenewDuration, enableAutoComplete, null, false, null); + this(receiveMode, prefetchCount, maxLockRenewDuration, enableAutoComplete, null, null); } ReceiverOptions(ReceiveMode receiveMode, int prefetchCount, Duration maxLockRenewDuration, - boolean enableAutoComplete, String sessionId, boolean isRollingSessionReceiver, - Integer maxConcurrentSessions) { + boolean enableAutoComplete, String sessionId, Integer maxConcurrentSessions) { this.receiveMode = receiveMode; this.prefetchCount = prefetchCount; this.enableAutoComplete = enableAutoComplete; diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index bb52783ef908..08e8446b9f1d 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -78,7 +78,8 @@ public Mono acceptNextSession() { return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() .map(sessionId -> { final ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), - receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); + receiverOptions.getPrefetchCount(), receiverOptions.getMaxLockRenewDuration(), + receiverOptions.isAutoLockRenewEnabled(), sessionId, null); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions, receiveLink); @@ -107,7 +108,8 @@ public Mono acceptSession(String sessionId) { return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty")); } final ReceiverOptions newReceiverOptions = new ReceiverOptions(receiverOptions.getReceiveMode(), - receiverOptions.getPrefetchCount(), sessionId, null, receiverOptions.getMaxLockRenewDuration()); + receiverOptions.getPrefetchCount(), receiverOptions.getMaxLockRenewDuration(), + receiverOptions.isAutoLockRenewEnabled(), sessionId, null); final ServiceBusSessionManager sessionSpecificManager = new ServiceBusSessionManager(entityPath, entityType, connectionProcessor, tracerProvider, messageSerializer, newReceiverOptions); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java index e50d1217ffda..198dff0c7c6f 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java @@ -68,6 +68,7 @@ class ServiceBusReceiverAsyncClientIntegrationTest extends IntegrationTestBase { * Receiver used to clean up resources in {@link #afterTest()}. */ private ServiceBusReceiverAsyncClient receiveAndDeleteReceiver; + private Mono receiveAndDeleteReceiverMono; ServiceBusReceiverAsyncClientIntegrationTest() { super(new ClientLogger(ServiceBusReceiverAsyncClientIntegrationTest.class)); @@ -85,12 +86,19 @@ protected void afterTest() { final int pendingDeferred = messagesDeferredPending.size(); if (pending < 1 && pendingDeferred < 1) { dispose(receiver, sender, receiveAndDeleteReceiver); + if (receiveAndDeleteReceiverMono != null) { + dispose(receiveAndDeleteReceiverMono.block()); + } return; } // In the case that this test failed... we're going to drain the queue or subscription. try { + dispose(receiver, sender); if (pending > 0) { + if (receiveAndDeleteReceiverMono != null) { + receiveAndDeleteReceiver = receiveAndDeleteReceiverMono.block(); + } receiveAndDeleteReceiver.receiveMessages() .map(message -> { logger.info("Message received: {}", message.getMessage().getSequenceNumber()); @@ -118,7 +126,7 @@ protected void afterTest() { } catch (Exception e) { logger.warning("Error occurred when draining for deferred messages.", e); } finally { - dispose(receiver, sender, receiveAndDeleteReceiver); + dispose(receiveAndDeleteReceiver); } } @@ -312,19 +320,14 @@ void receiveTwoMessagesAutoComplete(MessagingEntityType entityType, boolean isSe if (isSessionEnabled) { assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .sessionId(sessionId) - .buildAsyncClient(); - this.receiveAndDeleteReceiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, + .buildAsyncClient().acceptSession(sessionId).block(); + this.receiveAndDeleteReceiverMono = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .sessionId(sessionId) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient(); + .buildAsyncClient().acceptSession(sessionId); } else { this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) .buildAsyncClient(); - this.receiveAndDeleteReceiver = getReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient(); } final String messageId = UUID.randomUUID().toString(); @@ -360,13 +363,11 @@ void receiveMessageAutoComplete(MessagingEntityType entityType, boolean isSessio if (isSessionEnabled) { assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .sessionId(sessionId) - .buildAsyncClient(); - this.receiveAndDeleteReceiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, + .buildAsyncClient().acceptSession(sessionId).block(); + this.receiveAndDeleteReceiverMono = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .sessionId(sessionId) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient(); + .buildAsyncClient().acceptSession(sessionId); } else { this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) .buildAsyncClient(); @@ -1266,11 +1267,11 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde this.receiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) .disableAutoComplete() .buildAsyncClient().acceptSession(sessionId).block(); - this.receiveAndDeleteReceiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, + this.receiveAndDeleteReceiverMono = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) .disableAutoComplete() .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient().acceptSession(sessionId).block(); + .buildAsyncClient().acceptSession(sessionId); } else { this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) .disableAutoComplete() diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java index 3e50eaef15f8..bfdd9d0d7f64 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientTest.java @@ -939,7 +939,7 @@ void autoCompleteMessageSessionReceiver() { final List messages = getMessages(); final String lockToken = UUID.randomUUID().toString(); final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, PREFETCH, null, - true, "Some-Session", false, null); + true, "Some-Session", null); final ServiceBusReceiverAsyncClient sessionReceiver2 = new ServiceBusReceiverAsyncClient(NAMESPACE, ENTITY_PATH, MessagingEntityType.QUEUE, receiverOptions, connectionProcessor, CLEANUP_INTERVAL, tracerProvider, messageSerializer, onClientClose); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java index 3486b7d753d0..d9f2a66753c0 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; +import reactor.core.publisher.Mono; import java.time.Duration; import java.time.OffsetDateTime; @@ -54,6 +55,7 @@ class ServiceBusReceiverClientIntegrationTest extends IntegrationTestBase { * Receiver used to clean up resources in {@link #afterTest()}. */ private ServiceBusReceiverClient receiveAndDeleteReceiver; + private Mono receiveAndDeleteReceiverMono; protected ServiceBusReceiverClientIntegrationTest() { super(new ClientLogger(ServiceBusReceiverClientIntegrationTest.class)); @@ -69,11 +71,18 @@ protected void afterTest() { final int pending = messagesPending.get(); if (pending < 1 && messagesDeferred.get().size() < 1) { dispose(receiver, sender, receiveAndDeleteReceiver); + if (receiveAndDeleteReceiverMono != null) { + dispose(receiveAndDeleteReceiverMono.block()); + } return; } // In the case that this test failed... we're going to drain the queue or subscription. if (pending > 0) { + dispose(receiver, sender); + if (receiveAndDeleteReceiverMono != null) { + receiveAndDeleteReceiver = receiveAndDeleteReceiverMono.block(); + } try { IterableStream removedMessage = receiveAndDeleteReceiver.receiveMessages( pending, Duration.ofSeconds(15)); @@ -99,7 +108,7 @@ protected void afterTest() { } } - dispose(receiver, sender, receiveAndDeleteReceiver); + dispose(receiveAndDeleteReceiver); } /** @@ -818,10 +827,10 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde assertNotNull(sessionId, "'sessionId' should have been set."); this.receiver = getSessionReceiverBuilder(false, entityType, entityIndex, sharedConnection) .buildClient().acceptSession(sessionId); - this.receiveAndDeleteReceiver = getSessionReceiverBuilder(false, entityType, entityIndex, + this.receiveAndDeleteReceiverMono = Mono.fromCallable(() -> getSessionReceiverBuilder(false, entityType, entityIndex, sharedConnection) .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildClient().acceptSession(sessionId); + .buildClient().acceptSession(sessionId)); } else { this.receiver = getReceiverBuilder(false, entityType, entityIndex, sharedConnection) .buildClient(); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java index e0e7ef7f570a..3adf24e6822c 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSenderAsyncClientIntegrationTest.java @@ -506,23 +506,10 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde final boolean isSessionAware = false; final boolean sharedConnection = true; - /** - * Sets the sender and receiver. If session is enabled, then a single-named session receiver is created with - * shared connection as needed. - */ - private void setSenderAndReceiver(MessagingEntityType entityType, int entityIndex, boolean useCredentials, - boolean isSessionEnabled, boolean shareConnection) { - this.sender = getSenderBuilder(useCredentials, entityType, entityIndex, isSessionEnabled, shareConnection).buildAsyncClient(); - - if (isSessionEnabled) { - assertNotNull(sessionId, "'sessionId' should have been set."); - this.receiver = getSessionReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient().acceptSession(sessionId).block(); - } else { - this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, shareConnection) - .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) - .buildAsyncClient(); - } + this.sender = getSenderBuilder(useCredentials, entityType, entityIndex, isSessionAware, sharedConnection) + .buildAsyncClient(); + this.receiver = getReceiverBuilder(useCredentials, entityType, entityIndex, sharedConnection) + .receiveMode(ReceiveMode.RECEIVE_AND_DELETE) + .buildAsyncClient(); } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java index 2aabcbb80504..68b32b6ab095 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerIntegrationTest.java @@ -126,7 +126,7 @@ private void setSenderAndReceiver(MessagingEntityType entityType, int entityInde .buildAsyncClient(); ServiceBusSessionReceiverClientBuilder sessionBuilder = getSessionReceiverBuilder(false, entityType, entityIndex, false); - this.receiver = onBuild.apply(sessionBuilder).buildAsyncClient(); + this.receiver = onBuild.apply(sessionBuilder).buildAsyncClient().acceptSession(sessionId).block(); } private static void assertMessageEquals(String sessionId, String messageId, String contents, diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java index 23b38c8b32c0..1e7138939dca 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java @@ -146,7 +146,7 @@ void afterEach(TestInfo testInfo) { @Test void acceptSession() { // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, null, MAX_LOCK_RENEWAL); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false, null, null); final String lockToken = "a-lock-token"; final String linkName = "my-link-name"; final String sessionId = linkName; @@ -195,7 +195,7 @@ void acceptSession() { @Test void acceptNextSession() { // Arrange - final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, 1, MAX_LOCK_RENEWAL); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false, null, null); sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, tracerProvider, messageSerializer, receiverOptions); From 464d533a943d63b75d8e0931f48846aa10da25fd Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 13:58:48 -0700 Subject: [PATCH 08/23] Use ServiceBusClientBuilder's retryPolicy timeout --- .../servicebus/ServiceBusClientBuilder.java | 3 +- .../servicebus/ServiceBusReceiverClient.java | 8 --- .../ServiceBusSessionReceiverClient.java | 49 ++++--------------- .../ServiceBusSessionReceiverClientTest.java | 16 +++--- 4 files changed, 21 insertions(+), 55 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java index 0fe00eb3fd1b..1476bc6b59dd 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java @@ -766,7 +766,8 @@ public ServiceBusSessionReceiverAsyncClient buildAsyncClient() { * queueName()} or {@link #topicName(String) topicName()}, respectively. */ public ServiceBusSessionReceiverClient buildClient() { - return new ServiceBusSessionReceiverClient(buildAsyncClient(false)); + return new ServiceBusSessionReceiverClient(buildAsyncClient(false), + retryOptions.getTryTimeout()); } private ServiceBusSessionReceiverAsyncClient buildAsyncClient(boolean isAutoCompleteAllowed) { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java index c63ed6b527e4..38c550a96a15 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java @@ -53,14 +53,6 @@ public final class ServiceBusReceiverClient implements AutoCloseable { this.operationTimeout = Objects.requireNonNull(operationTimeout, "'operationTimeout' cannot be null."); } - // TODO: ServiceBusReceiverClient will remove the above constructor that accepts operationTimeout. - // This one is added because ServiceBusSessionReceiverClient need this constructor right now. - // this.operationTimeout won't be needed after timeout is moved to methods. - ServiceBusReceiverClient(ServiceBusReceiverAsyncClient asyncClient) { - this.asyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); - this.operationTimeout = Duration.ofDays(10000); // TODO: remove after moving timeout to methods. - } - /** * Gets the fully qualified Service Bus namespace that the connection is associated with. This is likely similar to * {@code {yournamespace}.servicebus.windows.net}. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index 38bd8e4ba196..80ee5d0d83c2 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -28,9 +28,11 @@ */ public class ServiceBusSessionReceiverClient implements AutoCloseable { private final ServiceBusSessionReceiverAsyncClient sessionAsyncClient; + private final Duration operationTimeout; - ServiceBusSessionReceiverClient(ServiceBusSessionReceiverAsyncClient asyncClient) { + ServiceBusSessionReceiverClient(ServiceBusSessionReceiverAsyncClient asyncClient, Duration operationTimeout) { this.sessionAsyncClient = Objects.requireNonNull(asyncClient, "'asyncClient' cannot be null."); + this.operationTimeout = operationTimeout; } /** @@ -41,25 +43,12 @@ public class ServiceBusSessionReceiverClient implements AutoCloseable { *

* @return A {@link ServiceBusReceiverClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - */ - public ServiceBusReceiverClient acceptNextSession() { - return sessionAsyncClient.acceptNextSession() - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) - .block(); - } - - /** - * Create a link for the next available session and use the link to create a {@link ServiceBusReceiverClient} - * to receive messages from that session. - * @param timeout the call is cancelled after this duration. - * @return A {@link ServiceBusReceiverClient} that is tied to the available session. * @throws IllegalStateException if the operation times out. */ - public ServiceBusReceiverClient acceptNextSession(Duration timeout) { - Objects.requireNonNull(timeout, "'timeout' can not be null."); + public ServiceBusReceiverClient acceptNextSession() { return sessionAsyncClient.acceptNextSession() - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) - .block(timeout); + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) + .block(operationTimeout); } /** @@ -74,32 +63,12 @@ public ServiceBusReceiverClient acceptNextSession(Duration timeout) { * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. - */ - public ServiceBusReceiverClient acceptSession(String sessionId) { - return sessionAsyncClient.acceptSession(sessionId) - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) - .block(); - } - - /** - * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverClient} - * to receive messages from the session. - * - * @param sessionId The session Id. - * @param timeout the call is cancelled after this duration. - * - * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. - * @throws NullPointerException if {@code sessionId} is null. - * @throws IllegalArgumentException if {@code sessionId} is empty. - * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. * @throws IllegalStateException if the operation times out. */ - public ServiceBusReceiverClient acceptSession(String sessionId, Duration timeout) { - Objects.requireNonNull(timeout, "'timeout' can not be null."); + public ServiceBusReceiverClient acceptSession(String sessionId) { return sessionAsyncClient.acceptSession(sessionId) - .map(asyncClient -> new ServiceBusReceiverClient(asyncClient)) - .block(timeout); + .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) + .block(operationTimeout); } @Override diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java index ce7f6a700ebe..189636025e76 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClientTest.java @@ -41,7 +41,8 @@ void afterEach(TestInfo testInfo) { @Test void acceptSession() { when(sessionAsyncClient.acceptSession(anyString())).thenReturn(Mono.just(asyncClient)); - ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient, + Duration.ofMillis(100)); assertNotNull(sessionClient.acceptSession("sessionId")); } @@ -50,16 +51,18 @@ void acceptSession() { void acceptSessionTimeout() { when(sessionAsyncClient.acceptSession(anyString())).thenReturn(Mono.just(asyncClient) .delayElement(Duration.ofMillis(100))); - ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient, + Duration.ofMillis(50)); assertThrows(IllegalStateException.class, - () -> sessionClient.acceptSession("sessionId", Duration.ofMillis(50))); + () -> sessionClient.acceptSession("sessionId")); } @Test void acceptNextSession() { when(sessionAsyncClient.acceptNextSession()).thenReturn(Mono.just(asyncClient)); - ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient, + Duration.ofMillis(100)); assertNotNull(sessionClient.acceptNextSession()); } @@ -68,9 +71,10 @@ void acceptNextSession() { void acceptNextSessionTimeout() { when(sessionAsyncClient.acceptNextSession()).thenReturn(Mono.just(asyncClient) .delayElement(Duration.ofMillis(100))); - ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient); + ServiceBusSessionReceiverClient sessionClient = new ServiceBusSessionReceiverClient(sessionAsyncClient, + Duration.ofMillis(50)); assertThrows(IllegalStateException.class, - () -> sessionClient.acceptNextSession(Duration.ofMillis(50))); + () -> sessionClient.acceptNextSession()); } } From 27580ca03571a5d37c1db182a9c7fdd71c9cebe3 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 15:56:38 -0700 Subject: [PATCH 09/23] Remove/hide receiver methods that have param sessionId --- .../ServiceBusReceiverAsyncClient.java | 207 ++++++++---------- .../servicebus/ServiceBusReceiverClient.java | 146 ++---------- ...BusReceiverAsyncClientIntegrationTest.java | 4 +- ...rviceBusReceiverClientIntegrationTest.java | 7 +- .../ServiceBusReceiverClientTest.java | 36 +-- 5 files changed, 121 insertions(+), 279 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index c2d2bad46eb0..4869960d8f2b 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -370,33 +370,13 @@ public Mono deadLetter(ServiceBusReceivedMessage message, DeadLetterOption } /** - * Gets the state of a session given its identifier. - * - * @param sessionId Identifier of session to get. + * Gets the state of the session if this receiver is a session receiver. * * @return The session state or an empty Mono if there is no state set for the session. * @throws IllegalStateException if the receiver is a non-session receiver. */ - public Mono getSessionState(String sessionId) { - if (isDisposed.get()) { - return monoError(logger, new IllegalStateException( - String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "getSessionState"))); - } else if (!receiverOptions.isSessionReceiver()) { - return monoError(logger, new IllegalStateException("Cannot get session state on a non-session receiver.")); - } - - if (sessionManager != null) { - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } - return sessionManager.getSessionState(sessionId); - } else { - return connectionProcessor - .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) - .flatMap(channel -> channel.getSessionState(sessionId, getLinkName(sessionId))); - } + public Mono getSessionState() { + return getSessionState(receiverOptions.getSessionId()); } /** @@ -422,16 +402,11 @@ public Mono peekMessage() { * @throws IllegalStateException if the receiver is disposed. * @see Message browsing */ - public Mono peekMessage(String sessionId) { + Mono peekMessage(String sessionId) { if (isDisposed.get()) { return monoError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peek"))); } - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(channel -> { @@ -472,16 +447,11 @@ public Mono peekMessageAt(long sequenceNumber) { * @return A peeked {@link ServiceBusReceivedMessage}. * @see Message browsing */ - public Mono peekMessageAt(long sequenceNumber, String sessionId) { + Mono peekMessageAt(long sequenceNumber, String sessionId) { if (isDisposed.get()) { return monoError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekAt"))); } - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(node -> node.peek(sequenceNumber, sessionId, getLinkName(sessionId))); @@ -510,16 +480,12 @@ public Flux peekMessages(int maxMessages) { * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. * @see Message browsing */ - public Flux peekMessages(int maxMessages, String sessionId) { + Flux peekMessages(int maxMessages, String sessionId) { if (isDisposed.get()) { return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekBatch"))); } - if (validateSessionId(sessionId)) { - return fluxError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } + return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> { @@ -577,16 +543,12 @@ public Flux peekMessagesAt(int maxMessages, long sequ * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. * @see Message browsing */ - public Flux peekMessagesAt(int maxMessages, long sequenceNumber, String sessionId) { + Flux peekMessagesAt(int maxMessages, long sequenceNumber, String sessionId) { if (isDisposed.get()) { return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "peekBatchAt"))); } - if (validateSessionId(sessionId)) { - return fluxError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } + return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> node.peek(sequenceNumber, sessionId, getLinkName(sessionId), maxMessages)); @@ -654,12 +616,7 @@ public Mono receiveDeferredMessage(long sequenceNumbe * * @return A deferred message with the matching {@code sequenceNumber}. */ - public Mono receiveDeferredMessage(long sequenceNumber, String sessionId) { - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } + Mono receiveDeferredMessage(long sequenceNumber, String sessionId) { return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMap(node -> node.receiveDeferredMessages(receiverOptions.getReceiveMode(), @@ -699,17 +656,12 @@ public Flux receiveDeferredMessages(Iterable se * * @return An {@link IterableStream} of deferred {@link ServiceBusReceivedMessage messages}. */ - public Flux receiveDeferredMessages(Iterable sequenceNumbers, + Flux receiveDeferredMessages(Iterable sequenceNumbers, String sessionId) { if (isDisposed.get()) { return fluxError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "receiveDeferredMessageBatch"))); } - if (validateSessionId(sessionId)) { - return fluxError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } return connectionProcessor .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) .flatMapMany(node -> node.receiveDeferredMessages(receiverOptions.getReceiveMode(), @@ -820,38 +772,18 @@ public Mono renewMessageLock(ServiceBusReceivedMessage message, Duration m } /** - * Renews the session lock. - * - * @param sessionId Identifier of session to get. + * Renews the session lock if this receiver is a session receiver. * * @return The next expiration time for the session lock. * @throws IllegalStateException if the receiver is a non-session receiver. */ - public Mono renewSessionLock(String sessionId) { - if (isDisposed.get()) { - return monoError(logger, new IllegalStateException( - String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "renewSessionLock"))); - } else if (!receiverOptions.isSessionReceiver()) { - return monoError(logger, new IllegalStateException("Cannot renew session lock on a non-session receiver.")); - } - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } - final String linkName = sessionManager != null - ? sessionManager.getLinkName(sessionId) - : null; - - return connectionProcessor - .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) - .flatMap(channel -> channel.renewSessionLock(sessionId, linkName)); + public Mono renewSessionLock() { + return renewSessionLock(receiverOptions.getSessionId()); } /** - * Starts the auto lock renewal for a session id. + * Starts the auto lock renewal for the session this receiver works for. * - * @param sessionId Id for the session to renew. * @param maxLockRenewalDuration Maximum duration to keep renewing the session lock. * * @return A lock renewal operation for the message. @@ -859,56 +791,26 @@ public Mono renewSessionLock(String sessionId) { * @throws IllegalArgumentException if {@code sessionId} is an empty string. * @throws IllegalStateException if the receiver is a non-session receiver or the receiver is disposed. */ - public Mono renewSessionLock(String sessionId, Duration maxLockRenewalDuration) { - if (isDisposed.get()) { - return monoError(logger, new IllegalStateException( - String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "getAutoRenewSessionLock"))); - } else if (!receiverOptions.isSessionReceiver()) { - return monoError(logger, new IllegalStateException( - "Cannot renew session lock on a non-session receiver.")); - } else if (maxLockRenewalDuration == null) { - return monoError(logger, new NullPointerException("'maxLockRenewalDuration' cannot be null.")); - } else if (maxLockRenewalDuration.isNegative()) { - return monoError(logger, new IllegalArgumentException( - "'maxLockRenewalDuration' cannot be negative.")); - } else if (Objects.isNull(sessionId)) { - return monoError(logger, new NullPointerException("'sessionId' cannot be null.")); - } else if (sessionId.isEmpty()) { - return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty.")); - } - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } - final LockRenewalOperation operation = new LockRenewalOperation(sessionId, maxLockRenewalDuration, true, - this::renewSessionLock); - - renewalContainer.addOrUpdate(sessionId, OffsetDateTime.now().plus(maxLockRenewalDuration), operation); - return operation.getCompletionOperation(); + public Mono renewSessionLock(Duration maxLockRenewalDuration) { + return this.renewSessionLock(receiverOptions.getSessionId(), maxLockRenewalDuration); } /** - * Sets the state of a session given its identifier. + * Sets the state of the session this receiver works for. * - * @param sessionId Identifier of session to get. * @param sessionState State to set on the session. * * @return A Mono that completes when the session is set * @throws IllegalStateException if the receiver is a non-session receiver. */ - public Mono setSessionState(String sessionId, byte[] sessionState) { + public Mono setSessionState(byte[] sessionState) { if (isDisposed.get()) { return monoError(logger, new IllegalStateException( String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "setSessionState"))); } else if (!receiverOptions.isSessionReceiver()) { return monoError(logger, new IllegalStateException("Cannot set session state on a non-session receiver.")); } - if (validateSessionId(sessionId)) { - return monoError(logger, new IllegalArgumentException(String.format( - "This receiver client is tied to session %s. It can't be used for another session(%s).", - receiverOptions.getSessionId(), sessionId))); - } + String sessionId = receiverOptions.getSessionId(); final String linkName = sessionManager != null ? sessionManager.getLinkName(sessionId) : null; @@ -1175,8 +1077,71 @@ private String getLinkName(String sessionId) { } } - private boolean validateSessionId(String sessionId) { - String optionSessionId = receiverOptions.getSessionId(); - return optionSessionId != null && !optionSessionId.equals(sessionId); + Mono renewSessionLock(String sessionId) { + if (isDisposed.get()) { + return monoError(logger, new IllegalStateException( + String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "renewSessionLock"))); + } else if (!receiverOptions.isSessionReceiver()) { + return monoError(logger, new IllegalStateException("Cannot renew session lock on a non-session receiver.")); + } + final String linkName = sessionManager != null + ? sessionManager.getLinkName(sessionId) + : null; + + return connectionProcessor + .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) + .flatMap(channel -> channel.renewSessionLock(sessionId, linkName)); + } + + Mono renewSessionLock(String sessionId, Duration maxLockRenewalDuration) { + if (isDisposed.get()) { + return monoError(logger, new IllegalStateException( + String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "getAutoRenewSessionLock"))); + } else if (!receiverOptions.isSessionReceiver()) { + return monoError(logger, new IllegalStateException( + "Cannot renew session lock on a non-session receiver.")); + } else if (maxLockRenewalDuration == null) { + return monoError(logger, new NullPointerException("'maxLockRenewalDuration' cannot be null.")); + } else if (maxLockRenewalDuration.isNegative()) { + return monoError(logger, new IllegalArgumentException( + "'maxLockRenewalDuration' cannot be negative.")); + } + final LockRenewalOperation operation = new LockRenewalOperation(sessionId, + maxLockRenewalDuration, true, this::renewSessionLock); + + renewalContainer.addOrUpdate(sessionId, OffsetDateTime.now().plus(maxLockRenewalDuration), operation); + return operation.getCompletionOperation(); + } + + Mono setSessionState(String sessionId, byte[] sessionState) { + if (isDisposed.get()) { + return monoError(logger, new IllegalStateException( + String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "setSessionState"))); + } else if (!receiverOptions.isSessionReceiver()) { + return monoError(logger, new IllegalStateException("Cannot set session state on a non-session receiver.")); + } + final String linkName = sessionManager != null + ? sessionManager.getLinkName(sessionId) + : null; + + return connectionProcessor + .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) + .flatMap(channel -> channel.setSessionState(sessionId, sessionState, linkName)); + } + + Mono getSessionState(String sessionId) { + if (isDisposed.get()) { + return monoError(logger, new IllegalStateException( + String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "getSessionState"))); + } else if (!receiverOptions.isSessionReceiver()) { + return monoError(logger, new IllegalStateException("Cannot get session state on a non-session receiver.")); + } + if (sessionManager != null) { + return sessionManager.getSessionState(sessionId); + } else { + return connectionProcessor + .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) + .flatMap(channel -> channel.getSessionState(sessionId, getLinkName(sessionId))); + } } } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java index 38c550a96a15..6ef4cd224f51 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java @@ -210,15 +210,13 @@ public void deadLetter(ServiceBusReceivedMessage message, DeadLetterOptions opti } /** - * Gets the state of a session given its identifier. - * - * @param sessionId Identifier of session to get. + * Gets the state of the session if this receiver is a session receiver. * * @return The session state or null if there is no state set for the session. * @throws IllegalStateException if the receiver is a non-session receiver. */ - public byte[] getSessionState(String sessionId) { - return asyncClient.getSessionState(sessionId).block(operationTimeout); + public byte[] getSessionState() { + return asyncClient.getSessionState().block(operationTimeout); } /** @@ -233,20 +231,6 @@ public ServiceBusReceivedMessage peekMessage() { return asyncClient.peekMessage().block(operationTimeout); } - /** - * Reads the next active message without changing the state of the receiver or the message source. The first call to - * {@code peek()} fetches the first active message for this receiver. Each subsequent call fetches the subsequent - * message in the entity. - * - * @param sessionId Session id of the message to peek from. {@code null} if there is no session. - * - * @return A peeked {@link ServiceBusReceivedMessage}. - * @see Message browsing - */ - public ServiceBusReceivedMessage peekMessage(String sessionId) { - return asyncClient.peekMessage(sessionId).block(operationTimeout); - } - /** * Starting from the given sequence number, reads next the active message without changing the state of the receiver * or the message source. @@ -260,20 +244,6 @@ public ServiceBusReceivedMessage peekMessageAt(long sequenceNumber) { return asyncClient.peekMessageAt(sequenceNumber).block(operationTimeout); } - /** - * Starting from the given sequence number, reads next the active message without changing the state of the receiver - * or the message source. - * - * @param sequenceNumber The sequence number from where to read the message. - * @param sessionId Session id of the message to peek from. {@code null} if there is no session. - * - * @return A peeked {@link ServiceBusReceivedMessage}. - * @see Message browsing - */ - public ServiceBusReceivedMessage peekMessageAt(long sequenceNumber, String sessionId) { - return asyncClient.peekMessageAt(sequenceNumber, sessionId).block(operationTimeout); - } - /** * Reads the next batch of active messages without changing the state of the receiver or the message source. * @@ -298,31 +268,6 @@ public IterableStream peekMessages(int maxMessages) { return new IterableStream<>(messages); } - /** - * Reads the next batch of active messages without changing the state of the receiver or the message source. - * - * @param maxMessages The number of messages. - * @param sessionId Session id of the messages to peek from. {@code null} if there is no session. - * - * @return An {@link IterableStream} of {@link ServiceBusReceivedMessage messages} that are peeked. - * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. - * @see Message browsing - */ - public IterableStream peekMessages(int maxMessages, String sessionId) { - if (maxMessages <= 0) { - throw logger.logExceptionAsError(new IllegalArgumentException( - "'maxMessages' cannot be less than or equal to 0. maxMessages: " + maxMessages)); - } - - final Flux messages = asyncClient.peekMessages(maxMessages, sessionId) - .timeout(operationTimeout); - - // Subscribe so we can kick off this operation. - messages.subscribe(); - - return new IterableStream<>(messages); - } - /** * Starting from the given sequence number, reads the next batch of active messages without changing the state of * the receiver or the message source. @@ -349,34 +294,6 @@ public IterableStream peekMessagesAt(int maxMessages, return new IterableStream<>(messages); } - /** - * Starting from the given sequence number, reads the next batch of active messages without changing the state of - * the receiver or the message source. - * - * @param maxMessages The number of messages. - * @param sequenceNumber The sequence number from where to start reading messages. - * @param sessionId Session id of the messages to peek from. {@code null} if there is no session. - * - * @return An {@link IterableStream} of {@link ServiceBusReceivedMessage} peeked. - * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. - * @see Message browsing - */ - public IterableStream peekMessagesAt(int maxMessages, long sequenceNumber, - String sessionId) { - if (maxMessages <= 0) { - throw logger.logExceptionAsError(new IllegalArgumentException( - "'maxMessages' cannot be less than or equal to 0. maxMessages: " + maxMessages)); - } - - final Flux messages = asyncClient.peekMessagesAt(maxMessages, sequenceNumber, - sessionId).timeout(operationTimeout); - - // Subscribe so we can kick off this operation. - messages.subscribe(); - - return new IterableStream<>(messages); - } - /** * Receives an iterable stream of {@link ServiceBusReceivedMessage messages} from the Service Bus entity. The * receive operation will wait for a default 1 minute for receiving a message before it times out. You can it @@ -434,20 +351,6 @@ public ServiceBusReceivedMessage receiveDeferredMessage(long sequenceNumber) { return asyncClient.receiveDeferredMessage(sequenceNumber).block(operationTimeout); } - /** - * Receives a deferred {@link ServiceBusReceivedMessage message}. Deferred messages can only be received by using - * sequence number. - * - * @param sequenceNumber The {@link ServiceBusReceivedMessage#getSequenceNumber() sequence number} of the - * message. - * @param sessionId Session id of the deferred message. - * - * @return A deferred message with the matching {@code sequenceNumber}. - */ - public ServiceBusReceivedMessage receiveDeferredMessage(long sequenceNumber, String sessionId) { - return asyncClient.receiveDeferredMessage(sequenceNumber, sessionId).block(operationTimeout); - } - /** * Receives a batch of deferred {@link ServiceBusReceivedMessage messages}. Deferred messages can only be received * by using sequence number. @@ -466,26 +369,6 @@ public IterableStream receiveDeferredMessageBatch(Ite return new IterableStream<>(messages); } - /** - * Receives a batch of deferred {@link ServiceBusReceivedMessage messages}. Deferred messages can only be received - * by using sequence number. - * - * @param sequenceNumbers The sequence numbers of the deferred messages. - * @param sessionId Session id of the deferred messages. {@code null} if there is no session. - * - * @return An {@link IterableStream} of deferred {@link ServiceBusReceivedMessage messages}. - */ - public IterableStream receiveDeferredMessageBatch(Iterable sequenceNumbers, - String sessionId) { - final Flux messages = asyncClient.receiveDeferredMessages(sequenceNumbers, - sessionId).timeout(operationTimeout); - - // Subscribe so we can kick off this operation. - messages.subscribe(); - - return new IterableStream<>(messages); - } - /** * Renews the lock on the specified message. The lock will be renewed based on the setting specified on the entity. * When a message is received in {@link ReceiveMode#PEEK_LOCK} mode, the message is locked on the server for this @@ -529,23 +412,20 @@ public void renewMessageLock(ServiceBusReceivedMessage message, Duration maxLock } /** - * Sets the state of a session given its identifier. - * - * @param sessionId Identifier of session to get. + * Sets the state of the session if this receiver is a session receiver. * * @return The next expiration time for the session lock. * @throws NullPointerException if {@code sessionId} is null. * @throws IllegalArgumentException if {@code sessionId} is an empty string. * @throws IllegalStateException if the receiver is a non-session receiver. */ - public OffsetDateTime renewSessionLock(String sessionId) { - return asyncClient.renewSessionLock(sessionId).block(operationTimeout); + public OffsetDateTime renewSessionLock() { + return asyncClient.renewSessionLock().block(operationTimeout); } /** - * Starts the auto lock renewal for a session id. + * Starts the auto lock renewal for the session that this receiver works for. * - * @param sessionId Id for the session to renew. * @param maxLockRenewalDuration Maximum duration to keep renewing the session. * @param onError A function to call when an error occurs during lock renewal. * @@ -553,27 +433,27 @@ public OffsetDateTime renewSessionLock(String sessionId) { * @throws IllegalArgumentException if {@code sessionId} is an empty string. * @throws IllegalStateException if the receiver is a non-session receiver or the receiver is disposed. */ - public void renewSessionLock(String sessionId, Duration maxLockRenewalDuration, Consumer onError) { + public void renewSessionLock(Duration maxLockRenewalDuration, Consumer onError) { + String sessionId = asyncClient.getReceiverOptions().getSessionId(); final Consumer throwableConsumer = onError != null ? onError : error -> logger.warning("Exception occurred while renewing session: '{}'.", sessionId, error); - asyncClient.renewSessionLock(sessionId, maxLockRenewalDuration).subscribe( + asyncClient.renewSessionLock(maxLockRenewalDuration).subscribe( v -> logger.verbose("Completed renewing session: '{}'", sessionId), throwableConsumer, () -> logger.verbose("Auto session lock renewal operation completed: {}", sessionId)); } /** - * Sets the state of a session given its identifier. + * Sets the state of the session if this receiver is a session receiver. * - * @param sessionId Identifier of session to get. * @param sessionState State to set on the session. * * @throws IllegalStateException if the receiver is a non-session receiver. */ - public void setSessionState(String sessionId, byte[] sessionState) { - asyncClient.setSessionState(sessionId, sessionState).block(operationTimeout); + public void setSessionState(byte[] sessionState) { + asyncClient.setSessionState(sessionState).block(operationTimeout); } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java index 198dff0c7c6f..756e008f63aa 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClientIntegrationTest.java @@ -942,12 +942,12 @@ void setAndGetSessionState(MessagingEntityType entityType) { logger.info("SessionId: {}. LockToken: {}. LockedUntil: {}. Message received.", m.getSessionId(), m.getMessage().getLockToken(), m.getMessage().getLockedUntil()); receivedMessage.set(m.getMessage()); - return receiver.setSessionState(sessionId, sessionState); + return receiver.setSessionState(sessionState); })) .expectComplete() .verify(); - StepVerifier.create(receiver.getSessionState(sessionId)) + StepVerifier.create(receiver.getSessionState()) .assertNext(state -> { logger.info("State received: {}", new String(state, UTF_8)); assertArrayEquals(sessionState, state); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java index d9f2a66753c0..daac1f6d6c42 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientIntegrationTest.java @@ -741,11 +741,8 @@ void receiveAndDefer(MessagingEntityType entityType, boolean isSessionEnabled) { // cleanup final ServiceBusReceivedMessage deferred; - if (isSessionEnabled) { - deferred = receiver.receiveDeferredMessage(receivedMessage.getSequenceNumber(), sessionId); - } else { - deferred = receiver.receiveDeferredMessage(receivedMessage.getSequenceNumber()); - } + deferred = receiver.receiveDeferredMessage(receivedMessage.getSequenceNumber()); + receiver.complete(deferred); messagesPending.addAndGet(-maxMessages); } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java index b026ff44c458..26001ea5c17d 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java @@ -221,13 +221,13 @@ void autoRenewSessionLock() { fail("On error should not have been invoked."); return null; }).when(onErrorConsumer).accept(any()); - when(asyncClient.renewSessionLock(LOCK_TOKEN, maxDuration)).thenReturn(publisher.mono()); + when(asyncClient.renewSessionLock(maxDuration)).thenReturn(publisher.mono()); // Act - client.renewSessionLock(LOCK_TOKEN, maxDuration, onErrorConsumer); + client.renewSessionLock(maxDuration, onErrorConsumer); // Assert - verify(asyncClient).renewSessionLock(LOCK_TOKEN, maxDuration); + verify(asyncClient).renewSessionLock(maxDuration); } /** @@ -240,15 +240,15 @@ void autoRenewSessionLockFails() { final TestPublisher publisher = TestPublisher.create(); final Throwable testError = new IllegalAccessException("Some exception"); - when(asyncClient.renewSessionLock(LOCK_TOKEN, maxDuration)).thenReturn(publisher.mono()); + when(asyncClient.renewSessionLock(maxDuration)).thenReturn(publisher.mono()); - client.renewSessionLock(LOCK_TOKEN, maxDuration, onErrorConsumer); + client.renewSessionLock(maxDuration, onErrorConsumer); // Act publisher.error(testError); // Assert - verify(asyncClient).renewSessionLock(LOCK_TOKEN, maxDuration); + verify(asyncClient).renewSessionLock(maxDuration); verify(onErrorConsumer).accept(testError); } @@ -262,15 +262,15 @@ void autoRenewSessionLockFailsNull() { final TestPublisher publisher = TestPublisher.create(); final Throwable testError = new IllegalAccessException("Some exception"); - when(asyncClient.renewSessionLock(LOCK_TOKEN, maxDuration)).thenReturn(publisher.mono()); + when(asyncClient.renewSessionLock(maxDuration)).thenReturn(publisher.mono()); - client.renewSessionLock(LOCK_TOKEN, maxDuration, null); + client.renewSessionLock(maxDuration, null); // Act publisher.error(testError); // Assert - verify(asyncClient).renewSessionLock(LOCK_TOKEN, maxDuration); + verify(asyncClient).renewSessionLock(maxDuration); verify(onErrorConsumer, never()).accept(testError); } @@ -394,10 +394,10 @@ void getSessionState() { // Arrange final String sessionId = "a-session-id"; final byte[] contents = new byte[]{10, 111, 23}; - when(asyncClient.getSessionState(sessionId)).thenReturn(Mono.just(contents)); + when(asyncClient.getSessionState()).thenReturn(Mono.just(contents)); // Act - final byte[] actual = client.getSessionState(sessionId); + final byte[] actual = client.getSessionState(); // Assert assertEquals(contents, actual); @@ -407,10 +407,10 @@ void getSessionState() { void getSessionStateNull() { // Arrange final String sessionId = "a-session-id"; - when(asyncClient.getSessionState(sessionId)).thenReturn(Mono.empty()); + when(asyncClient.getSessionState()).thenReturn(Mono.empty()); // Act - final byte[] actual = client.getSessionState(sessionId); + final byte[] actual = client.getSessionState(); // Assert assertNull(actual); @@ -809,10 +809,10 @@ void renewSessionLock() { // Arrange final String sessionId = "a-session-id"; final OffsetDateTime response = Instant.ofEpochSecond(1585259339).atOffset(ZoneOffset.UTC); - when(asyncClient.renewSessionLock(sessionId)).thenReturn(Mono.just(response)); + when(asyncClient.renewSessionLock()).thenReturn(Mono.just(response)); // Act - final OffsetDateTime actual = client.renewSessionLock(sessionId); + final OffsetDateTime actual = client.renewSessionLock(); // Assert assertEquals(response, actual); @@ -823,12 +823,12 @@ void setSessionState() { // Arrange final String sessionId = "a-session-id"; final byte[] contents = new byte[]{10, 111, 23}; - when(asyncClient.setSessionState(sessionId, contents)).thenReturn(Mono.empty()); + when(asyncClient.setSessionState(contents)).thenReturn(Mono.empty()); // Act - client.setSessionState(sessionId, contents); + client.setSessionState(contents); // Assert - verify(asyncClient).setSessionState(sessionId, contents); + verify(asyncClient).setSessionState(contents); } } From 30266e1dbe9af39fbb3d55a3f9478dbd1e5911c9 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 22:31:33 -0700 Subject: [PATCH 10/23] Small fixes --- .../ServiceBusReceiverAsyncClient.java | 4 +++ .../ServiceBusSessionReceiverAsyncClient.java | 22 +++++++++++----- .../ServiceBusSessionReceiverClient.java | 25 +++++++++++++------ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index 4869960d8f2b..62ce3495cc53 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -1105,6 +1105,10 @@ Mono renewSessionLock(String sessionId, Duration maxLockRenewalDuration) { } else if (maxLockRenewalDuration.isNegative()) { return monoError(logger, new IllegalArgumentException( "'maxLockRenewalDuration' cannot be negative.")); + } else if (Objects.isNull(sessionId)) { + return monoError(logger, new NullPointerException("'sessionId' cannot be null.")); + } else if (sessionId.isEmpty()) { + return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty.")); } final LockRenewalOperation operation = new LockRenewalOperation(sessionId, maxLockRenewalDuration, true, this::renewSessionLock); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 08e8446b9f1d..ed48f59b5338 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -5,6 +5,8 @@ import com.azure.core.amqp.implementation.MessageSerializer; import com.azure.core.amqp.implementation.TracerProvider; +import com.azure.core.annotation.ReturnType; +import com.azure.core.annotation.ServiceMethod; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.messaging.servicebus.implementation.MessagingEntityType; @@ -27,7 +29,7 @@ *

* *
  • - * Use {@link #acceptNextSession()} to acquire the lock of the next availabe session + * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} @@ -67,13 +69,15 @@ public final class ServiceBusSessionReceiverAsyncClient implements AutoCloseable /** * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverAsyncClient} - * to receive messages from the session. - *

    Accept next available session

    + * to receive messages from the session. It will wait until a session is available if no one is available + * immediately. + *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} *

    * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. */ + @ServiceMethod(returns = ReturnType.SINGLE) public Mono acceptNextSession() { return unNamedSessionManager.getActiveLink().flatMap(receiveLink -> receiveLink.getSessionId() .map(sessionId -> { @@ -91,7 +95,8 @@ public Mono acceptNextSession() { /** * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverAsyncClient} - * to receive messages from the session. + * to receive messages from the session. If the session is already locked by another client, an + * {@link com.azure.core.amqp.exception.AmqpException} is thrown immediately. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} *

    @@ -100,10 +105,15 @@ public Mono acceptNextSession() { * @throws NullPointerException if {@code sessionId} is null. * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. + * @throws com.azure.core.amqp.exception.AmqpException with errorCondition + * {@link com.azure.core.amqp.exception.AmqpErrorCondition#SESSION_CANNOT_BE_LOCKED} + * if the session is locked by another client. */ + @ServiceMethod(returns = ReturnType.SINGLE) public Mono acceptSession(String sessionId) { - sessionId = Objects.requireNonNull(sessionId, "'sessionId' cannot be null"); + if (sessionId == null) { + return monoError(logger, new NullPointerException("'sessionId' cannot be null")); + } if (CoreUtils.isNullOrEmpty(sessionId)) { return monoError(logger, new IllegalArgumentException("'sessionId' cannot be empty")); } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index 80ee5d0d83c2..f77bbb37ebd0 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -3,6 +3,9 @@ package com.azure.messaging.servicebus; +import com.azure.core.annotation.ReturnType; +import com.azure.core.annotation.ServiceMethod; + import java.time.Duration; import java.util.Objects; @@ -17,7 +20,7 @@ *

    *
  • *
  • - * Use {@link #acceptNextSession()} to acquire the lock of the next availabe session + * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} @@ -26,7 +29,7 @@ * * */ -public class ServiceBusSessionReceiverClient implements AutoCloseable { +public final class ServiceBusSessionReceiverClient implements AutoCloseable { private final ServiceBusSessionReceiverAsyncClient sessionAsyncClient; private final Duration operationTimeout; @@ -37,14 +40,18 @@ public class ServiceBusSessionReceiverClient implements AutoCloseable { /** * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverClient} - * to receive messages from the session. It will wait if no session is available. - *

    Accept next available session

    + * to receive messages from the session. It will wait until a session is available if no one is available + * immediately. + *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} *

    * @return A {@link ServiceBusReceiverClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - * @throws IllegalStateException if the operation times out. + * @throws IllegalStateException if the operation times out. The timeout duration is the tryTimeout + * of when you build this client with the + * {@link ServiceBusClientBuilder#retryOptions(com.azure.core.amqp.AmqpRetryOptions)}. */ + @ServiceMethod(returns = ReturnType.SINGLE) public ServiceBusReceiverClient acceptNextSession() { return sessionAsyncClient.acceptNextSession() .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) @@ -53,7 +60,8 @@ public ServiceBusReceiverClient acceptNextSession() { /** * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverClient} - * to receive messages from the session. + * to receive messages from the session. If the session is already locked by another client, an + * {@link com.azure.core.amqp.exception.AmqpException} is thrown immediately. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} *

    @@ -63,8 +71,11 @@ public ServiceBusReceiverClient acceptNextSession() { * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. - * @throws IllegalStateException if the operation times out. + * @throws IllegalStateException if the operation times out. The timeout duration is the tryTimeout + * of when you build this client with the + * {@link ServiceBusClientBuilder#retryOptions(com.azure.core.amqp.AmqpRetryOptions)}. */ + @ServiceMethod(returns = ReturnType.SINGLE) public ServiceBusReceiverClient acceptSession(String sessionId) { return sessionAsyncClient.acceptSession(sessionId) .map(asyncClient -> new ServiceBusReceiverClient(asyncClient, operationTimeout)) From 4eb8093ac1d21eb7f87130becfdffa3e472ff03f Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 23:02:09 -0700 Subject: [PATCH 11/23] Remove INVALID_LOCK_TOKEN_STRING --- .../com/azure/messaging/servicebus/implementation/Messages.java | 1 - .../src/main/resources/azure-messaging-servicebus.properties | 1 - 2 files changed, 2 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/Messages.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/Messages.java index 49671a2fdb02..5c7ca158388f 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/Messages.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/Messages.java @@ -20,7 +20,6 @@ public class Messages { public static final String CLASS_NOT_A_SUPPORTED_TYPE = getMessage("CLASS_NOT_A_SUPPORTED_TYPE"); public static final String INVALID_OPERATION_DISPOSED_RECEIVER = getMessage("INVALID_OPERATION_DISPOSED_RECEIVER"); - public static final String INVALID_LOCK_TOKEN_STRING = getMessage("INVALID_LOCK_TOKEN_STRING"); public static final String MESSAGE_NOT_OF_TYPE = getMessage("MESSAGE_NOT_OF_TYPE"); public static final String REQUEST_VALUE_NOT_VALID = getMessage("REQUEST_VALUE_NOT_VALID"); diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties b/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties index 291f6ee5831a..0600b337ca0e 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/resources/azure-messaging-servicebus.properties @@ -5,4 +5,3 @@ MESSAGE_NOT_OF_TYPE=Message body type is not of type Data, but type: %s. Not set REQUEST_VALUE_NOT_VALID=Back pressure request value not valid. It must be between {} and {}. INVALID_OPERATION_DISPOSED_RECEIVER=Cannot perform operation '%s' on a disposed receiver. INVALID_LOCK_TOKEN_STRING=Invalid lock token '%s'. -SESSION_BOUND_RECEIVER=This receiver client accepts session %s. It can't be used for another session %s. From 60d0b5282beb89005ff11ad4796c8cd73a4a36f7 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 23:35:47 -0700 Subject: [PATCH 12/23] Fix unnamesession test --- .../messaging/servicebus/ServiceBusSessionManagerTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java index 0d73c526c2ac..833b78e2b561 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionManagerTest.java @@ -347,6 +347,7 @@ void multipleSessions() { void multipleReceiveUnnamedSession() { // Arrange final int expectedLinksCreated = 2; + final Callable onRenewal = () -> OffsetDateTime.now().plus(Duration.ofSeconds(5)); final ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, Duration.ZERO, false, null, 1); @@ -358,6 +359,7 @@ void multipleReceiveUnnamedSession() { when(amqpReceiveLink.getLinkName()).thenReturn(linkName); when(amqpReceiveLink.getSessionId()).thenReturn(Mono.just(sessionId)); + when(amqpReceiveLink.getSessionLockedUntil()).thenReturn(Mono.fromCallable(onRenewal)); // Session 2's final ServiceBusReceiveLink amqpReceiveLink2 = mock(ServiceBusReceiveLink.class); @@ -372,6 +374,7 @@ void multipleReceiveUnnamedSession() { when(amqpReceiveLink2.getEndpointStates()).thenReturn(endpointProcessor); when(amqpReceiveLink2.getLinkName()).thenReturn(linkName2); when(amqpReceiveLink2.getSessionId()).thenReturn(Mono.just(sessionId2)); + when(amqpReceiveLink2.getSessionLockedUntil()).thenReturn(Mono.fromCallable(onRenewal)); final AtomicInteger count = new AtomicInteger(); when(connection.createReceiveLink(anyString(), eq(ENTITY_PATH), any(ReceiveMode.class), isNull(), From 600f60ffa7ee0d7acc3c259a9dac78c0e4067f95 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Wed, 28 Oct 2020 23:38:13 -0700 Subject: [PATCH 13/23] cannot acquire lock wording adjustment --- .../servicebus/ServiceBusSessionReceiverAsyncClient.java | 6 ++---- .../servicebus/ServiceBusSessionReceiverClient.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index ed48f59b5338..75ccdc88b0f6 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -96,7 +96,7 @@ public Mono acceptNextSession() { /** * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverAsyncClient} * to receive messages from the session. If the session is already locked by another client, an - * {@link com.azure.core.amqp.exception.AmqpException} is thrown immediately. + * {@link com.azure.core.amqp.exception.AmqpException} is thrown. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} *

    @@ -105,9 +105,7 @@ public Mono acceptNextSession() { * @throws NullPointerException if {@code sessionId} is null. * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - * @throws com.azure.core.amqp.exception.AmqpException with errorCondition - * {@link com.azure.core.amqp.exception.AmqpErrorCondition#SESSION_CANNOT_BE_LOCKED} - * if the session is locked by another client. + * @throws com.azure.core.amqp.exception.AmqpException if the lock cannot be acquired. */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono acceptSession(String sessionId) { diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index f77bbb37ebd0..35178768faad 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -70,7 +70,7 @@ public ServiceBusReceiverClient acceptNextSession() { * @throws NullPointerException if {@code sessionId} is null. * @throws IllegalArgumentException if {@code sessionId} is empty. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. - * @throws com.azure.core.amqp.exception.AmqpException if the session has been locked by another session receiver. + * @throws com.azure.core.amqp.exception.AmqpException if the lock cannot be acquired. * @throws IllegalStateException if the operation times out. The timeout duration is the tryTimeout * of when you build this client with the * {@link ServiceBusClientBuilder#retryOptions(com.azure.core.amqp.AmqpRetryOptions)}. From 22308ddad7055229082aae6e30588856f3d97339 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 00:11:53 -0700 Subject: [PATCH 14/23] Add maxLockRenewal to fix test --- .../ServiceBusSessionReceiverAsyncClientTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java index 1e7138939dca..069cb8901a56 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClientTest.java @@ -146,7 +146,7 @@ void afterEach(TestInfo testInfo) { @Test void acceptSession() { // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false, null, null); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, Duration.ZERO, false, null, null); final String lockToken = "a-lock-token"; final String linkName = "my-link-name"; final String sessionId = linkName; @@ -189,13 +189,13 @@ void acceptSession() { .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) .assertNext(context -> assertMessageEquals(sessionId, receivedMessage, context)) - .verifyComplete(); + .thenCancel().verify(); } @Test void acceptNextSession() { // Arrange - ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false, null, null); + ReceiverOptions receiverOptions = new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, Duration.ZERO, false, null, null); sessionManager = new ServiceBusSessionManager(ENTITY_PATH, ENTITY_TYPE, connectionProcessor, tracerProvider, messageSerializer, receiverOptions); From 925d7b8b19688527c2e8699c20a1cd56b8770bb3 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 00:23:28 -0700 Subject: [PATCH 15/23] Add @ServiceClient annotation --- .../servicebus/ServiceBusSessionReceiverAsyncClient.java | 2 ++ .../messaging/servicebus/ServiceBusSessionReceiverClient.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 75ccdc88b0f6..30020d48f108 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -6,6 +6,7 @@ import com.azure.core.amqp.implementation.MessageSerializer; import com.azure.core.amqp.implementation.TracerProvider; 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; @@ -38,6 +39,7 @@ * * */ +@ServiceClient(builder = ServiceBusClientBuilder.class, isAsync = true) public final class ServiceBusSessionReceiverAsyncClient implements AutoCloseable { private final String fullyQualifiedNamespace; private final String entityPath; diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index 35178768faad..3a8b84a2cc38 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -4,6 +4,7 @@ package com.azure.messaging.servicebus; import com.azure.core.annotation.ReturnType; +import com.azure.core.annotation.ServiceClient; import com.azure.core.annotation.ServiceMethod; import java.time.Duration; @@ -29,6 +30,7 @@ * * */ +@ServiceClient(builder = ServiceBusClientBuilder.class) public final class ServiceBusSessionReceiverClient implements AutoCloseable { private final ServiceBusSessionReceiverAsyncClient sessionAsyncClient; private final Duration operationTimeout; From b08e95df5b54b6a89c1ee1b54cb7f39ca05198f0 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 11:36:01 -0700 Subject: [PATCH 16/23] Adjust readme sample code lines --- .../azure-messaging-servicebus/README.md | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/README.md b/sdk/servicebus/azure-messaging-servicebus/README.md index 0d360fbd8c0f..cbbdb6ac30dc 100644 --- a/sdk/servicebus/azure-messaging-servicebus/README.md +++ b/sdk/servicebus/azure-messaging-servicebus/README.md @@ -57,7 +57,7 @@ Both the asynchronous and synchronous Service Bus sender and receiver clients ar `ServiceBusClientBuilder`. The snippets below create a synchronous Service Bus sender and an asynchronous receiver, respectively. - + ```java ServiceBusSenderClient sender = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") @@ -66,7 +66,7 @@ ServiceBusSenderClient sender = new ServiceBusClientBuilder() .buildClient(); ``` - + ```java ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") @@ -102,7 +102,7 @@ refer to [the associated documentation][aad_authorization]. Use the returned token credential to authenticate the client: - + ```java TokenCredential credential = new DefaultAzureCredentialBuilder() .build(); @@ -152,7 +152,7 @@ a topic. The snippet below creates a synchronous [`ServiceBusSenderClient`][ServiceBusSenderClient] to publish a message to a queue. - + ```java ServiceBusSenderClient sender = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") @@ -187,7 +187,7 @@ queue or topic/subscriber. The snippet below creates a [`ServiceBusReceiverClient`][ServiceBusReceiverClient] to receive messages from a topic subscription. - + ```java ServiceBusReceiverClient receiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") @@ -214,7 +214,7 @@ receiver.close(); The asynchronous [`ServiceBusReceiverAsyncClient`][ServiceBusReceiverAsyncClient] continuously fetches messages until the `subscription` is disposed. - + ```java ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") @@ -247,7 +247,7 @@ When a message is received, it can be settled using any of the `complete()`, `ab overloads. The sample below completes a received message from synchronous [`ServiceBusReceiverClient`][ServiceBusReceiverClient]. - + ```java // This fetches a batch of 10 messages or until the default operation timeout has elapsed. receiver.receiveMessages(10).forEach(context -> { @@ -287,7 +287,7 @@ Create a [`ServiceBusSenderClient`][ServiceBusSenderClient] for a session enable `ServiceBusMessage.setSessionId(String)` on a `ServiceBusMessage` will publish the message to that session. If the session does not exist, it is created. - + ```java // Setting sessionId publishes that message to a specific session, in this case, "greeting". ServiceBusMessage message = new ServiceBusMessage("Hello world") @@ -300,25 +300,26 @@ sender.sendMessage(message); Receivers can fetch messages from a specific session or the first available, unlocked session. - + ```java // Creates a session-enabled receiver that gets messages from the session "greetings". -ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() +ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") .sessionReceiver() .queueName("<< QUEUE NAME >>") - .sessionId("greetings") .buildAsyncClient(); +Mono receiverAsyncClient = sessionReceiver.acceptSession("greetings"); ``` - + ```java // Creates a session-enabled receiver that gets messages from the first available session. -ServiceBusReceiverAsyncClient receiver = new ServiceBusClientBuilder() +ServiceBusSessionReceiverAsyncClient sessionReceiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") .sessionReceiver() .queueName("<< QUEUE NAME >>") .buildAsyncClient(); +Mono receiverAsyncClient = sessionReceiver.acceptNextSession(); ``` ### Create a dead-letter queue Receiver @@ -328,7 +329,7 @@ The dead-letter queue doesn't need to be explicitly created and can't be deleted of the main entity. For session enabled or non-session queue or topic subscriptions, the dead-letter receiver can be created the same way as shown below. Learn more about dead-letter queue [here][dead-letter-queue]. - + ```java ServiceBusReceiverClient receiver = new ServiceBusClientBuilder() .connectionString("<< CONNECTION STRING FOR THE SERVICE BUS NAMESPACE >>") From c86c5fc5abc8ddca59fda0e2cb0387e007a36aeb Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 11:36:48 -0700 Subject: [PATCH 17/23] Supress @ServiceClient check for methods acceptNextSession and acceptSession --- .../src/main/resources/checkstyle/checkstyle-suppressions.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml index d2d6a48257c4..350114d7c9ad 100755 --- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml +++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -167,6 +167,10 @@ the main ServiceBusClientBuilder. --> + + + + From ef3d9d52103833b8ae914b463bd928ce5b1fdb34 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 15:14:51 -0700 Subject: [PATCH 18/23] Add back maxConcurrentSessions to ServiceBusSessionReceiverBuilder at package level --- .../servicebus/ServiceBusClientBuilder.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java index 1476bc6b59dd..ce079b18c61f 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java @@ -624,6 +624,7 @@ public ServiceBusSenderClient buildClient() { @ServiceClientBuilder(serviceClients = {ServiceBusReceiverClient.class, ServiceBusReceiverAsyncClient.class}) public final class ServiceBusSessionReceiverClientBuilder { private boolean enableAutoComplete = true; + private Integer maxConcurrentSessions = null; private int prefetchCount = DEFAULT_PREFETCH_COUNT; private String queueName; private ReceiveMode receiveMode = ReceiveMode.PEEK_LOCK; @@ -664,6 +665,24 @@ public ServiceBusSessionReceiverClientBuilder maxAutoLockRenewDuration(Duration return this; } + /** + * Enables session processing roll-over by processing at most {@code maxConcurrentSessions}. + * + * @param maxConcurrentSessions Maximum number of concurrent sessions to process at any given time. + * + * @return The modified {@link ServiceBusSessionReceiverClientBuilder} object. + * @throws IllegalArgumentException if {@code maxConcurrentSessions} is less than 1. + */ + ServiceBusSessionReceiverClientBuilder maxConcurrentSessions(int maxConcurrentSessions) { + if (maxConcurrentSessions < 1) { + throw logger.logExceptionAsError(new IllegalArgumentException( + "maxConcurrentSessions cannot be less than 1.")); + } + + this.maxConcurrentSessions = maxConcurrentSessions; + return this; + } + /** * Sets the prefetch count of the receiver. For both {@link ReceiveMode#PEEK_LOCK PEEK_LOCK} and {@link * ReceiveMode#RECEIVE_AND_DELETE RECEIVE_AND_DELETE} modes the default value is 1. @@ -791,7 +810,7 @@ private ServiceBusSessionReceiverAsyncClient buildAsyncClient(boolean isAutoComp final ServiceBusConnectionProcessor connectionProcessor = getOrCreateConnectionProcessor(messageSerializer); final ReceiverOptions receiverOptions = new ReceiverOptions(receiveMode, prefetchCount, - maxAutoLockRenewDuration, enableAutoComplete, null, null); + maxAutoLockRenewDuration, enableAutoComplete, null, maxConcurrentSessions); return new ServiceBusSessionReceiverAsyncClient(connectionProcessor.getFullyQualifiedNamespace(), entityPath, entityType, receiverOptions, connectionProcessor, tracerProvider, messageSerializer, From c13f2809ef3ffd5cf3d86fec7ac81ff969da1e47 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 16:29:26 -0700 Subject: [PATCH 19/23] Remove

    from javadoc --- .../servicebus/ServiceBusSessionReceiverAsyncClient.java | 6 ++---- .../servicebus/ServiceBusSessionReceiverClient.java | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 30020d48f108..7d6e8b072990 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -27,14 +27,12 @@ * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} - *

    *
  • *
  • * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} - *

    *
  • * * @@ -75,7 +73,7 @@ public final class ServiceBusSessionReceiverAsyncClient implements AutoCloseable * immediately. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} - *

    + *

    * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. */ @@ -101,7 +99,7 @@ public Mono acceptNextSession() { * {@link com.azure.core.amqp.exception.AmqpException} is thrown. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} - *

    + * * @param sessionId The session Id. * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the specified session. * @throws NullPointerException if {@code sessionId} is null. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index 3a8b84a2cc38..c515acf0fc58 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -18,14 +18,12 @@ * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} - *

    * *
  • * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} - *

    *
  • * * @@ -46,7 +44,7 @@ public final class ServiceBusSessionReceiverClient implements AutoCloseable { * immediately. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} - *

    + *

    * @return A {@link ServiceBusReceiverClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. * @throws IllegalStateException if the operation times out. The timeout duration is the tryTimeout @@ -66,7 +64,7 @@ public ServiceBusReceiverClient acceptNextSession() { * {@link com.azure.core.amqp.exception.AmqpException} is thrown immediately. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} - *

    + *

    * @param sessionId The session Id. * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. * @throws NullPointerException if {@code sessionId} is null. From e7177900c0688f105b99540bffe42d0282495828 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 16:31:29 -0700 Subject: [PATCH 20/23] Restore some session related code but hide with package level --- .../servicebus/ServiceBusClientBuilder.java | 66 ++++++++ .../servicebus/ServiceBusReceiverClient.java | 141 +++++++++++++++--- 2 files changed, 187 insertions(+), 20 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java index ce079b18c61f..98b47ad619e0 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusClientBuilder.java @@ -754,6 +754,72 @@ public ServiceBusSessionReceiverClientBuilder topicName(String topicName) { return this; } + /** + * Creates an asynchronous, session-aware Service Bus receiver responsible for reading {@link + * ServiceBusMessage messages} from a specific queue or topic. + * + * @return An new {@link ServiceBusReceiverAsyncClient} that receives messages from a queue or topic. + * @throws IllegalStateException if {@link #queueName(String) queueName} or {@link #topicName(String) + * topicName} are not set or, both of these fields are set. It is also thrown if the Service Bus {@link + * #connectionString(String) connectionString} contains an {@code EntityPath} that does not match one set in + * {@link #queueName(String) queueName} or {@link #topicName(String) topicName}. Lastly, if a {@link + * #topicName(String) topicName} is set, but {@link #subscriptionName(String) subscriptionName} is not. + * @throws IllegalArgumentException Queue or topic name are not set via {@link #queueName(String) + * queueName()} or {@link #topicName(String) topicName()}, respectively. + */ + ServiceBusReceiverAsyncClient buildAsyncClientForProcessor() { + return buildAsyncClientForProcessor(true); + } + + /** + * Creates a synchronous, session-aware Service Bus receiver responsible for reading {@link + * ServiceBusMessage messages} from a specific queue or topic. + * + * @return An new {@link ServiceBusReceiverClient} that receives messages from a queue or topic. + * @throws IllegalStateException if {@link #queueName(String) queueName} or {@link #topicName(String) + * topicName} are not set or, both of these fields are set. It is also thrown if the Service Bus {@link + * #connectionString(String) connectionString} contains an {@code EntityPath} that does not match one set in + * {@link #queueName(String) queueName} or {@link #topicName(String) topicName}. Lastly, if a {@link + * #topicName(String) topicName} is set, but {@link #subscriptionName(String) subscriptionName} is not. + * @throws IllegalArgumentException Queue or topic name are not set via {@link #queueName(String) + * queueName()} or {@link #topicName(String) topicName()}, respectively. + */ + ServiceBusReceiverClient buildClientForProcessor() { + return new ServiceBusReceiverClient(buildAsyncClientForProcessor(false), retryOptions.getTryTimeout()); + } + + private ServiceBusReceiverAsyncClient buildAsyncClientForProcessor(boolean isAutoCompleteAllowed) { + final MessagingEntityType entityType = validateEntityPaths(logger, connectionStringEntityName, topicName, + queueName); + final String entityPath = getEntityPath(logger, entityType, queueName, topicName, subscriptionName, + SubQueue.NONE); + + if (!isAutoCompleteAllowed && enableAutoComplete) { + logger.warning( + "'enableAutoComplete' is not supported in synchronous client except through callback receive."); + enableAutoComplete = false; + } else if (enableAutoComplete && receiveMode == ReceiveMode.RECEIVE_AND_DELETE) { + throw logger.logExceptionAsError(new IllegalStateException( + "'enableAutoComplete' is not valid for RECEIVE_AND_DELETE mode.")); + } + + if (receiveMode == ReceiveMode.RECEIVE_AND_DELETE) { + maxAutoLockRenewDuration = Duration.ZERO; + } + + final ServiceBusConnectionProcessor connectionProcessor = getOrCreateConnectionProcessor(messageSerializer); + final ReceiverOptions receiverOptions = new ReceiverOptions(receiveMode, prefetchCount, + maxAutoLockRenewDuration, enableAutoComplete, null, + maxConcurrentSessions); + + final ServiceBusSessionManager sessionManager = new ServiceBusSessionManager(entityPath, entityType, + connectionProcessor, tracerProvider, messageSerializer, receiverOptions); + + return new ServiceBusReceiverAsyncClient(connectionProcessor.getFullyQualifiedNamespace(), entityPath, + entityType, receiverOptions, connectionProcessor, ServiceBusConstants.OPERATION_TIMEOUT, + tracerProvider, messageSerializer, ServiceBusClientBuilder.this::onClientClose, sessionManager); + } + /** * Creates an asynchronous, session-aware Service Bus receiver responsible for reading {@link * ServiceBusMessage messages} from a specific queue or topic. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java index 6ef4cd224f51..66acf0c326d5 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverClient.java @@ -216,7 +216,7 @@ public void deadLetter(ServiceBusReceivedMessage message, DeadLetterOptions opti * @throws IllegalStateException if the receiver is a non-session receiver. */ public byte[] getSessionState() { - return asyncClient.getSessionState().block(operationTimeout); + return this.getSessionState(asyncClient.getReceiverOptions().getSessionId()); } /** @@ -228,9 +228,22 @@ public byte[] getSessionState() { * @see Message browsing */ public ServiceBusReceivedMessage peekMessage() { - return asyncClient.peekMessage().block(operationTimeout); + return this.peekMessage(asyncClient.getReceiverOptions().getSessionId()); } + /** + * Reads the next active message without changing the state of the receiver or the message source. The first call to + * {@code peek()} fetches the first active message for this receiver. Each subsequent call fetches the subsequent + * message in the entity. + * + * @param sessionId Session id of the message to peek from. {@code null} if there is no session. + * + * @return A peeked {@link ServiceBusReceivedMessage}. + * @see Message browsing + */ + ServiceBusReceivedMessage peekMessage(String sessionId) { + return asyncClient.peekMessage(sessionId).block(operationTimeout); + } /** * Starting from the given sequence number, reads next the active message without changing the state of the receiver * or the message source. @@ -241,7 +254,21 @@ public ServiceBusReceivedMessage peekMessage() { * @see Message browsing */ public ServiceBusReceivedMessage peekMessageAt(long sequenceNumber) { - return asyncClient.peekMessageAt(sequenceNumber).block(operationTimeout); + return this.peekMessageAt(sequenceNumber, asyncClient.getReceiverOptions().getSessionId()); + } + + /** + * Starting from the given sequence number, reads next the active message without changing the state of the receiver + * or the message source. + * + * @param sequenceNumber The sequence number from where to read the message. + * @param sessionId Session id of the message to peek from. {@code null} if there is no session. + * + * @return A peeked {@link ServiceBusReceivedMessage}. + * @see Message browsing + */ + ServiceBusReceivedMessage peekMessageAt(long sequenceNumber, String sessionId) { + return asyncClient.peekMessageAt(sequenceNumber, sessionId).block(operationTimeout); } /** @@ -254,12 +281,26 @@ public ServiceBusReceivedMessage peekMessageAt(long sequenceNumber) { * @see Message browsing */ public IterableStream peekMessages(int maxMessages) { + return this.peekMessages(maxMessages, asyncClient.getReceiverOptions().getSessionId()); + } + + /** + * Reads the next batch of active messages without changing the state of the receiver or the message source. + * + * @param maxMessages The number of messages. + * @param sessionId Session id of the messages to peek from. {@code null} if there is no session. + * + * @return An {@link IterableStream} of {@link ServiceBusReceivedMessage messages} that are peeked. + * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. + * @see Message browsing + */ + IterableStream peekMessages(int maxMessages, String sessionId) { if (maxMessages <= 0) { throw logger.logExceptionAsError(new IllegalArgumentException( "'maxMessages' cannot be less than or equal to 0. maxMessages: " + maxMessages)); } - final Flux messages = asyncClient.peekMessages(maxMessages) + final Flux messages = asyncClient.peekMessages(maxMessages, sessionId) .timeout(operationTimeout); // Subscribe so we can kick off this operation. @@ -280,13 +321,30 @@ public IterableStream peekMessages(int maxMessages) { * @see Message browsing */ public IterableStream peekMessagesAt(int maxMessages, long sequenceNumber) { + return this.peekMessagesAt(maxMessages, sequenceNumber, asyncClient.getReceiverOptions().getSessionId()); + } + + /** + * Starting from the given sequence number, reads the next batch of active messages without changing the state of + * the receiver or the message source. + * + * @param maxMessages The number of messages. + * @param sequenceNumber The sequence number from where to start reading messages. + * @param sessionId Session id of the messages to peek from. {@code null} if there is no session. + * + * @return An {@link IterableStream} of {@link ServiceBusReceivedMessage} peeked. + * @throws IllegalArgumentException if {@code maxMessages} is not a positive integer. + * @see Message browsing + */ + IterableStream peekMessagesAt(int maxMessages, long sequenceNumber, + String sessionId) { if (maxMessages <= 0) { throw logger.logExceptionAsError(new IllegalArgumentException( "'maxMessages' cannot be less than or equal to 0. maxMessages: " + maxMessages)); } - final Flux messages = asyncClient.peekMessagesAt(maxMessages, sequenceNumber) - .timeout(operationTimeout); + final Flux messages = asyncClient.peekMessagesAt(maxMessages, sequenceNumber, + sessionId).timeout(operationTimeout); // Subscribe so we can kick off this operation. messages.subscribe(); @@ -348,7 +406,21 @@ public IterableStream receiveMessages(int maxM * @return A deferred message with the matching {@code sequenceNumber}. */ public ServiceBusReceivedMessage receiveDeferredMessage(long sequenceNumber) { - return asyncClient.receiveDeferredMessage(sequenceNumber).block(operationTimeout); + return this.receiveDeferredMessage(sequenceNumber, asyncClient.getReceiverOptions().getSessionId()); + } + + /** + * Receives a deferred {@link ServiceBusReceivedMessage message}. Deferred messages can only be received by using + * sequence number. + * + * @param sequenceNumber The {@link ServiceBusReceivedMessage#getSequenceNumber() sequence number} of the + * message. + * @param sessionId Session id of the deferred message. + * + * @return A deferred message with the matching {@code sequenceNumber}. + */ + ServiceBusReceivedMessage receiveDeferredMessage(long sequenceNumber, String sessionId) { + return asyncClient.receiveDeferredMessage(sequenceNumber, sessionId).block(operationTimeout); } /** @@ -360,8 +432,22 @@ public ServiceBusReceivedMessage receiveDeferredMessage(long sequenceNumber) { * @return An {@link IterableStream} of deferred {@link ServiceBusReceivedMessage messages}. */ public IterableStream receiveDeferredMessageBatch(Iterable sequenceNumbers) { - final Flux messages = asyncClient.receiveDeferredMessages(sequenceNumbers) - .timeout(operationTimeout); + return this.receiveDeferredMessageBatch(sequenceNumbers, asyncClient.getReceiverOptions().getSessionId()); + } + + /** + * Receives a batch of deferred {@link ServiceBusReceivedMessage messages}. Deferred messages can only be received + * by using sequence number. + * + * @param sequenceNumbers The sequence numbers of the deferred messages. + * @param sessionId Session id of the deferred messages. {@code null} if there is no session. + * + * @return An {@link IterableStream} of deferred {@link ServiceBusReceivedMessage messages}. + */ + IterableStream receiveDeferredMessageBatch(Iterable sequenceNumbers, + String sessionId) { + final Flux messages = asyncClient.receiveDeferredMessages(sequenceNumbers, + sessionId).timeout(operationTimeout); // Subscribe so we can kick off this operation. messages.subscribe(); @@ -420,7 +506,7 @@ public void renewMessageLock(ServiceBusReceivedMessage message, Duration maxLock * @throws IllegalStateException if the receiver is a non-session receiver. */ public OffsetDateTime renewSessionLock() { - return asyncClient.renewSessionLock().block(operationTimeout); + return this.renewSessionLock(asyncClient.getReceiverOptions().getSessionId()); } /** @@ -434,15 +520,7 @@ public OffsetDateTime renewSessionLock() { * @throws IllegalStateException if the receiver is a non-session receiver or the receiver is disposed. */ public void renewSessionLock(Duration maxLockRenewalDuration, Consumer onError) { - String sessionId = asyncClient.getReceiverOptions().getSessionId(); - final Consumer throwableConsumer = onError != null - ? onError - : error -> logger.warning("Exception occurred while renewing session: '{}'.", sessionId, error); - - asyncClient.renewSessionLock(maxLockRenewalDuration).subscribe( - v -> logger.verbose("Completed renewing session: '{}'", sessionId), - throwableConsumer, - () -> logger.verbose("Auto session lock renewal operation completed: {}", sessionId)); + this.renewSessionLock(asyncClient.getReceiverOptions().getSessionId(), maxLockRenewalDuration, onError); } /** @@ -453,7 +531,7 @@ public void renewSessionLock(Duration maxLockRenewalDuration, Consumer onError) { + final Consumer throwableConsumer = onError != null + ? onError + : error -> logger.warning("Exception occurred while renewing session: '{}'.", sessionId, error); + + asyncClient.renewSessionLock(maxLockRenewalDuration).subscribe( + v -> logger.verbose("Completed renewing session: '{}'", sessionId), + throwableConsumer, + () -> logger.verbose("Auto session lock renewal operation completed: {}", sessionId)); + } + + void setSessionState(String sessionId, byte[] sessionState) { + asyncClient.setSessionState(sessionId, sessionState).block(operationTimeout); + } + + byte[] getSessionState(String sessionId) { + return asyncClient.getSessionState(sessionId).block(operationTimeout); + } } From 110fcb7c857eda89f222660867d03e009d13a812 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Thu, 29 Oct 2020 20:36:16 -0700 Subject: [PATCH 21/23] Update sync ServiceBusReceiverClientTest --- .../ServiceBusReceiverAsyncClient.java | 15 +----- .../ServiceBusReceiverClientTest.java | 54 ++++++++++++------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java index 62ce3495cc53..d7daa1471044 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusReceiverAsyncClient.java @@ -804,20 +804,7 @@ public Mono renewSessionLock(Duration maxLockRenewalDuration) { * @throws IllegalStateException if the receiver is a non-session receiver. */ public Mono setSessionState(byte[] sessionState) { - if (isDisposed.get()) { - return monoError(logger, new IllegalStateException( - String.format(INVALID_OPERATION_DISPOSED_RECEIVER, "setSessionState"))); - } else if (!receiverOptions.isSessionReceiver()) { - return monoError(logger, new IllegalStateException("Cannot set session state on a non-session receiver.")); - } - String sessionId = receiverOptions.getSessionId(); - final String linkName = sessionManager != null - ? sessionManager.getLinkName(sessionId) - : null; - - return connectionProcessor - .flatMap(connection -> connection.getManagementNode(entityPath, entityType)) - .flatMap(channel -> channel.setSessionState(sessionId, sessionState, linkName)); + return this.setSessionState(receiverOptions.getSessionId(), sessionState); } /** diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java index 26001ea5c17d..c975b62fa26e 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/ServiceBusReceiverClientTest.java @@ -40,7 +40,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -55,6 +54,7 @@ class ServiceBusReceiverClientTest { private static final String NAMESPACE = "test-namespace"; private static final String ENTITY_PATH = "test-entity-path"; private static final String LOCK_TOKEN = UUID.randomUUID().toString(); + private static final String SESSION_ID = "test-session-id"; private static final Duration OPERATION_TIMEOUT = Duration.ofSeconds(5); @@ -72,6 +72,8 @@ class ServiceBusReceiverClientTest { private ServiceBusReceivedMessage message; @Mock private Consumer onErrorConsumer; + @Mock + private ReceiverOptions sessionReceiverOptions; @BeforeEach void setup() { @@ -79,7 +81,7 @@ void setup() { when(asyncClient.getEntityPath()).thenReturn(ENTITY_PATH); when(asyncClient.getFullyQualifiedNamespace()).thenReturn(NAMESPACE); when(asyncClient.getReceiverOptions()).thenReturn(new ReceiverOptions(ReceiveMode.PEEK_LOCK, 1, null, false)); - + when(sessionReceiverOptions.getSessionId()).thenReturn(SESSION_ID); client = new ServiceBusReceiverClient(asyncClient, OPERATION_TIMEOUT); } @@ -392,9 +394,9 @@ void deadLetterMessageWithOptions() { @Test void getSessionState() { // Arrange - final String sessionId = "a-session-id"; final byte[] contents = new byte[]{10, 111, 23}; - when(asyncClient.getSessionState()).thenReturn(Mono.just(contents)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.getSessionState(SESSION_ID)).thenReturn(Mono.just(contents)); // Act final byte[] actual = client.getSessionState(); @@ -406,8 +408,8 @@ void getSessionState() { @Test void getSessionStateNull() { // Arrange - final String sessionId = "a-session-id"; - when(asyncClient.getSessionState()).thenReturn(Mono.empty()); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.getSessionState(SESSION_ID)).thenReturn(Mono.empty()); // Act final byte[] actual = client.getSessionState(); @@ -420,7 +422,8 @@ void getSessionStateNull() { void peekMessage() { // Arrange final ServiceBusReceivedMessage message = mock(ServiceBusReceivedMessage.class); - when(asyncClient.peekMessage()).thenReturn(Mono.just(message)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessage(SESSION_ID)).thenReturn(Mono.just(message)); // Act final ServiceBusReceivedMessage actual = client.peekMessage(); @@ -432,7 +435,8 @@ void peekMessage() { @Test void peekMessageEmptyEntity() { // Arrange - when(asyncClient.peekMessage()).thenReturn(Mono.empty()); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessage(SESSION_ID)).thenReturn(Mono.empty()); // Act final ServiceBusReceivedMessage actual = client.peekMessage(); @@ -446,7 +450,8 @@ void peekMessageFromSequence() { // Arrange final long sequenceNumber = 154; final ServiceBusReceivedMessage message = mock(ServiceBusReceivedMessage.class); - when(asyncClient.peekMessageAt(sequenceNumber)).thenReturn(Mono.just(message)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessageAt(sequenceNumber, SESSION_ID)).thenReturn(Mono.just(message)); // Act final ServiceBusReceivedMessage actual = client.peekMessageAt(sequenceNumber); @@ -462,7 +467,8 @@ void peekMessageFromSequence() { void peekMessagesEmptyEntity() { // Arrange final int maxMessages = 10; - when(asyncClient.peekMessages(maxMessages)).thenReturn(Flux.empty()); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessages(maxMessages, SESSION_ID)).thenReturn(Flux.empty()); // Act final IterableStream actual = client.peekMessages(maxMessages); @@ -505,7 +511,9 @@ void peekMessagesMax() { }); }); - when(asyncClient.peekMessages(maxMessages)).thenReturn(messages); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessages(maxMessages, SESSION_ID)).thenReturn(messages); + // Act final IterableStream actual = client.peekMessages(maxMessages); @@ -549,7 +557,8 @@ void peekMessagesLessThan() { }); }); - when(asyncClient.peekMessages(maxMessages)).thenReturn(messages); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessages(maxMessages, SESSION_ID)).thenReturn(messages); // Act final IterableStream actual = client.peekMessages(maxMessages); @@ -576,7 +585,8 @@ void peekMessagesMaxSequenceNumber() { sink.complete(); })); - when(asyncClient.peekMessagesAt(maxMessages, sequenceNumber)).thenReturn(messages); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.peekMessagesAt(maxMessages, sequenceNumber, SESSION_ID)).thenReturn(messages); // Act final IterableStream actual = client.peekMessagesAt(maxMessages, sequenceNumber); @@ -758,7 +768,8 @@ void receiveDeferredMessage() { // Arrange final long sequenceNumber = 231412; final ServiceBusReceivedMessage message = mock(ServiceBusReceivedMessage.class); - when(asyncClient.receiveDeferredMessage(anyLong())).thenReturn(Mono.just(message)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.receiveDeferredMessage(sequenceNumber, SESSION_ID)).thenReturn(Mono.just(message)); // Act final ServiceBusReceivedMessage actual = client.receiveDeferredMessage(sequenceNumber); @@ -766,7 +777,7 @@ void receiveDeferredMessage() { // Assert assertEquals(message, actual); - verify(asyncClient).receiveDeferredMessage(sequenceNumber); + verify(asyncClient).receiveDeferredMessage(sequenceNumber, SESSION_ID); } @Test @@ -776,7 +787,8 @@ void receiveDeferredMessageBatch() { final long sequenceNumber2 = 13124; final ServiceBusReceivedMessage message = mock(ServiceBusReceivedMessage.class); final ServiceBusReceivedMessage message2 = mock(ServiceBusReceivedMessage.class); - when(asyncClient.receiveDeferredMessages(any())).thenReturn(Flux.just(message, message2)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.receiveDeferredMessages(any(), eq(SESSION_ID))).thenReturn(Flux.just(message, message2)); List collection = Arrays.asList(sequenceNumber, sequenceNumber2); // Act @@ -809,7 +821,9 @@ void renewSessionLock() { // Arrange final String sessionId = "a-session-id"; final OffsetDateTime response = Instant.ofEpochSecond(1585259339).atOffset(ZoneOffset.UTC); - when(asyncClient.renewSessionLock()).thenReturn(Mono.just(response)); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.renewSessionLock(SESSION_ID)).thenReturn(Mono.just(response)); + // Act final OffsetDateTime actual = client.renewSessionLock(); @@ -821,14 +835,14 @@ void renewSessionLock() { @Test void setSessionState() { // Arrange - final String sessionId = "a-session-id"; final byte[] contents = new byte[]{10, 111, 23}; - when(asyncClient.setSessionState(contents)).thenReturn(Mono.empty()); + when(asyncClient.getReceiverOptions()).thenReturn(sessionReceiverOptions); + when(asyncClient.setSessionState(SESSION_ID, contents)).thenReturn(Mono.empty()); // Act client.setSessionState(contents); // Assert - verify(asyncClient).setSessionState(contents); + verify(asyncClient).setSessionState(SESSION_ID, contents); } } From d5147930745172c145805102ede773b6e93e478a Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Fri, 30 Oct 2020 01:21:42 -0700 Subject: [PATCH 22/23] Try fix javadoc ci problem --- .../servicebus/ServiceBusSessionReceiverAsyncClient.java | 6 +++--- .../servicebus/ServiceBusSessionReceiverClient.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 7d6e8b072990..2ee274d1b43e 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -25,13 +25,13 @@ *

      *
    • * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. - *

      + * * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} *

    • *
    • * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. - *

      + * * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} *

    • *
    @@ -99,7 +99,7 @@ public Mono acceptNextSession() { * {@link com.azure.core.amqp.exception.AmqpException} is thrown. *

    * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} - * + *

    * @param sessionId The session Id. * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the specified session. * @throws NullPointerException if {@code sessionId} is null. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index c515acf0fc58..a8994c59091a 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -16,13 +16,13 @@ *

      *
    • * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. - *

      + * * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} *

    • *
    • * Use {@link #acceptNextSession()} to acquire the lock of the next available session * without specifying the session id. - *

      + * * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} *

    • *
    From 36c8c95a2d0c929c73c7604b24fc3db176e97db2 Mon Sep 17 00:00:00 2001 From: Yijun Xie <48257664+YijunXieMS@users.noreply.github.com> Date: Fri, 30 Oct 2020 01:32:40 -0700 Subject: [PATCH 23/23] Try fix javadoc ci problem --- .../ServiceBusSessionReceiverAsyncClient.java | 25 ++++++++----------- .../ServiceBusSessionReceiverClient.java | 24 +++++++----------- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java index 2ee274d1b43e..3594b8aca09e 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverAsyncClient.java @@ -22,19 +22,14 @@ /** * This session receiver client is used to acquire session locks from a queue or topic and create * {@link ServiceBusReceiverAsyncClient} instances that are tied to the locked sessions. - *
      - *
    • - * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. * - * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} - *
    • - *
    • - * Use {@link #acceptNextSession()} to acquire the lock of the next available session - * without specifying the session id. + * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. * - * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} - *
    • - *
    + * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} + * + * Use {@link #acceptNextSession()} to acquire the lock of the next available session without specifying the session id. + * + * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} * */ @ServiceClient(builder = ServiceBusClientBuilder.class, isAsync = true) @@ -71,9 +66,9 @@ public final class ServiceBusSessionReceiverAsyncClient implements AutoCloseable * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverAsyncClient} * to receive messages from the session. It will wait until a session is available if no one is available * immediately. - *

    + * * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#nextsession} - *

    + * * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. */ @@ -97,9 +92,9 @@ public Mono acceptNextSession() { * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverAsyncClient} * to receive messages from the session. If the session is already locked by another client, an * {@link com.azure.core.amqp.exception.AmqpException} is thrown. - *

    + * * {@codesnippet com.azure.messaging.servicebus.servicebusasyncreceiverclient.instantiation#sessionId} - *

    + * * @param sessionId The session Id. * @return A {@link ServiceBusReceiverAsyncClient} that is tied to the specified session. * @throws NullPointerException if {@code sessionId} is null. diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java index a8994c59091a..57078cfc28ee 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/ServiceBusSessionReceiverClient.java @@ -13,19 +13,13 @@ /** * This session receiver client is used to acquire session locks from a queue or topic and create * {@link ServiceBusReceiverClient} instances that are tied to the locked sessions. - *

      - *
    • - * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. + * Use {@link #acceptSession(String)} to acquire the lock of a session if you know the session id. * - * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} - *
    • - *
    • - * Use {@link #acceptNextSession()} to acquire the lock of the next available session - * without specifying the session id. + * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} * - * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} - *
    • - *
    + * Use {@link #acceptNextSession()} to acquire the lock of the next available session without specifying the session id. + * + * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} * */ @ServiceClient(builder = ServiceBusClientBuilder.class) @@ -42,9 +36,9 @@ public final class ServiceBusSessionReceiverClient implements AutoCloseable { * Acquires a session lock for the next available session and create a {@link ServiceBusReceiverClient} * to receive messages from the session. It will wait until a session is available if no one is available * immediately. - *

    + * * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#nextsession} - *

    + * * @return A {@link ServiceBusReceiverClient} that is tied to the available session. * @throws UnsupportedOperationException if the queue or topic subscription is not session-enabled. * @throws IllegalStateException if the operation times out. The timeout duration is the tryTimeout @@ -62,9 +56,9 @@ public ServiceBusReceiverClient acceptNextSession() { * Acquires a session lock for {@code sessionId} and create a {@link ServiceBusReceiverClient} * to receive messages from the session. If the session is already locked by another client, an * {@link com.azure.core.amqp.exception.AmqpException} is thrown immediately. - *

    + * * {@codesnippet com.azure.messaging.servicebus.servicebusreceiverclient.instantiation#sessionId} - *

    + * * @param sessionId The session Id. * @return A {@link ServiceBusReceiverClient} that is tied to the specified session. * @throws NullPointerException if {@code sessionId} is null.