From 5f107630650dde951113a55a9f6e6e884253f670 Mon Sep 17 00:00:00 2001 From: John Taubensee Date: Mon, 3 Apr 2017 16:29:41 -0700 Subject: [PATCH 1/4] Delete readme.md Deleting outdated readme --- .../src/main/java/com/microsoft/azure/servicebus/readme.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/readme.md diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/readme.md b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/readme.md deleted file mode 100644 index b4a35dfc6..000000000 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -com.microsoft.azure.servicebus is not yet ready to be "ServiceBus Topic/Queue client". -This is created with a futuristic goal - to enable CodeSharing between Topic/Queue Client and EventHubs Client. \ No newline at end of file From 864c71e8eda7ab6e593267efb2c1557ec821f128 Mon Sep 17 00:00:00 2001 From: Sreeram Garlapati Date: Tue, 4 Apr 2017 19:01:43 -0700 Subject: [PATCH 2/4] Fix connection, link and timer object leaks after EntityClose & fix inaccurate timer issues (#88) * Fix connection, link and timer object leaks after EntityClose * fixes stuck behaviors in Sender & Receiver * MsgReceiver should update the pendingReceive queue before enqueing work to ReactorDispatcher * fix receiver when prefetch queue is full * Fix Sender & Receiver dormant links after close * move messagesender timer out-of reactorDispatcher queue * handle scenarios where an underlying linkOpen is pending and user invokes receiver.close() * fix the close paths for send - for dormant links * Move link open close timers out of ReactorDispatcher * Clear pendingSends & Receives in case of CloseTimeout * Don't open amqpconnection if MsgFactory is in Closed() state * Fix sender stuck issue due to a missing not(!) in if check * Fix dead links registered for ConnectionHandler errors * add manual test which can be used to test intermittent connection scanarios effectively * minor refactor * don't proactively throw sendlink error on token renewal failure * nit fixes * code refactor * refactoring messagesender * Update minor release * minor refactor --- ConsumingEvents.md | 2 +- PublishingEvents.md | 2 +- .../servicebus/ActiveClientTokenManager.java | 28 +- .../azure/servicebus/CBSChannel.java | 11 +- .../azure/servicebus/ClientConstants.java | 2 +- .../azure/servicebus/ISessionProvider.java | 3 +- .../azure/servicebus/MessageReceiver.java | 351 ++++++++++-------- .../azure/servicebus/MessageSender.java | 281 ++++++++------ .../azure/servicebus/MessagingFactory.java | 10 +- .../azure/servicebus/ServiceBusException.java | 2 +- .../servicebus/amqp/ReactorDispatcher.java | 16 +- .../azure/servicebus/amqp/SessionHandler.java | 26 +- .../sendrecv/ReceiveParallelManualTest.java | 134 +++++++ pom.xml | 2 +- readme.md | 2 +- 15 files changed, 543 insertions(+), 329 deletions(-) create mode 100644 azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java diff --git a/ConsumingEvents.md b/ConsumingEvents.md index b8f6680e8..008f69fa3 100644 --- a/ConsumingEvents.md +++ b/ConsumingEvents.md @@ -30,7 +30,7 @@ following dependency declaration inside of your Maven project file: com.microsoft.azure azure-eventhubs-clients - 0.13.0 + 0.13.1 ``` diff --git a/PublishingEvents.md b/PublishingEvents.md index a9b3b20e6..707d1a4e1 100644 --- a/PublishingEvents.md +++ b/PublishingEvents.md @@ -12,7 +12,7 @@ following dependency declaration inside of your Maven project file: com.microsoft.azure azure-eventhubs-clients - 0.13.0 + 0.13.1 ``` diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java index 88ab33b5f..77766dc89 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java @@ -6,16 +6,20 @@ import java.time.Duration; import java.util.Locale; +import java.util.concurrent.ScheduledFuture; import java.util.logging.Level; import java.util.logging.Logger; public class ActiveClientTokenManager { private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - public final Runnable sendTokenTask; - public final ClientEntity clientEntity; - public final Duration tokenRefreshInterval; + + private ScheduledFuture timer; + + private final Object timerLock; + private final Runnable sendTokenTask; + private final ClientEntity clientEntity; + private final Duration tokenRefreshInterval; public ActiveClientTokenManager( final ClientEntity clientEntity, @@ -25,8 +29,18 @@ public ActiveClientTokenManager( this.sendTokenTask = sendTokenAsync; this.clientEntity = clientEntity; this.tokenRefreshInterval = tokenRefreshInterval; + this.timerLock = new Object(); + + synchronized (this.timerLock) { + this.timer = Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); + } + } + + public void cancel() { - Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); + synchronized (this.timerLock) { + this.timer.cancel(false); + } } private class TimerCallback implements Runnable { @@ -38,7 +52,9 @@ public void run() { sendTokenTask.run(); - Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); + synchronized (timerLock) { + timer = Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); + } } else { diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java index 8fa95abb9..3b576fbb5 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.HashMap; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import org.apache.qpid.proton.Proton; import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; @@ -107,10 +107,13 @@ public void run(IOperationResult operationCal CBSChannel.this.sessionProvider.getSession( "cbs-session", null, - new Consumer() { + new BiConsumer() { @Override - public void accept(ErrorCondition error) { - operationCallback.onError(new AmqpException(error)); + public void accept(ErrorCondition error, Exception exception) { + if (error != null) + operationCallback.onError(new AmqpException(error)); + else if (exception != null) + operationCallback.onError(exception); } })); diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java index 6a44e5b9b..cb2d6c057 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java @@ -52,7 +52,7 @@ private ClientConstants() { } public final static String DEFAULT_RETRY = "Default"; public final static String PRODUCT_NAME = "MSJavaClient"; - public final static String CURRENT_JAVACLIENT_VERSION = "0.13.0"; + public final static String CURRENT_JAVACLIENT_VERSION = "0.13.1"; public static final String PLATFORM_INFO = getPlatformInfo(); diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java index 61b107b61..32eab7184 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java @@ -4,6 +4,7 @@ */ package com.microsoft.azure.servicebus; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.apache.qpid.proton.amqp.transport.ErrorCondition; @@ -14,5 +15,5 @@ interface ISessionProvider Session getSession( final String path, final Consumer onSessionOpen, - final Consumer onSessionOpenError); + final BiConsumer onSessionOpenError); } \ No newline at end of file diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java index 3a479cf69..ec0f1d78b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java @@ -10,13 +10,15 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,18 +59,22 @@ public final class MessageReceiver extends ClientEntity implements IAmqpReceiver private final CompletableFuture linkClose; private final Object prefetchCountSync; private final IReceiverSettingsProvider settingsProvider; - private final String tokenAudience; - private final ActiveClientTokenManager activeClientTokenManager; - + private final String tokenAudience; + private final ActiveClientTokenManager activeClientTokenManager; + private final WorkItem linkOpen; + private final ConcurrentLinkedQueue prefetchedMessages; + private final ReceiveWork receiveWork; + private final CreateAndReceive createAndReceive; + private int prefetchCount; - private ConcurrentLinkedQueue prefetchedMessages; - private Receiver receiveLink; - private WorkItem linkOpen; + private Receiver receiveLink; private Duration receiveTimeout; - private Message lastReceivedMessage; + private Message lastReceivedMessage; private Exception lastKnownLinkError; private int nextCreditToFlow; - private boolean creatingLink; + private boolean creatingLink; + private ScheduledFuture openTimer; + private ScheduledFuture closeTimer; private MessageReceiver(final MessagingFactory factory, final String name, @@ -87,94 +93,81 @@ private MessageReceiver(final MessagingFactory factory, this.lastKnownLinkError = null; this.receiveTimeout = factory.getOperationTimeout(); this.prefetchCountSync = new Object(); - this.settingsProvider = settingsProvider; - this.linkOpen = new WorkItem<>(new CompletableFuture<>(), factory.getOperationTimeout()); + this.settingsProvider = settingsProvider; + this.linkOpen = new WorkItem<>(new CompletableFuture<>(), factory.getOperationTimeout()); this.pendingReceives = new ConcurrentLinkedQueue<>(); // onOperationTimeout delegate - per receive call this.onOperationTimedout = new Runnable() { - public void run() - { - WorkItem> topWorkItem = null; - boolean workItemTimedout = false; - while((topWorkItem = MessageReceiver.this.pendingReceives.peek()) != null) - { - if (topWorkItem.getTimeoutTracker().remaining().toMillis() <= MessageReceiver.MIN_TIMEOUT_DURATION_MILLIS) - { - WorkItem> dequedWorkItem = MessageReceiver.this.pendingReceives.poll(); - if (dequedWorkItem != null) - { - workItemTimedout = true; - dequedWorkItem.getWork().complete(null); - } - else - break; - } - else - { - MessageReceiver.this.scheduleOperationTimer(topWorkItem.getTimeoutTracker()); - break; - } - } - - if (workItemTimedout) - { - // workaround to push the sendflow-performative to reactor - // this sets the receiveLink endpoint to modified state - // (and increment the unsentCredits in proton by 0) - try - { - MessageReceiver.this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - MessageReceiver.this.receiveLink.flow(0); - } - }); - } - catch (IOException ignore) - { - } - } - } + public void run() + { + WorkItem> topWorkItem = null; + while((topWorkItem = MessageReceiver.this.pendingReceives.peek()) != null) + { + if (topWorkItem.getTimeoutTracker().remaining().toMillis() <= MessageReceiver.MIN_TIMEOUT_DURATION_MILLIS) + { + WorkItem> dequedWorkItem = MessageReceiver.this.pendingReceives.poll(); + if (dequedWorkItem != null && dequedWorkItem.getWork() != null && !dequedWorkItem.getWork().isDone()) { + dequedWorkItem.getWork().complete(null); + } + else + break; + } + else + { + MessageReceiver.this.scheduleOperationTimer(topWorkItem.getTimeoutTracker()); + break; + } + } + } }; - this.tokenAudience = String.format("amqp://%s/%s", underlyingFactory.getHostName(), receivePath); - - this.activeClientTokenManager = new ActiveClientTokenManager( - this, - new Runnable() { - @Override - public void run() { - try { - underlyingFactory.getCBSChannel().sendToken( - underlyingFactory.getReactorScheduler(), - underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), - tokenAudience, - new IOperationResult() { - @Override - public void onComplete(Void result) { - if (TRACE_LOGGER.isLoggable(Level.FINE)) { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, - "path[%s], linkName[%s] - token renewed", receivePath, receiveLink.getName())); - } - } - @Override - public void onError(Exception error) { - MessageReceiver.this.onError(error); - } - }); - } - catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { - MessageReceiver.this.onError(exception); - } + this.receiveWork = new ReceiveWork(); + this.createAndReceive = new CreateAndReceive(); + + this.tokenAudience = String.format("amqp://%s/%s", underlyingFactory.getHostName(), receivePath); + + this.activeClientTokenManager = new ActiveClientTokenManager( + this, + new Runnable() { + @Override + public void run() { + try { + underlyingFactory.getCBSChannel().sendToken( + underlyingFactory.getReactorScheduler(), + underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), + tokenAudience, + new IOperationResult() { + @Override + public void onComplete(Void result) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, + "path[%s], linkName[%s] - token renewed", receivePath, receiveLink.getName())); + } + } + @Override + public void onError(Exception error) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s], tokenRenewalFailure[%s]", receivePath, receiveLink.getName(), error.getMessage())); + } + } + }); + } + catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", receivePath, receiveLink.getName(), exception.getMessage())); } - }, - ClientConstants.TOKEN_REFRESH_INTERVAL); + } + } + }, + ClientConstants.TOKEN_REFRESH_INTERVAL); } // @param connection Connection on which the MessageReceiver's receive Amqp link need to be created on. @@ -195,10 +188,10 @@ public static CompletableFuture create( return msgReceiver.createLink(); } - public String getReceivePath() - { - return this.receivePath; - } + public String getReceivePath() + { + return this.receivePath; + } private CompletableFuture createLink() { @@ -225,13 +218,13 @@ public void onEvent() private List receiveCore(final int messageCount) { List returnMessages = null; - Message currentMessage = this.pollPrefetchQueue(); + Message currentMessage; - while (currentMessage != null) + while ((currentMessage = this.pollPrefetchQueue()) != null) { if (returnMessages == null) { - returnMessages = new LinkedList(); + returnMessages = new LinkedList<>(); } returnMessages.add(currentMessage); @@ -239,8 +232,6 @@ private List receiveCore(final int messageCount) { break; } - - currentMessage = this.pollPrefetchQueue(); } return returnMessages; @@ -304,49 +295,37 @@ public CompletableFuture> receive(final int maxMessageCount) this.scheduleOperationTimer(TimeoutTracker.create(this.receiveTimeout)); } - CompletableFuture> onReceive = new CompletableFuture>(); + CompletableFuture> onReceive = new CompletableFuture<>(); + pendingReceives.offer(new ReceiveWorkItem(onReceive, receiveTimeout, maxMessageCount)); - try - { - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - final List messages = receiveCore(maxMessageCount); - if (messages != null) - onReceive.complete(messages); - else - pendingReceives.offer(new ReceiveWorkItem(onReceive, receiveTimeout, maxMessageCount)); - - // calls to reactor should precede enqueue of the workItem into PendingReceives. - // This will allow error handling to enact on the enqueued workItem. - if (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED) - { - createReceiveLink(); - } - } - }); + try { + this.underlyingFactory.scheduleOnReactorThread(this.createAndReceive); } - catch (IOException ioException) - { - onReceive.completeExceptionally( - new ServiceBusException(false, "Receive failed while dispatching to Reactor, see cause for more details.", ioException)); + catch (IOException ioException) { + onReceive.completeExceptionally(new OperationCancelledException("Receive failed while dispatching to Reactor, see cause for more details.", ioException)); } - + return onReceive; } @Override public void onOpenComplete(Exception exception) { - this.creatingLink = false; + this.creatingLink = false; if (exception == null) { + if (this.getIsClosingOrClosed()) { + + this.receiveLink.close(); + return; + } + if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) { this.linkOpen.getWork().complete(this); + if (this.openTimer != null) + this.openTimer.cancel(false); } this.lastKnownLinkError = null; @@ -368,6 +347,8 @@ public void onOpenComplete(Exception exception) { this.setClosed(); ExceptionUtil.completeExceptionally(this.linkOpen.getWork(), exception, this); + if (this.openTimer != null) + this.openTimer.cancel(false); } this.lastKnownLinkError = exception; @@ -377,14 +358,12 @@ public void onOpenComplete(Exception exception) @Override public void onReceiveComplete(Delivery delivery) { - Message message = null; - int msgSize = delivery.pending(); byte[] buffer = new byte[msgSize]; int read = receiveLink.recv(buffer, 0, msgSize); - message = Proton.message(); + Message message = Proton.message(); message.decode(buffer, 0, read); delivery.settle(); @@ -392,14 +371,7 @@ public void onReceiveComplete(Delivery delivery) this.prefetchedMessages.add(message); this.underlyingFactory.getRetryPolicy().resetRetryCount(this.getClientId()); - final ReceiveWorkItem currentReceive = this.pendingReceives.poll(); - if (currentReceive != null && !currentReceive.getWork().isDone()) - { - List messages = this.receiveCore(currentReceive.maxMessageCount); - - CompletableFuture> future = currentReceive.getWork(); - future.complete(messages); - } + this.receiveWork.onEvent(); } public void onError(final ErrorCondition error) @@ -411,10 +383,14 @@ public void onError(final ErrorCondition error) @Override public void onError(final Exception exception) { - this.prefetchedMessages.clear(); + this.prefetchedMessages.clear(); + this.underlyingFactory.deregisterForConnectionError(this.receiveLink); if (this.getIsClosingOrClosed()) { + if (this.closeTimer != null) + this.closeTimer.cancel(false); + WorkItem> workItem = null; final boolean isTransientException = exception == null || (exception instanceof ServiceBusException && ((ServiceBusException) exception).getIsTransient()); @@ -431,34 +407,35 @@ public void onError(final Exception exception) } } - this.linkClose.complete(null); + this.linkClose.complete(null); } else { this.lastKnownLinkError = exception == null ? this.lastKnownLinkError : exception; - final Exception completionException = exception == null - ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : exception; - - this.onOpenComplete(completionException); + final Exception completionException = exception == null + ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : exception; + + this.onOpenComplete(completionException); final WorkItem> workItem = this.pendingReceives.peek(); final Duration nextRetryInterval = workItem != null && workItem.getTimeoutTracker() != null ? this.underlyingFactory.getRetryPolicy().getNextRetryInterval(this.getClientId(), completionException, workItem.getTimeoutTracker().remaining()) : null; - boolean recreateScheduled = true; + boolean recreateScheduled = true; - if (nextRetryInterval != null) + if (nextRetryInterval != null) { - try + try { this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler() { @Override public void onEvent() { - if (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED) + if (!MessageReceiver.this.getIsClosingOrClosed() + && (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED)) { createReceiveLink(); underlyingFactory.getRetryPolicy().incrementRetryCount(getClientId()); @@ -468,9 +445,9 @@ public void onEvent() } catch (IOException ignore) { - recreateScheduled = false; + recreateScheduled = false; } - } + } if (nextRetryInterval == null || !recreateScheduled) { @@ -492,7 +469,7 @@ private void scheduleOperationTimer(final TimeoutTracker tracker) } private void createReceiveLink() - { + { if (creatingLink) return; @@ -503,6 +480,13 @@ private void createReceiveLink() @Override public void accept(Session session) { + // if the MessageReceiver is closed - we no-longer need to create the link + if (MessageReceiver.this.getIsClosingOrClosed()) { + + session.close(); + return; + } + final Source source = new Source(); source.setAddress(receivePath); @@ -535,22 +519,19 @@ public void accept(Session session) receiver.open(); - if (MessageReceiver.this.receiveLink != null) - { - final Receiver oldReceiver = MessageReceiver.this.receiveLink; - MessageReceiver.this.underlyingFactory.deregisterForConnectionError(oldReceiver); - } - MessageReceiver.this.receiveLink = receiver; } }; - final Consumer onSessionOpenFailed = new Consumer() + final BiConsumer onSessionOpenFailed = new BiConsumer() { @Override - public void accept(ErrorCondition t) + public void accept(ErrorCondition t, Exception u) { - onError(t); + if (t != null) + onError(t); + else if (u != null) + onError(u); } }; @@ -562,6 +543,9 @@ public void accept(ErrorCondition t) new IOperationResult() { @Override public void onComplete(Void result) { + if (MessageReceiver.this.getIsClosingOrClosed()) + return; + underlyingFactory.getSession( receivePath, onSessionOpen, @@ -604,8 +588,8 @@ private void sendFlow(final int credits) if(TRACE_LOGGER.isLoggable(Level.FINE)) { - TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s]", - this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow)); + TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s], ThreadId[%s]", + this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow, Thread.currentThread().getId())); } } } @@ -613,7 +597,7 @@ private void sendFlow(final int credits) private void scheduleLinkOpenTimeout(final TimeoutTracker timeout) { // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - Timer.schedule( + this.openTimer = Timer.schedule( new Runnable() { public void run() @@ -641,7 +625,7 @@ public void run() private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - Timer.schedule( + this.closeTimer = Timer.schedule( new Runnable() { public void run() @@ -657,6 +641,7 @@ public void run() } ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageReceiver.this); + MessageReceiver.this.onError((Exception) null); } } } @@ -713,18 +698,23 @@ protected CompletableFuture onClose() { try { + this.activeClientTokenManager.cancel(); + scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { @Override public void onEvent() - { + { if (receiveLink != null && receiveLink.getLocalState() != EndpointState.CLOSED) { receiveLink.close(); - scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); } else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CLOSED) { + if (closeTimer != null) + closeTimer.cancel(false); + linkClose.complete(null); } } @@ -738,4 +728,35 @@ else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CL return this.linkClose; } + + private final class ReceiveWork extends DispatchHandler { + + @Override + public void onEvent() { + + ReceiveWorkItem pendingReceive; + while (!prefetchedMessages.isEmpty() && (pendingReceive = pendingReceives.poll()) != null) { + + if (pendingReceive.getWork() != null && !pendingReceive.getWork().isDone()) { + + Collection receivedMessages = receiveCore(pendingReceive.maxMessageCount); + pendingReceive.getWork().complete(receivedMessages); + } + } + } + } + + private final class CreateAndReceive extends DispatchHandler { + + @Override + public void onEvent() { + + receiveWork.onEvent(); + + if (!MessageReceiver.this.getIsClosingOrClosed() + && (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED)) { + createReceiveLink(); + } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java index fa7c10a98..cc9cbb32a 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java @@ -12,6 +12,7 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.Comparator; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.Iterator; import java.util.LinkedList; @@ -63,15 +64,15 @@ public class MessageSender extends ClientEntity implements IAmqpSender, IErrorCo private final MessagingFactory underlyingFactory; private final String sendPath; - private final Duration operationTimeout; + private final Duration operationTimeout; private final RetryPolicy retryPolicy; private final CompletableFuture linkClose; private final Object pendingSendLock; private final ConcurrentHashMap> pendingSendsData; private final PriorityQueue pendingSends; private final DispatchHandler sendWork; - private final ActiveClientTokenManager activeClientTokenManager; - private final String tokenAudience; + private final ActiveClientTokenManager activeClientTokenManager; + private final String tokenAudience; private Sender sendLink; private CompletableFuture linkFirstOpen; @@ -79,9 +80,11 @@ public class MessageSender extends ClientEntity implements IAmqpSender, IErrorCo private TimeoutTracker openLinkTracker; private Exception lastKnownLinkError; private Instant lastKnownErrorReportedAt; - private boolean creatingLink; + private boolean creatingLink; + private ScheduledFuture closeTimer; + private ScheduledFuture openTimer; - public static CompletableFuture create( + public static CompletableFuture create( final MessagingFactory factory, final String sendLinkName, final String senderPath) @@ -154,18 +157,26 @@ public void run() { public void onComplete(Void result) { if (TRACE_LOGGER.isLoggable(Level.FINE)) { TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, - "path[%s], linkName[%s] - token renewed", sendPath, sendLink.getName())); + String.format(Locale.US, + "path[%s], linkName[%s] - token renewed", sendPath, sendLink.getName())); } } @Override public void onError(Exception error) { - MessageSender.this.onError(error); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s] - tokenRenewalFailure[%s]", sendPath, sendLink.getName(), error.getMessage())); + } } }); } catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { - MessageSender.this.onError(exception); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", sendPath, sendLink.getName(), exception.getMessage())); + } } } }, @@ -188,32 +199,12 @@ private CompletableFuture sendCore( final int messageFormat, final CompletableFuture onSend, final TimeoutTracker tracker, - final String deliveryTag, final Exception lastKnownError, final ScheduledFuture timeoutTask) { this.throwIfClosed(this.lastKnownLinkError); - if (tracker != null && onSend != null && (tracker.remaining().isNegative() || tracker.remaining().isZero())) - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, - "path[%s], linkName[%s], deliveryTag[%s] - timed out at sendCore", this.sendPath, this.sendLink.getName(), deliveryTag)); - } - - if (timeoutTask != null) - { - timeoutTask.cancel(false); - } - - this.throwSenderTimeout(onSend, null); - return onSend; - } - final boolean isRetrySend = (onSend != null); - final String tag = (deliveryTag == null) ? UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) : deliveryTag; final CompletableFuture onSendFuture = (onSend == null) ? new CompletableFuture<>() : onSend; @@ -221,15 +212,27 @@ private CompletableFuture sendCore( new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, this.operationTimeout) : new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, tracker); - if (lastKnownError != null) + final TimeoutTracker currentSendTracker = sendWaiterData.getTimeoutTracker(); + final String deliveryTag = UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) + "_" + currentSendTracker.elapsed().getSeconds(); + + if (lastKnownError != null) { sendWaiterData.setLastKnownException(lastKnownError); - } + } + + if (timeoutTask != null) + timeoutTask.cancel(false); + + final ScheduledFuture timeoutTimerTask = Timer.schedule( + new SendTimeout(deliveryTag, sendWaiterData), + currentSendTracker.remaining(), TimerType.OneTimeRun); + + sendWaiterData.setTimeoutTask(timeoutTimerTask); synchronized (this.pendingSendLock) { - this.pendingSendsData.put(tag, sendWaiterData); - this.pendingSends.offer(new WeightedDeliveryTag(tag, isRetrySend ? 1 : 0)); + this.pendingSendsData.put(deliveryTag, sendWaiterData); + this.pendingSends.offer(new WeightedDeliveryTag(deliveryTag, isRetrySend ? 1 : 0)); } try @@ -239,7 +242,7 @@ private CompletableFuture sendCore( catch (IOException ioException) { onSendFuture.completeExceptionally( - new ServiceBusException(false, "Send failed while dispatching to Reactor, see cause for more details.", ioException)); + new OperationCancelledException("Send failed while dispatching to Reactor, see cause for more details.", ioException)); } return onSendFuture; @@ -252,7 +255,7 @@ private CompletableFuture send( final CompletableFuture onSend, final TimeoutTracker tracker) { - return this.sendCore(bytes, arrayOffset, messageFormat, onSend, tracker, null, null, null); + return this.sendCore(bytes, arrayOffset, messageFormat, onSend, tracker, null, null); } public CompletableFuture send(final Iterable messages) @@ -262,7 +265,7 @@ public CompletableFuture send(final Iterable messages) throw new IllegalArgumentException("Sending Empty batch of messages is not allowed."); } - Message firstMessage = messages.iterator().next(); + final Message firstMessage = messages.iterator().next(); if (IteratorUtil.sizeEquals(messages, 1)) { return this.send(firstMessage); @@ -270,10 +273,10 @@ public CompletableFuture send(final Iterable messages) // proton-j doesn't support multiple dataSections to be part of AmqpMessage // here's the alternate approach provided by them: https://github.com/apache/qpid-proton/pull/54 - Message batchMessage = Proton.message(); + final Message batchMessage = Proton.message(); batchMessage.setMessageAnnotations(firstMessage.getMessageAnnotations()); - byte[] bytes = new byte[ClientConstants.MAX_MESSAGE_LENGTH_BYTES]; + final byte[] bytes = new byte[ClientConstants.MAX_MESSAGE_LENGTH_BYTES]; int encodedSize = batchMessage.encode(bytes, 0, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); int byteArrayOffset = encodedSize; @@ -294,7 +297,7 @@ public CompletableFuture send(final Iterable messages) } catch(BufferOverflowException exception) { - final CompletableFuture sendTask = new CompletableFuture(); + final CompletableFuture sendTask = new CompletableFuture<>(); sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception)); return sendTask; } @@ -310,7 +313,7 @@ public CompletableFuture send(Message msg) int payloadSize = AmqpUtil.getDataSerializedSize(msg); int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); - byte[] bytes = new byte[allocationSize]; + final byte[] bytes = new byte[allocationSize]; int encodedSize = 0; try { @@ -333,6 +336,12 @@ public void onOpenComplete(Exception completionException) if (completionException == null) { + if (this.getIsClosingOrClosed()) { + + this.sendLink.close(); + return; + } + this.openLinkTracker = null; this.lastKnownLinkError = null; @@ -341,6 +350,8 @@ public void onOpenComplete(Exception completionException) if (!this.linkFirstOpen.isDone()) { this.linkFirstOpen.complete(this); + if (this.openTimer != null) + this.openTimer.cancel(false); } else { @@ -375,6 +386,8 @@ public void onOpenComplete(Exception completionException) { this.setClosed(); ExceptionUtil.completeExceptionally(this.linkFirstOpen, completionException, this); + if (this.openTimer != null) + this.openTimer.cancel(false); } } } @@ -382,7 +395,7 @@ public void onOpenComplete(Exception completionException) @Override public void onClose(final ErrorCondition condition) { - final Exception completionException = (condition != null && condition.getCondition() != null) ? ExceptionUtil.toException(condition) : null; + final Exception completionException = (condition != null && condition.getCondition() != null) ? ExceptionUtil.toException(condition) : null; this.onError(completionException); } @@ -390,9 +403,13 @@ public void onClose(final ErrorCondition condition) public void onError(final Exception completionException) { this.linkCredit = 0; + this.underlyingFactory.deregisterForConnectionError(this.sendLink); if (this.getIsClosingOrClosed()) { + if (this.closeTimer != null && !this.closeTimer.isDone()) + this.closeTimer.cancel(false); + synchronized (this.pendingSendLock) { for (Map.Entry> pendingSend: this.pendingSendsData.entrySet()) @@ -409,6 +426,7 @@ public void onError(final Exception completionException) } this.linkClose.complete(null); + return; } else @@ -416,21 +434,21 @@ public void onError(final Exception completionException) this.lastKnownLinkError = completionException == null ? this.lastKnownLinkError : completionException; this.lastKnownErrorReportedAt = Instant.now(); - final Exception finalCompletionException = completionException == null - ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; - - this.onOpenComplete(finalCompletionException); + final Exception finalCompletionException = completionException == null + ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; + + this.onOpenComplete(finalCompletionException); final Map.Entry> pendingSendEntry = IteratorUtil.getFirst(this.pendingSendsData.entrySet()); if (pendingSendEntry != null && pendingSendEntry.getValue() != null) { final TimeoutTracker tracker = pendingSendEntry.getValue().getTimeoutTracker(); - if (tracker != null) + if (tracker != null) { final Duration nextRetryInterval = this.retryPolicy.getNextRetryInterval(this.getClientId(), finalCompletionException, tracker.remaining()); - boolean scheduledRecreate = true; + boolean scheduledRecreate = true; - if (nextRetryInterval != null) + if (nextRetryInterval != null) { try { @@ -439,7 +457,8 @@ public void onError(final Exception completionException) @Override public void onEvent() { - if (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED) + if (!MessageSender.this.getIsClosingOrClosed() + && (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED)) { recreateSendLink(); } @@ -448,11 +467,11 @@ public void onEvent() } catch (IOException ignore) { - scheduledRecreate = false; + scheduledRecreate = false; } } - if (nextRetryInterval == null || !scheduledRecreate) + if (nextRetryInterval == null || !scheduledRecreate) { synchronized (this.pendingSendLock) { @@ -494,10 +513,10 @@ public void onSendComplete(final Delivery delivery) } else if (outcome instanceof Rejected) { - Rejected rejected = (Rejected) outcome; - ErrorCondition error = rejected.getError(); + final Rejected rejected = (Rejected) outcome; + final ErrorCondition error = rejected.getError(); - Exception exception = ExceptionUtil.toException(error); + final Exception exception = ExceptionUtil.toException(error); if (ExceptionUtil.isGeneralSendError(error.getCondition())) { @@ -505,7 +524,7 @@ else if (outcome instanceof Rejected) this.lastKnownErrorReportedAt = Instant.now(); } - Duration retryInterval = this.retryPolicy.getNextRetryInterval( + final Duration retryInterval = this.retryPolicy.getNextRetryInterval( this.getClientId(), exception, pendingSendWorkItem.getTimeoutTracker().remaining()); if (retryInterval == null) { @@ -522,7 +541,14 @@ else if (outcome instanceof Rejected) @Override public void onEvent() { - MessageSender.this.reSend(deliveryTag, pendingSendWorkItem, false); + MessageSender.this.sendCore( + pendingSendWorkItem.getMessage(), + pendingSendWorkItem.getEncodedMessageSize(), + pendingSendWorkItem.getMessageFormat(), + pendingSendWorkItem.getWork(), + pendingSendWorkItem.getTimeoutTracker(), + pendingSendWorkItem.getLastKnownException(), + pendingSendWorkItem.getTimeoutTask()); } }); } @@ -546,27 +572,12 @@ else if (outcome instanceof Released) } else { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch", this.sendPath, this.sendLink.getName(), deliveryTag)); + if (TRACE_LOGGER.isLoggable(Level.FINE)) + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch (or send timedout)", this.sendPath, this.sendLink.getName(), deliveryTag)); } } - private void reSend(final String deliveryTag, final ReplayableWorkItem pendingSend, boolean reuseDeliveryTag) - { - if (pendingSend != null) - { - this.sendCore(pendingSend.getMessage(), - pendingSend.getEncodedMessageSize(), - pendingSend.getMessageFormat(), - pendingSend.getWork(), - pendingSend.getTimeoutTracker(), - reuseDeliveryTag ? deliveryTag : null, - pendingSend.getLastKnownException(), - pendingSend.getTimeoutTask()); - } - } - private void cleanupFailedSend(final ReplayableWorkItem failedSend, final Exception exception) { if (failedSend.getTimeoutTask() != null) @@ -587,6 +598,12 @@ private void createSendLink() @Override public void accept(Session session) { + if (MessageSender.this.getIsClosingOrClosed()) { + + session.close(); + return; + } + final Sender sender = session.sender(TrackingUtil.getLinkName(session)); final Target target = new Target(); target.setAddress(sendPath); @@ -603,22 +620,19 @@ public void accept(Session session) MessageSender.this.underlyingFactory.registerForConnectionError(sender); sender.open(); - if (MessageSender.this.sendLink != null) - { - final Sender oldSender = MessageSender.this.sendLink; - MessageSender.this.underlyingFactory.deregisterForConnectionError(oldSender); - } - MessageSender.this.sendLink = sender; } }; - final Consumer onSessionOpenError = new Consumer() + final BiConsumer onSessionOpenError = new BiConsumer() { @Override - public void accept(ErrorCondition t) + public void accept(ErrorCondition t, Exception u) { - MessageSender.this.onClose(t); + if (t != null) + MessageSender.this.onClose(t); + else if (u != null) + MessageSender.this.onError(u); } }; @@ -631,6 +645,9 @@ public void accept(ErrorCondition t) new IOperationResult() { @Override public void onComplete(Void result) { + if (MessageSender.this.getIsClosingOrClosed()) + return; + underlyingFactory.getSession( sendPath, onSessionOpen, @@ -654,7 +671,7 @@ private void initializeLinkOpen(TimeoutTracker timeout) this.linkFirstOpen = new CompletableFuture<>(); // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - Timer.schedule( + this.openTimer = Timer.schedule( new Runnable() { public void run() @@ -724,26 +741,32 @@ private void recreateSendLink() // actual send on the SenderLink should happen only in this method & should run on Reactor Thread private void processSendWork() { - final Sender sendLinkCurrent = this.sendLink; - - if (sendLinkCurrent.getLocalState() == EndpointState.CLOSED || sendLinkCurrent.getRemoteState() == EndpointState.CLOSED) + if (this.sendLink.getLocalState() == EndpointState.CLOSED || this.sendLink.getRemoteState() == EndpointState.CLOSED) { - this.recreateSendLink(); - return; + if (!this.getIsClosingOrClosed()) + this.recreateSendLink(); + + return; } - while (sendLinkCurrent.getLocalState() == EndpointState.ACTIVE && sendLinkCurrent.getRemoteState() == EndpointState.ACTIVE + while (this.sendLink.getLocalState() == EndpointState.ACTIVE && this.sendLink.getRemoteState() == EndpointState.ACTIVE && this.linkCredit > 0) { - final WeightedDeliveryTag deliveryTag; + final WeightedDeliveryTag weightedDelivery; final ReplayableWorkItem sendData; + final String deliveryTag; synchronized (this.pendingSendLock) { - deliveryTag = this.pendingSends.poll(); - sendData = deliveryTag != null - ? this.pendingSendsData.get(deliveryTag.getDeliveryTag()) - : null; - } + weightedDelivery = this.pendingSends.poll(); + if (weightedDelivery != null) { + deliveryTag = weightedDelivery.getDeliveryTag(); + sendData = this.pendingSendsData.get(deliveryTag); + } + else { + sendData = null; + deliveryTag = null; + } + } if (sendData != null) { @@ -751,7 +774,7 @@ private void processSendWork() { // CoreSend could enque Sends into PendingSends Queue and can fail the SendCompletableFuture // (when It fails to schedule the ProcessSendWork on reactor Thread) - this.pendingSendsData.remove(sendData); + this.pendingSendsData.remove(deliveryTag); continue; } @@ -762,13 +785,13 @@ private void processSendWork() try { - delivery = sendLinkCurrent.delivery(deliveryTag.getDeliveryTag().getBytes()); + delivery = this.sendLink.delivery(deliveryTag.getBytes()); delivery.setMessageFormat(sendData.getMessageFormat()); - sentMsgSize = sendLinkCurrent.send(sendData.getMessage(), 0, sendData.getEncodedMessageSize()); + sentMsgSize = this.sendLink.send(sendData.getMessage(), 0, sendData.getEncodedMessageSize()); assert sentMsgSize == sendData.getEncodedMessageSize() : "Contract of the ProtonJ library for Sender.Send API changed"; - linkAdvance = sendLinkCurrent.advance(); + linkAdvance = this.sendLink.advance(); } catch(Exception exception) { @@ -778,21 +801,6 @@ private void processSendWork() if (linkAdvance) { this.linkCredit--; - - ScheduledFuture timeoutTask = Timer.schedule(new Runnable() - { - @Override - public void run() - { - if (!sendData.getWork().isDone()) - { - MessageSender.this.pendingSendsData.remove(deliveryTag); - MessageSender.this.throwSenderTimeout(sendData.getWork(), sendData.getLastKnownException()); - } - } - }, this.operationTimeout, TimerType.OneTimeRun); - - sendData.setTimeoutTask(timeoutTask); sendData.setWaitingForAck(); } else @@ -809,20 +817,19 @@ public void run() delivery.free(); } - sendData.getWork().completeExceptionally( - sendException != null + sendData.getWork().completeExceptionally(sendException != null ? new OperationCancelledException("Send operation failed. Please see cause for more details", sendException) : new OperationCancelledException( - String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag))); + String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag))); } } else { if (deliveryTag != null) { - if (TRACE_LOGGER.isLoggable(Level.SEVERE)) + if (TRACE_LOGGER.isLoggable(Level.FINE)) { - TRACE_LOGGER.log(Level.SEVERE, + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.", this.sendPath, this.sendLink.getName(), deliveryTag)); } @@ -856,7 +863,7 @@ private void throwSenderTimeout(CompletableFuture pendingSendWork, Excepti private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - Timer.schedule( + this.closeTimer = Timer.schedule( new Runnable() { public void run() @@ -872,7 +879,8 @@ public void run() } ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageSender.this); - } + MessageSender.this.onError((Exception) null); + } } } , timeout.remaining() @@ -886,6 +894,8 @@ protected CompletableFuture onClose() { try { + this.activeClientTokenManager.cancel(); + scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { @Override @@ -894,10 +904,12 @@ public void onEvent() if (sendLink != null && sendLink.getLocalState() != EndpointState.CLOSED) { sendLink.close(); - scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); } else if (sendLink == null || sendLink.getRemoteState() == EndpointState.CLOSED) { + if (closeTimer != null) + closeTimer.cancel(false); + linkClose.complete(null); } } @@ -943,4 +955,27 @@ public int compare(WeightedDeliveryTag deliveryTag0, WeightedDeliveryTag deliver return deliveryTag1.getPriority() - deliveryTag0.getPriority(); } } + + private class SendTimeout implements Runnable + { + private final String deliveryTag; + private final ReplayableWorkItem sendWaiterData; + + public SendTimeout( + final String deliveryTag, + final ReplayableWorkItem sendWaiterData) { + this.sendWaiterData = sendWaiterData; + this.deliveryTag = deliveryTag; + } + + @Override + public void run() + { + if (!sendWaiterData.getWork().isDone()) + { + MessageSender.this.pendingSendsData.remove(deliveryTag); + MessageSender.this.throwSenderTimeout(sendWaiterData.getWork(), sendWaiterData.getLastKnownException()); + } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java index 7516b6ef0..1e696a290 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -165,8 +166,13 @@ public CBSChannel getCBSChannel() } @Override - public Session getSession(final String path, final Consumer onRemoteSessionOpen, final Consumer onRemoteSessionOpenError) + public Session getSession(final String path, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) { + if (this.getIsClosingOrClosed()) { + + onRemoteSessionOpenError.accept(null, new OperationCancelledException("underlying messagingFactory instance is closed")); + } + if (this.connection == null || this.connection.getLocalState() == EndpointState.CLOSED || this.connection.getRemoteState() == EndpointState.CLOSED) { this.connection = this.getReactor().connectionToHost(this.hostName, ClientConstants.AMQPS_PORT, this.connectionHandler); @@ -210,6 +216,8 @@ public void onOpenComplete(Exception exception) { this.open.complete(this); this.openConnection.complete(this.connection); + if (this.getIsClosingOrClosed()) + this.connection.close(); } else { diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServiceBusException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServiceBusException.java index eb59ae091..814d3236c 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServiceBusException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServiceBusException.java @@ -34,7 +34,7 @@ public ServiceBusException(final boolean isTransient, final Throwable cause) this.isTransient = isTransient; } - ServiceBusException(final boolean isTransient, final String message, final Throwable cause) + public ServiceBusException(final boolean isTransient, final String message, final Throwable cause) { super(message, cause); this.isTransient = isTransient; diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java index ad273fa3a..3667fe709 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java @@ -9,7 +9,6 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.Pipe; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.HashSet; import org.apache.qpid.proton.engine.BaseHandler; import org.apache.qpid.proton.engine.Event; @@ -72,7 +71,7 @@ private void signalWorkQueue() throws IOException { try { - this.ioSignal.sink().write(ByteBuffer.allocate(1)); + while (this.ioSignal.sink().write(ByteBuffer.allocate(1)) == 0) {} } catch(ClosedChannelException ignorePipeClosedDuringReactorShutdown) { @@ -116,18 +115,11 @@ public void run(Selectable selectable) throw new RuntimeException(ioException); } - final HashSet completedWork = new HashSet(); - BaseHandler topWork = workQueue.poll(); - while (topWork != null) + BaseHandler topWork; + while ((topWork = workQueue.poll()) != null) { - if (!completedWork.contains(topWork)) - { - topWork.onTimerTask(null); - completedWork.add(topWork); - } - - topWork = workQueue.poll(); + topWork.onTimerTask(null); } } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java index 427993595..879862252 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java @@ -9,9 +9,9 @@ import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.engine.BaseHandler; import org.apache.qpid.proton.engine.Connection; @@ -23,6 +23,7 @@ import org.apache.qpid.proton.reactor.Reactor; import com.microsoft.azure.servicebus.ClientConstants; +import com.microsoft.azure.servicebus.ServiceBusException; public class SessionHandler extends BaseHandler { @@ -30,12 +31,12 @@ public class SessionHandler extends BaseHandler private final String entityName; private final Consumer onRemoteSessionOpen; - private final Consumer onRemoteSessionOpenError; + private final BiConsumer onRemoteSessionOpenError; private boolean sessionCreated = false; private boolean sessionOpenErrorDispatched = false; - public SessionHandler(final String entityName, final Consumer onRemoteSessionOpen, final Consumer onRemoteSessionOpenError) + public SessionHandler(final String entityName, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) { this.entityName = entityName; this.onRemoteSessionOpenError = onRemoteSessionOpenError; @@ -45,7 +46,7 @@ public SessionHandler(final String entityName, final Consumer onRemoteS @Override public void onSessionLocalOpen(Event e) { - if (onRemoteSessionOpenError != null) { + if (this.onRemoteSessionOpenError != null) { ReactorHandler reactorHandler = null; final Reactor reactor = e.getReactor(); @@ -71,9 +72,12 @@ public void onSessionLocalOpen(Event e) } session.close(); - onRemoteSessionOpenError.accept(new ErrorCondition( - Symbol.getSymbol("amqp:reactorDispatcher:faulted"), - String.format("underlying IO of reactorDispatcher faulted with error: %s", ignore.getMessage()))); + this.onRemoteSessionOpenError.accept( + null, + new ServiceBusException( + false, + String.format("underlying IO of reactorDispatcher faulted with error: %s", ignore.getMessage()), + ignore)); } } } @@ -126,7 +130,7 @@ public void onSessionRemoteClose(Event e) this.sessionOpenErrorDispatched = true; if (!sessionCreated && this.onRemoteSessionOpenError != null) - this.onRemoteSessionOpenError.accept(session.getRemoteCondition()); + this.onRemoteSessionOpenError.accept(session.getRemoteCondition(), null); } @Override @@ -159,7 +163,7 @@ public void onEvent() { if (connection.getRemoteCondition() != null && connection.getRemoteCondition().getCondition() != null) { session.close(); - onRemoteSessionOpenError.accept(connection.getRemoteCondition()); + onRemoteSessionOpenError.accept(connection.getRemoteCondition(), null); return; } @@ -167,13 +171,13 @@ public void onEvent() { if (transport != null && transport.getCondition() != null && transport.getCondition().getCondition() != null) { session.close(); - onRemoteSessionOpenError.accept(transport.getCondition()); + onRemoteSessionOpenError.accept(transport.getCondition(), null); return; } } session.close(); - onRemoteSessionOpenError.accept(new ErrorCondition(Symbol.getSymbol("amqp:session:open-failed"), "session creation timedout.")); + onRemoteSessionOpenError.accept(null, new ServiceBusException(false, "session creation timedout.")); } } } diff --git a/azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java b/azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java new file mode 100644 index 000000000..303bd36a0 --- /dev/null +++ b/azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE file in the project root for full license information. + */ +package com.microsoft.azure.eventhubs.sendrecv; + +import java.time.Instant; +import java.util.Iterator; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.microsoft.azure.eventhubs.EventData; +import com.microsoft.azure.eventhubs.EventHubClient; +import com.microsoft.azure.eventhubs.PartitionReceiver; +import com.microsoft.azure.eventhubs.PartitionSender; +import com.microsoft.azure.eventhubs.lib.ApiTestBase; +import com.microsoft.azure.eventhubs.lib.TestBase; +import com.microsoft.azure.eventhubs.lib.TestContext; +import com.microsoft.azure.servicebus.amqp.AmqpConstants; +import com.microsoft.azure.servicebus.ConnectionStringBuilder; +import com.microsoft.azure.servicebus.IteratorUtil; +import com.microsoft.azure.servicebus.ServiceBusException; + +public class ReceiveParallelManualTest extends ApiTestBase +{ + static final String cgName = TestContext.getConsumerGroupName(); + static final String partitionId = "0"; + + static EventHubClient ehClient; + + @BeforeClass + public static void initializeEventHub() throws Exception + { + FileHandler fhc = new FileHandler("c:\\proton-sb-sendbatch-1100.log", false); + Logger lc1 = Logger.getLogger("servicebus.trace"); + fhc.setFormatter(new SimpleFormatter()); + lc1.addHandler(fhc); + lc1.setLevel(Level.FINE); + + final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString()); + + } + + class PRunnable implements Runnable{ + final String sPartitionId; + + PRunnable(final String sPartitionId) { + this.sPartitionId = sPartitionId; + } + @Override + public void run() { + + try { + TestBase.pushEventsToPartition(ehClient, sPartitionId, 25000).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (ServiceBusException e) { + e.printStackTrace(); + } + + PartitionReceiver offsetReceiver1 = null; + try { + offsetReceiver1 = ehClient.createReceiverSync(cgName, sPartitionId, PartitionReceiver.START_OF_STREAM, false); + } catch (ServiceBusException e) { + e.printStackTrace(); + } + + Iterable receivedEvents; + long totalEvents = 0L; + while (true) { + try { + if ((receivedEvents = offsetReceiver1.receiveSync(10)) != null && !IteratorUtil.sizeEquals(receivedEvents, 0)) { + + long batchSize = (1 + IteratorUtil.getLast(receivedEvents.iterator()).getSystemProperties().getSequenceNumber()) - + (IteratorUtil.getFirst(receivedEvents).getSystemProperties().getSequenceNumber()); + totalEvents += batchSize; + System.out.println(String.format("[partitionId: %s] received %s events; total sofar: %s, begin: %s, end: %s", + sPartitionId, + batchSize, + totalEvents, + IteratorUtil.getLast(receivedEvents.iterator()).getSystemProperties().getSequenceNumber(), + IteratorUtil.getFirst(receivedEvents).getSystemProperties().getSequenceNumber())); + } + else { + System.out.println(String.format("received null on partition %s", sPartitionId)); + } + } catch (Exception exp) { + System.out.println(exp.getMessage() + exp.toString()); + } + + try { + Thread.sleep(150); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + // Run this test manually and introduce network failures to test + // send/receive code is resilient to n/w failures + // and continues to run once the n/w is back online + // @Test() + public void testReceiverStartOfStreamFilters() throws Exception + { + new Thread(new PRunnable("0")).start(); + new Thread(new PRunnable("1")).start(); + new Thread(new PRunnable("2")).start(); + new Thread(new PRunnable("3")).start(); + System.in.read(); + } + + @AfterClass() + public static void cleanup() throws ServiceBusException + { + if (ehClient != null) + { + ehClient.closeSync(); + } + } +} diff --git a/pom.xml b/pom.xml index 69a7388d0..7415a3d69 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ 0.18.0 4.12 - 0.13.0 + 0.13.1 diff --git a/readme.md b/readme.md index dd4aa8fb6..074edd667 100644 --- a/readme.md +++ b/readme.md @@ -147,7 +147,7 @@ the required version of Apache Qpid Proton-J, and the crytography library BCPKIX com.microsoft.azure azure-eventhubs - 0.13.0 + 0.13.1 ``` From d0b14e6287918a1934145e5795d6db3b29af6cd1 Mon Sep 17 00:00:00 2001 From: Sreeram Kumar Garlapati Date: Thu, 6 Apr 2017 16:48:25 -0700 Subject: [PATCH 3/4] format javaclient sourcecode (#91) # Conflicts: # azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java --- .../microsoft/azure/eventhubs/EventData.java | 826 +++--- .../azure/eventhubs/EventDataUtil.java | 98 +- .../azure/eventhubs/EventHubClient.java | 2606 ++++++++--------- .../eventhubs/EventHubRuntimeInformation.java | 23 +- .../eventhubs/PartitionReceiveHandler.java | 66 +- .../azure/eventhubs/PartitionReceiver.java | 855 +++--- .../PartitionRuntimeInformation.java | 48 +- .../azure/eventhubs/PartitionSender.java | 394 ++- .../azure/eventhubs/ReceivePump.java | 171 +- .../azure/eventhubs/ReceiverOptions.java | 12 +- .../eventhubs/ReceiverRuntimeInformation.java | 39 +- .../servicebus/ActiveClientTokenManager.java | 35 +- .../AuthorizationFailedException.java | 47 +- .../azure/servicebus/CBSChannel.java | 148 +- .../azure/servicebus/ClientConstants.java | 201 +- .../azure/servicebus/ClientEntity.java | 91 +- .../servicebus/CommunicationException.java | 34 +- .../servicebus/ConnectionStringBuilder.java | 772 +++-- .../azure/servicebus/ErrorContext.java | 28 +- .../azure/servicebus/ExceptionUtil.java | 252 +- .../azure/servicebus/FaultTolerantObject.java | 57 +- .../azure/servicebus/IConnectionFactory.java | 5 +- .../servicebus/IErrorContextProvider.java | 5 +- .../servicebus/IReceiverSettingsProvider.java | 11 +- .../azure/servicebus/ISessionProvider.java | 11 +- .../servicebus/ITimeoutErrorHandler.java | 7 +- ...llegalConnectionStringFormatException.java | 31 +- .../servicebus/IllegalEntityException.java | 35 +- .../azure/servicebus/IteratorUtil.java | 96 +- .../azure/servicebus/MessageReceiver.java | 1087 ++++--- .../azure/servicebus/MessageSender.java | 1636 +++++------ .../azure/servicebus/MessagingFactory.java | 778 +++-- .../OperationCancelledException.java | 33 +- .../microsoft/azure/servicebus/PassByRef.java | 6 +- .../PayloadSizeExceededException.java | 38 +- .../servicebus/QuotaExceededException.java | 8 +- .../azure/servicebus/ReceiverContext.java | 120 +- .../ReceiverDisconnectedException.java | 38 +- .../azure/servicebus/ReplayableWorkItem.java | 119 +- .../azure/servicebus/RetryExponential.java | 110 +- .../azure/servicebus/RetryPolicy.java | 175 +- .../azure/servicebus/SenderContext.java | 104 +- .../azure/servicebus/ServerBusyException.java | 40 +- .../SharedAccessSignatureTokenProvider.java | 125 +- .../azure/servicebus/StringUtil.java | 42 +- .../azure/servicebus/TimeoutException.java | 40 +- .../azure/servicebus/TimeoutTracker.java | 86 +- .../com/microsoft/azure/servicebus/Timer.java | 98 +- .../microsoft/azure/servicebus/TimerType.java | 7 +- .../azure/servicebus/TrackingUtil.java | 53 +- .../microsoft/azure/servicebus/WorkItem.java | 37 +- .../azure/servicebus/amqp/AmqpConstants.java | 111 +- .../azure/servicebus/amqp/AmqpErrorCode.java | 29 +- .../azure/servicebus/amqp/AmqpException.java | 25 +- .../servicebus/amqp/AmqpResponseCode.java | 37 +- .../azure/servicebus/amqp/AmqpUtil.java | 200 +- .../servicebus/amqp/BaseLinkHandler.java | 158 +- .../servicebus/amqp/ConnectionHandler.java | 214 +- .../servicebus/amqp/CustomIOHandler.java | 29 +- .../servicebus/amqp/DispatchHandler.java | 15 +- .../servicebus/amqp/IAmqpConnection.java | 11 +- .../azure/servicebus/amqp/IAmqpLink.java | 15 +- .../azure/servicebus/amqp/IAmqpReceiver.java | 5 +- .../azure/servicebus/amqp/IAmqpSender.java | 7 +- .../azure/servicebus/amqp/IOperation.java | 2 +- .../servicebus/amqp/IOperationResult.java | 6 +- .../azure/servicebus/amqp/ProtonUtil.java | 17 +- .../servicebus/amqp/ReactorDispatcher.java | 235 +- .../azure/servicebus/amqp/ReactorHandler.java | 21 +- .../servicebus/amqp/ReceiveLinkHandler.java | 146 +- .../amqp/RequestResponseChannel.java | 99 +- .../servicebus/amqp/SendLinkHandler.java | 152 +- .../azure/servicebus/amqp/SessionHandler.java | 281 +- 73 files changed, 6314 insertions(+), 7285 deletions(-) diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java index 218b1fbf9..70f16c42b 100755 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java @@ -45,421 +45,425 @@ * Section (iii) is used for advanced scenarios, where the sending application uses third-party AMQP library to send the message to EventHubs and the receiving application * uses this client library to receive {@link EventData}. */ -public class EventData implements Serializable -{ - private static final long serialVersionUID = -5631628195600014255L; - private static final int BODY_DATA_NULL = -1; - - transient private Binary bodyData; - transient private Object amqpBody; - - private Map properties; - private SystemProperties systemProperties; - - private EventData() - { - } - - /** - * Internal Constructor - intended to be used only by the {@link PartitionReceiver} to Create #EventData out of #Message - */ - @SuppressWarnings("unchecked") - EventData(Message amqpMessage) - { - if (amqpMessage == null) - { - throw new IllegalArgumentException("amqpMessage cannot be null"); - } - - final Map messageAnnotations = amqpMessage.getMessageAnnotations().getValue(); - final HashMap receiveProperties = new HashMap<>(); - - for (Map.Entry annotation: messageAnnotations.entrySet()) - { - receiveProperties.put(annotation.getKey().toString(), annotation.getValue() != null ? annotation.getValue() : null); - } - - if (amqpMessage.getProperties() != null) - { - if (amqpMessage.getMessageId() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_MESSAGE_ID, amqpMessage.getMessageId()); - if (amqpMessage.getUserId() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_USER_ID, amqpMessage.getUserId()); - if (amqpMessage.getAddress() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_TO, amqpMessage.getAddress()); - if (amqpMessage.getSubject() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_SUBJECT, amqpMessage.getSubject()); - if (amqpMessage.getReplyTo() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO, amqpMessage.getReplyTo()); - if (amqpMessage.getCorrelationId() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CORRELATION_ID, amqpMessage.getCorrelationId()); - if (amqpMessage.getContentType() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE, amqpMessage.getContentType()); - if (amqpMessage.getContentEncoding() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING, amqpMessage.getContentEncoding()); - if (amqpMessage.getProperties().getAbsoluteExpiryTime() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME, amqpMessage.getExpiryTime()); - if (amqpMessage.getProperties().getCreationTime() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CREATION_TIME, amqpMessage.getCreationTime()); - if (amqpMessage.getGroupId() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_ID, amqpMessage.getGroupId()); - if (amqpMessage.getProperties().getGroupSequence() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE, amqpMessage.getGroupSequence()); - if (amqpMessage.getReplyToGroupId() != null) receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID, amqpMessage.getReplyToGroupId()); - } - - this.systemProperties = new SystemProperties(receiveProperties); - this.properties = amqpMessage.getApplicationProperties() == null ? null - : ((Map)(amqpMessage.getApplicationProperties().getValue())); - - Section bodySection = amqpMessage.getBody(); - if (bodySection != null) { - if (bodySection instanceof Data) { - this.bodyData = ((Data) bodySection).getValue(); - this.amqpBody = this.bodyData; - } - else if (bodySection instanceof AmqpValue) { - this.amqpBody = ((AmqpValue) bodySection).getValue(); - } - else if (bodySection instanceof AmqpSequence) { - this.amqpBody = ((AmqpSequence) bodySection).getValue(); - } - } - - amqpMessage.clear(); - } - - /** - * Construct EventData to Send to EventHubs. - * Typical pattern to create a Sending EventData is: - *
-	 * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
-	 * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
-	 * 
- *

Sample Code: - *

-	 * EventData eventData = new EventData(telemetryEventBytes);
-	 * eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
-	 * partitionSender.Send(eventData);
-	 * 
- * @param data the actual payload of data in bytes to be Sent to EventHubs. - * @see EventHubClient#createFromConnectionString(String) - */ - public EventData(byte[] data) - { - this(); - - if (data == null) - { - throw new IllegalArgumentException("data cannot be null"); - } - - this.bodyData = new Binary(data); - } - - /** - * Construct EventData to Send to EventHubs. - * Typical pattern to create a Sending EventData is: - *
-	 * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
-	 * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
-	 *  
- *

Illustration: - *

 {@code
-	 *  EventData eventData = new EventData(telemetryEventBytes, offset, length);
-	 *  eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
-	 *  partitionSender.Send(eventData);
-	 *  }
- * @param data the byte[] where the payload of the Event to be sent to EventHubs is present - * @param offset Offset in the byte[] to read from ; inclusive index - * @param length length of the byte[] to be read, starting from offset - * @see EventHubClient#createFromConnectionString(String) - */ - public EventData(byte[] data, final int offset, final int length) - { - this(); - - if (data == null) - { - throw new IllegalArgumentException("data cannot be null"); - } - - this.bodyData = new Binary(data, offset, length); - } - - /** - * Construct EventData to Send to EventHubs. - * Typical pattern to create a Sending EventData is: - *
-	 * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
-	 * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
-	 *  
- *

Illustration: - *

 {@code
-	 *  EventData eventData = new EventData(telemetryEventByteBuffer);
-	 *  eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
-	 *	partitionSender.Send(eventData);
-	 *  }
- * @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs - * @see EventHubClient#createFromConnectionString(String) - */ - public EventData(ByteBuffer buffer) - { - this(); - - if (buffer == null) - { - throw new IllegalArgumentException("data cannot be null"); - } - - this.bodyData = Binary.create(buffer); - } - - /** - * Use this method only if, the sender could be sending messages using third-party AMQP libraries. - *

If all the senders of EventHub use client libraries released and maintained by Microsoft Azure EventHubs, use {@link #getBytes()} method. - *

Get the value of AMQP messages' Body section on the received {@link EventData}. - *

If the AMQP message Body is always guaranteed to have Data section, use {@link #getBytes()} method. - * @return returns the Object which could represent either Data or AmqpValue or AmqpSequence. - *

{@link Binary} if the Body is Data section - *

{@link List} if the Body is AmqpSequence - *

package org.apache.qpid.proton.amqp contains various AMQP types that could be returned. - */ - public Object getObject() - { - return this.amqpBody; +public class EventData implements Serializable { + private static final long serialVersionUID = -5631628195600014255L; + private static final int BODY_DATA_NULL = -1; + + transient private Binary bodyData; + transient private Object amqpBody; + + private Map properties; + private SystemProperties systemProperties; + + private EventData() { + } + + /** + * Internal Constructor - intended to be used only by the {@link PartitionReceiver} to Create #EventData out of #Message + */ + @SuppressWarnings("unchecked") + EventData(Message amqpMessage) { + if (amqpMessage == null) { + throw new IllegalArgumentException("amqpMessage cannot be null"); } - - /** - * Get Actual Payload/Data wrapped by EventData. - * This is the underlying array and should be used in conjunction with {@link #getBodyOffset()} and {@link #getBodyLength()}. - * @return byte[] of the actual data

null if the body of the AMQP message doesn't have Data section - * @deprecated use {@link #getBytes()} - */ - @Deprecated - public byte[] getBody() - { - return this.bodyData == null ? null : this.bodyData.getArray(); - } - - /** - * Get the offset of the current Payload/Data in the byte array returned by {@link #getBody()}. - * @return returns the byte[] of the actual data - * @see #getBodyLength() - * @see #getBody() - * @deprecated use {@link #getBytes()} - */ - @Deprecated - public int getBodyOffset() - { - return this.bodyData == null ? 0 : this.bodyData.getArrayOffset(); - } - - /** - * Get the length of the Actual Payload/Data in the byte array returned by {@link #getBody()}. - * @return returns the byte[] of the actual data - * @see #getBody() - * @see #getBodyOffset() - * @deprecated use {@link #getBytes()} - */ - @Deprecated - public int getBodyLength() - { - return this.bodyData == null ? 0 : this.bodyData.getLength(); - } - - /** - * Get Actual Payload/Data wrapped by EventData. - * @return byte[] of the actual data - *

null if the body of the message has other inter-operable AMQP messages, whose body does not represent byte[]. - * In that case use {@link #getObject()}. - */ - public byte[] getBytes() { - - if (this.bodyData == null) - return null; - - return this.bodyData.getArray(); + + final Map messageAnnotations = amqpMessage.getMessageAnnotations().getValue(); + final HashMap receiveProperties = new HashMap<>(); + + for (Map.Entry annotation : messageAnnotations.entrySet()) { + receiveProperties.put(annotation.getKey().toString(), annotation.getValue() != null ? annotation.getValue() : null); } - /** - * Application property bag - * @return returns Application properties - */ - public Map getProperties() - { - if (this.properties == null) - { - this.properties = new HashMap<>(); - } - - return this.properties; - } - - /** - * Set Application Properties - * @param applicationProperties the Application Properties bag - * @deprecated use {@link #getProperties()} and add properties to the bag. - */ - @Deprecated - public void setProperties(final Map applicationProperties) - { - this.properties = applicationProperties; - } - - /** - * SystemProperties that are populated by EventHubService. - *

As these are populated by Service, they are only present on a Received EventData. - *

Usage:

- * - * final String offset = eventData.getSystemProperties().getOffset(); - * - * @return an encapsulation of all SystemProperties appended by EventHubs service into EventData. - * null if the {@link #EventData()} is not received and is created by the public constructors. - * @see SystemProperties#getOffset - * @see SystemProperties#getSequenceNumber - * @see SystemProperties#getPartitionKey - * @see SystemProperties#getEnqueuedTime - */ - public SystemProperties getSystemProperties() - { - return this.systemProperties; - } - - // This is intended to be used while sending EventData - so EventData.SystemProperties will not be copied over to the AmqpMessage - Message toAmqpMessage() - { - final Message amqpMessage = Proton.message(); - - if (this.properties != null && !this.properties.isEmpty()) - { - final ApplicationProperties applicationProperties = new ApplicationProperties(this.properties); - amqpMessage.setApplicationProperties(applicationProperties); - } - - if (this.systemProperties != null && !this.systemProperties.isEmpty()) - { - for(Map.Entry systemProperty: this.systemProperties.entrySet()) - { - final String propertyName = systemProperty.getKey(); - if (!EventDataUtil.RESERVED_SYSTEM_PROPERTIES.contains(propertyName)) - { - if (AmqpConstants.RESERVED_PROPERTY_NAMES.contains(propertyName)) - switch (propertyName) - { - case AmqpConstants.AMQP_PROPERTY_MESSAGE_ID: amqpMessage.setMessageId(systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_USER_ID: amqpMessage.setUserId((byte[]) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_TO: amqpMessage.setAddress((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_SUBJECT: amqpMessage.setSubject((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_REPLY_TO: amqpMessage.setReplyTo((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_CORRELATION_ID: amqpMessage.setCorrelationId(systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE: amqpMessage.setContentType((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING: amqpMessage.setContentEncoding((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME: amqpMessage.setExpiryTime((long) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_CREATION_TIME: amqpMessage.setCreationTime((long) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_GROUP_ID: amqpMessage.setGroupId((String) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE: amqpMessage.setGroupSequence((long) systemProperty.getValue()); break; - case AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID: amqpMessage.setReplyToGroupId((String) systemProperty.getValue()); break; - default: throw new RuntimeException("unreachable"); - } - else - { - final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null) - ? new MessageAnnotations(new HashMap<>()) - : amqpMessage.getMessageAnnotations(); - messageAnnotations.getValue().put(Symbol.getSymbol(systemProperty.getKey()), systemProperty.getValue()); - amqpMessage.setMessageAnnotations(messageAnnotations); - } - } - } - } - - if (this.bodyData != null) - { - amqpMessage.setBody(new Data(this.bodyData)); - } - else if (this.amqpBody != null) - { - if (this.amqpBody instanceof List) - { - amqpMessage.setBody(new AmqpSequence((List) this.amqpBody)); - } - else - { - amqpMessage.setBody(new AmqpValue(this.amqpBody)); + if (amqpMessage.getProperties() != null) { + if (amqpMessage.getMessageId() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_MESSAGE_ID, amqpMessage.getMessageId()); + if (amqpMessage.getUserId() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_USER_ID, amqpMessage.getUserId()); + if (amqpMessage.getAddress() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_TO, amqpMessage.getAddress()); + if (amqpMessage.getSubject() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_SUBJECT, amqpMessage.getSubject()); + if (amqpMessage.getReplyTo() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO, amqpMessage.getReplyTo()); + if (amqpMessage.getCorrelationId() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CORRELATION_ID, amqpMessage.getCorrelationId()); + if (amqpMessage.getContentType() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE, amqpMessage.getContentType()); + if (amqpMessage.getContentEncoding() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING, amqpMessage.getContentEncoding()); + if (amqpMessage.getProperties().getAbsoluteExpiryTime() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME, amqpMessage.getExpiryTime()); + if (amqpMessage.getProperties().getCreationTime() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_CREATION_TIME, amqpMessage.getCreationTime()); + if (amqpMessage.getGroupId() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_ID, amqpMessage.getGroupId()); + if (amqpMessage.getProperties().getGroupSequence() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE, amqpMessage.getGroupSequence()); + if (amqpMessage.getReplyToGroupId() != null) + receiveProperties.put(AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID, amqpMessage.getReplyToGroupId()); + } + + this.systemProperties = new SystemProperties(receiveProperties); + this.properties = amqpMessage.getApplicationProperties() == null ? null + : ((Map) (amqpMessage.getApplicationProperties().getValue())); + + Section bodySection = amqpMessage.getBody(); + if (bodySection != null) { + if (bodySection instanceof Data) { + this.bodyData = ((Data) bodySection).getValue(); + this.amqpBody = this.bodyData; + } else if (bodySection instanceof AmqpValue) { + this.amqpBody = ((AmqpValue) bodySection).getValue(); + } else if (bodySection instanceof AmqpSequence) { + this.amqpBody = ((AmqpSequence) bodySection).getValue(); + } + } + + amqpMessage.clear(); + } + + /** + * Construct EventData to Send to EventHubs. + * Typical pattern to create a Sending EventData is: + *

+     * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
+     * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
+     * 
+ *

Sample Code: + *

+     * EventData eventData = new EventData(telemetryEventBytes);
+     * eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
+     * partitionSender.Send(eventData);
+     * 
+ * + * @param data the actual payload of data in bytes to be Sent to EventHubs. + * @see EventHubClient#createFromConnectionString(String) + */ + public EventData(byte[] data) { + this(); + + if (data == null) { + throw new IllegalArgumentException("data cannot be null"); + } + + this.bodyData = new Binary(data); + } + + /** + * Construct EventData to Send to EventHubs. + * Typical pattern to create a Sending EventData is: + *
+     * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
+     * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
+     *  
+ *

Illustration: + *

 {@code
+     *  EventData eventData = new EventData(telemetryEventBytes, offset, length);
+     *  eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
+     *  partitionSender.Send(eventData);
+     *  }
+ * + * @param data the byte[] where the payload of the Event to be sent to EventHubs is present + * @param offset Offset in the byte[] to read from ; inclusive index + * @param length length of the byte[] to be read, starting from offset + * @see EventHubClient#createFromConnectionString(String) + */ + public EventData(byte[] data, final int offset, final int length) { + this(); + + if (data == null) { + throw new IllegalArgumentException("data cannot be null"); + } + + this.bodyData = new Binary(data, offset, length); + } + + /** + * Construct EventData to Send to EventHubs. + * Typical pattern to create a Sending EventData is: + *
+     * i.	Serialize the sending ApplicationEvent to be sent to EventHubs into bytes.
+     * ii.	If complex serialization logic is involved (for example: multiple types of data) - add a Hint using the {@link #getProperties()} for the Consumer.
+     *  
+ *

Illustration: + *

 {@code
+     *  EventData eventData = new EventData(telemetryEventByteBuffer);
+     *  eventData.getProperties().put("eventType", "com.microsoft.azure.monitoring.EtlEvent");
+     * 	partitionSender.Send(eventData);
+     *  }
+ * + * @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs + * @see EventHubClient#createFromConnectionString(String) + */ + public EventData(ByteBuffer buffer) { + this(); + + if (buffer == null) { + throw new IllegalArgumentException("data cannot be null"); + } + + this.bodyData = Binary.create(buffer); + } + + /** + * Use this method only if, the sender could be sending messages using third-party AMQP libraries. + *

If all the senders of EventHub use client libraries released and maintained by Microsoft Azure EventHubs, use {@link #getBytes()} method. + *

Get the value of AMQP messages' Body section on the received {@link EventData}. + *

If the AMQP message Body is always guaranteed to have Data section, use {@link #getBytes()} method. + * + * @return returns the Object which could represent either Data or AmqpValue or AmqpSequence. + *

{@link Binary} if the Body is Data section + *

{@link List} if the Body is AmqpSequence + *

package org.apache.qpid.proton.amqp contains various AMQP types that could be returned. + */ + public Object getObject() { + return this.amqpBody; + } + + /** + * Get Actual Payload/Data wrapped by EventData. + * This is the underlying array and should be used in conjunction with {@link #getBodyOffset()} and {@link #getBodyLength()}. + * + * @return byte[] of the actual data

null if the body of the AMQP message doesn't have Data section + * @deprecated use {@link #getBytes()} + */ + @Deprecated + public byte[] getBody() { + return this.bodyData == null ? null : this.bodyData.getArray(); + } + + /** + * Get the offset of the current Payload/Data in the byte array returned by {@link #getBody()}. + * + * @return returns the byte[] of the actual data + * @see #getBodyLength() + * @see #getBody() + * @deprecated use {@link #getBytes()} + */ + @Deprecated + public int getBodyOffset() { + return this.bodyData == null ? 0 : this.bodyData.getArrayOffset(); + } + + /** + * Get the length of the Actual Payload/Data in the byte array returned by {@link #getBody()}. + * + * @return returns the byte[] of the actual data + * @see #getBody() + * @see #getBodyOffset() + * @deprecated use {@link #getBytes()} + */ + @Deprecated + public int getBodyLength() { + return this.bodyData == null ? 0 : this.bodyData.getLength(); + } + + /** + * Get Actual Payload/Data wrapped by EventData. + * + * @return byte[] of the actual data + *

null if the body of the message has other inter-operable AMQP messages, whose body does not represent byte[]. + * In that case use {@link #getObject()}. + */ + public byte[] getBytes() { + + if (this.bodyData == null) + return null; + + return this.bodyData.getArray(); + } + + /** + * Application property bag + * + * @return returns Application properties + */ + public Map getProperties() { + if (this.properties == null) { + this.properties = new HashMap<>(); + } + + return this.properties; + } + + /** + * Set Application Properties + * + * @param applicationProperties the Application Properties bag + * @deprecated use {@link #getProperties()} and add properties to the bag. + */ + @Deprecated + public void setProperties(final Map applicationProperties) { + this.properties = applicationProperties; + } + + /** + * SystemProperties that are populated by EventHubService. + *

As these are populated by Service, they are only present on a Received EventData. + *

Usage:

+ * + * final String offset = eventData.getSystemProperties().getOffset(); + * + * + * @return an encapsulation of all SystemProperties appended by EventHubs service into EventData. + * null if the {@link #EventData()} is not received and is created by the public constructors. + * @see SystemProperties#getOffset + * @see SystemProperties#getSequenceNumber + * @see SystemProperties#getPartitionKey + * @see SystemProperties#getEnqueuedTime + */ + public SystemProperties getSystemProperties() { + return this.systemProperties; + } + + // This is intended to be used while sending EventData - so EventData.SystemProperties will not be copied over to the AmqpMessage + Message toAmqpMessage() { + final Message amqpMessage = Proton.message(); + + if (this.properties != null && !this.properties.isEmpty()) { + final ApplicationProperties applicationProperties = new ApplicationProperties(this.properties); + amqpMessage.setApplicationProperties(applicationProperties); + } + + if (this.systemProperties != null && !this.systemProperties.isEmpty()) { + for (Map.Entry systemProperty : this.systemProperties.entrySet()) { + final String propertyName = systemProperty.getKey(); + if (!EventDataUtil.RESERVED_SYSTEM_PROPERTIES.contains(propertyName)) { + if (AmqpConstants.RESERVED_PROPERTY_NAMES.contains(propertyName)) + switch (propertyName) { + case AmqpConstants.AMQP_PROPERTY_MESSAGE_ID: + amqpMessage.setMessageId(systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_USER_ID: + amqpMessage.setUserId((byte[]) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_TO: + amqpMessage.setAddress((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_SUBJECT: + amqpMessage.setSubject((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_REPLY_TO: + amqpMessage.setReplyTo((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_CORRELATION_ID: + amqpMessage.setCorrelationId(systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_CONTENT_TYPE: + amqpMessage.setContentType((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_CONTENT_ENCODING: + amqpMessage.setContentEncoding((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME: + amqpMessage.setExpiryTime((long) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_CREATION_TIME: + amqpMessage.setCreationTime((long) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_GROUP_ID: + amqpMessage.setGroupId((String) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_GROUP_SEQUENCE: + amqpMessage.setGroupSequence((long) systemProperty.getValue()); + break; + case AmqpConstants.AMQP_PROPERTY_REPLY_TO_GROUP_ID: + amqpMessage.setReplyToGroupId((String) systemProperty.getValue()); + break; + default: + throw new RuntimeException("unreachable"); + } + else { + final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null) + ? new MessageAnnotations(new HashMap<>()) + : amqpMessage.getMessageAnnotations(); + messageAnnotations.getValue().put(Symbol.getSymbol(systemProperty.getKey()), systemProperty.getValue()); + amqpMessage.setMessageAnnotations(messageAnnotations); } } + } + } - return amqpMessage; - } - - Message toAmqpMessage(final String partitionKey) - { - final Message amqpMessage = this.toAmqpMessage(); - - final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null) - ? new MessageAnnotations(new HashMap<>()) - : amqpMessage.getMessageAnnotations(); - messageAnnotations.getValue().put(AmqpConstants.PARTITION_KEY, partitionKey); - amqpMessage.setMessageAnnotations(messageAnnotations); - - return amqpMessage; - } - - private void writeObject(ObjectOutputStream out) throws IOException - { - out.defaultWriteObject(); - - out.writeInt(this.bodyData == null ? BODY_DATA_NULL : this.bodyData.getLength()); - if (this.bodyData != null) - out.write(this.bodyData.getArray(), this.bodyData.getArrayOffset(), this.bodyData.getLength()); - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException - { - in.defaultReadObject(); - - final int length = in.readInt(); - if (length != BODY_DATA_NULL) { - - final byte[] data = new byte[length]; - in.readFully(data, 0, length); - this.bodyData = new Binary(data, 0, length); - } - } - - public static class SystemProperties extends HashMap - { - private static final long serialVersionUID = -2827050124966993723L; - - public SystemProperties(final HashMap map) - { - super(Collections.unmodifiableMap(map)); - } - - public String getOffset() - { - return this.getSystemProperty(AmqpConstants.OFFSET_ANNOTATION_NAME); - } - - public String getPartitionKey() - { - return this.getSystemProperty(AmqpConstants.PARTITION_KEY_ANNOTATION_NAME); - } - - public Instant getEnqueuedTime() - { - final Date enqueuedTimeValue = this.getSystemProperty(AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME); - return enqueuedTimeValue != null ? enqueuedTimeValue.toInstant() : null; - } - - public long getSequenceNumber() - { - return this.getSystemProperty(AmqpConstants.SEQUENCE_NUMBER_ANNOTATION_NAME); - } - - public String getPublisher() - { - return this.getSystemProperty(AmqpConstants.PUBLISHER_ANNOTATION_NAME); - } - - @SuppressWarnings("unchecked") - private T getSystemProperty(final String key) - { - if (this.containsKey(key)) - { - return (T) (this.get(key)); - } - - return null; - } - } + if (this.bodyData != null) { + amqpMessage.setBody(new Data(this.bodyData)); + } else if (this.amqpBody != null) { + if (this.amqpBody instanceof List) { + amqpMessage.setBody(new AmqpSequence((List) this.amqpBody)); + } else { + amqpMessage.setBody(new AmqpValue(this.amqpBody)); + } + } + + return amqpMessage; + } + + Message toAmqpMessage(final String partitionKey) { + final Message amqpMessage = this.toAmqpMessage(); + + final MessageAnnotations messageAnnotations = (amqpMessage.getMessageAnnotations() == null) + ? new MessageAnnotations(new HashMap<>()) + : amqpMessage.getMessageAnnotations(); + messageAnnotations.getValue().put(AmqpConstants.PARTITION_KEY, partitionKey); + amqpMessage.setMessageAnnotations(messageAnnotations); + + return amqpMessage; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + + out.writeInt(this.bodyData == null ? BODY_DATA_NULL : this.bodyData.getLength()); + if (this.bodyData != null) + out.write(this.bodyData.getArray(), this.bodyData.getArrayOffset(), this.bodyData.getLength()); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + final int length = in.readInt(); + if (length != BODY_DATA_NULL) { + + final byte[] data = new byte[length]; + in.readFully(data, 0, length); + this.bodyData = new Binary(data, 0, length); + } + } + + public static class SystemProperties extends HashMap { + private static final long serialVersionUID = -2827050124966993723L; + + public SystemProperties(final HashMap map) { + super(Collections.unmodifiableMap(map)); + } + + public String getOffset() { + return this.getSystemProperty(AmqpConstants.OFFSET_ANNOTATION_NAME); + } + + public String getPartitionKey() { + return this.getSystemProperty(AmqpConstants.PARTITION_KEY_ANNOTATION_NAME); + } + + public Instant getEnqueuedTime() { + final Date enqueuedTimeValue = this.getSystemProperty(AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME); + return enqueuedTimeValue != null ? enqueuedTimeValue.toInstant() : null; + } + + public long getSequenceNumber() { + return this.getSystemProperty(AmqpConstants.SEQUENCE_NUMBER_ANNOTATION_NAME); + } + + public String getPublisher() { + return this.getSystemProperty(AmqpConstants.PUBLISHER_ANNOTATION_NAME); + } + + @SuppressWarnings("unchecked") + private T getSystemProperty(final String key) { + if (this.containsKey(key)) { + return (T) (this.get(key)); + } + + return null; + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventDataUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventDataUtil.java index c2209fcf9..ab26d1bfa 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventDataUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventDataUtil.java @@ -20,53 +20,53 @@ * Internal utility class for EventData */ final class EventDataUtil { - - @SuppressWarnings("serial") - static final Set RESERVED_SYSTEM_PROPERTIES = Collections.unmodifiableSet(new HashSet() - {{ - add(AmqpConstants.OFFSET_ANNOTATION_NAME); - add(AmqpConstants.PARTITION_KEY_ANNOTATION_NAME); - add(AmqpConstants.SEQUENCE_NUMBER_ANNOTATION_NAME); - add(AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME); - add(AmqpConstants.PUBLISHER_ANNOTATION_NAME); - }}); - - private EventDataUtil(){} - - static LinkedList toEventDataCollection(final Collection messages, final PassByRef lastMessageRef) { - - if (messages == null) { - return null; - } - - LinkedList events = new LinkedList<>(); - for (Message message : messages) { - - events.add(new EventData(message)); - - if (lastMessageRef != null) - lastMessageRef.set(message); - } - - return events; - } - - static Iterable toAmqpMessages(final Iterable eventDatas, final String partitionKey) { - - final LinkedList messages = new LinkedList<>(); - eventDatas.forEach(new Consumer() { - @Override - public void accept(EventData eventData) { - Message amqpMessage = partitionKey == null ? eventData.toAmqpMessage() : eventData.toAmqpMessage(partitionKey); - messages.add(amqpMessage); - } - }); - - return messages; - } - - static Iterable toAmqpMessages(final Iterable eventDatas) { - - return EventDataUtil.toAmqpMessages(eventDatas, null); - } + + @SuppressWarnings("serial") + static final Set RESERVED_SYSTEM_PROPERTIES = Collections.unmodifiableSet(new HashSet() {{ + add(AmqpConstants.OFFSET_ANNOTATION_NAME); + add(AmqpConstants.PARTITION_KEY_ANNOTATION_NAME); + add(AmqpConstants.SEQUENCE_NUMBER_ANNOTATION_NAME); + add(AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME); + add(AmqpConstants.PUBLISHER_ANNOTATION_NAME); + }}); + + private EventDataUtil() { + } + + static LinkedList toEventDataCollection(final Collection messages, final PassByRef lastMessageRef) { + + if (messages == null) { + return null; + } + + LinkedList events = new LinkedList<>(); + for (Message message : messages) { + + events.add(new EventData(message)); + + if (lastMessageRef != null) + lastMessageRef.set(message); + } + + return events; + } + + static Iterable toAmqpMessages(final Iterable eventDatas, final String partitionKey) { + + final LinkedList messages = new LinkedList<>(); + eventDatas.forEach(new Consumer() { + @Override + public void accept(EventData eventData) { + Message amqpMessage = partitionKey == null ? eventData.toAmqpMessage() : eventData.toAmqpMessage(partitionKey); + messages.add(amqpMessage); + } + }); + + return messages; + } + + static Iterable toAmqpMessages(final Iterable eventDatas) { + + return EventDataUtil.toAmqpMessages(eventDatas, null); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java index 45780d130..f75770ea8 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java @@ -28,1391 +28,1231 @@ /** * Anchor class - all EventHub client operations STARTS here. - * @see EventHubClient#createFromConnectionString(String) + * + * @see EventHubClient#createFromConnectionString(String) */ -public class EventHubClient extends ClientEntity -{ - public static final String DEFAULT_CONSUMER_GROUP_NAME = "$Default"; - - private final String eventHubName; - private final Object senderCreateSync; - - private MessagingFactory underlyingFactory; - private MessageSender sender; - private boolean isSenderCreateStarted; - private CompletableFuture createSender; - - private EventHubClient(final ConnectionStringBuilder connectionString) throws IOException, IllegalEntityException - { - super(StringUtil.getRandomString(), null); - - this.eventHubName = connectionString.getEntityPath(); - this.senderCreateSync = new Object(); +public class EventHubClient extends ClientEntity { + public static final String DEFAULT_CONSUMER_GROUP_NAME = "$Default"; + + private final String eventHubName; + private final Object senderCreateSync; + + private MessagingFactory underlyingFactory; + private MessageSender sender; + private boolean isSenderCreateStarted; + private CompletableFuture createSender; + + private EventHubClient(final ConnectionStringBuilder connectionString) throws IOException, IllegalEntityException { + super(StringUtil.getRandomString(), null); + + this.eventHubName = connectionString.getEntityPath(); + this.senderCreateSync = new Object(); + } + + /** + * Synchronous version of {@link #createFromConnectionString(String)}. + * + * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws ServiceBusException If Service Bus service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static EventHubClient createFromConnectionStringSync(final String connectionString) + throws ServiceBusException, IOException { + return createFromConnectionStringSync(connectionString, null); + } + + /** + * Synchronous version of {@link #createFromConnectionString(String)}. + * + * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. + * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws ServiceBusException If Service Bus service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static EventHubClient createFromConnectionStringSync(final String connectionString, final RetryPolicy retryPolicy) + throws ServiceBusException, IOException { + try { + return createFromConnectionString(connectionString, retryPolicy).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } } - /** - * Synchronous version of {@link #createFromConnectionString(String)}. - * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. - * @return EventHubClient which can be used to create Senders and Receivers to EventHub - * @throws ServiceBusException If Service Bus service encountered problems during connection creation. - * @throws IOException If the underlying Proton-J layer encounter network errors. - */ - public static EventHubClient createFromConnectionStringSync(final String connectionString) - throws ServiceBusException, IOException - { - return createFromConnectionStringSync(connectionString, null); - } - - /** - * Synchronous version of {@link #createFromConnectionString(String)}. - * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. - * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. - * @return EventHubClient which can be used to create Senders and Receivers to EventHub - * @throws ServiceBusException If Service Bus service encountered problems during connection creation. - * @throws IOException If the underlying Proton-J layer encounter network errors. - */ - public static EventHubClient createFromConnectionStringSync(final String connectionString, final RetryPolicy retryPolicy) - throws ServiceBusException, IOException - { - try - { - return createFromConnectionString(connectionString, retryPolicy).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Factory method to create an instance of {@link EventHubClient} using the supplied connectionString. - * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. - * - *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. - * - * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. - * @return EventHubClient which can be used to create Senders and Receivers to EventHub - * @throws ServiceBusException If Service Bus service encountered problems during connection creation. - * @throws IOException If the underlying Proton-J layer encounter network errors. - */ - public static CompletableFuture createFromConnectionString(final String connectionString) - throws ServiceBusException, IOException - { - return createFromConnectionString(connectionString, null); - } - - /** - * Factory method to create an instance of {@link EventHubClient} using the supplied connectionString. - * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. - * - *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. - * - * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. - * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. - * @return EventHubClient which can be used to create Senders and Receivers to EventHub - * @throws ServiceBusException If Service Bus service encountered problems during connection creation. - * @throws IOException If the underlying Proton-J layer encounter network errors. - */ - public static CompletableFuture createFromConnectionString(final String connectionString, final RetryPolicy retryPolicy) - throws ServiceBusException, IOException - { - final ConnectionStringBuilder connStr = new ConnectionStringBuilder(connectionString); - final EventHubClient eventHubClient = new EventHubClient(connStr); - - return MessagingFactory.createFromConnectionString(connectionString.toString(), retryPolicy) - .thenApplyAsync(new Function() - { - @Override - public EventHubClient apply(MessagingFactory factory) - { - eventHubClient.underlyingFactory = factory; - return eventHubClient; - } - }); - } - - /** - * Synchronous version of {@link #send(EventData)}. - * @param data the {@link EventData} to be sent. - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a predefined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) - */ - public final void sendSync(final EventData data) - throws ServiceBusException - { - try - { - this.send(data).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send {@link EventData} to EventHub. The sent {@link EventData} will land on any arbitrarily chosen EventHubs partition. - * - *

There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): - *

    - *
  • {@link #send(EventData)} or {@link #send(Iterable)} - *
  • {@link #send(EventData, String)} or {@link #send(Iterable, String)} - *
  • {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)} - *
- *

Use this method to Send, if: - *

-	 * a)  the send({@link EventData}) operation should be highly available and 
-	 * b)  the data needs to be evenly distributed among all partitions; exception being, when a subset of partitions are unavailable
-	 * 
- * - * {@link #send(EventData)} send's the {@link EventData} to a Service Gateway, which in-turn will forward the {@link EventData} to one of the EventHubs' partitions. Here's the message forwarding algorithm: - *
-	 * i.  Forward the {@link EventData}'s to EventHub partitions, by equally distributing the data among all partitions (ex: Round-robin the {@link EventData}'s to all EventHubs' partitions) 
-	 * ii. If one of the EventHub partitions is unavailable for a moment, the Service Gateway will automatically detect it and forward the message to another available partition - making the Send operation highly-available.
-	 * 
- * @param data the {@link EventData} to be sent. - * @return a CompletableFuture that can be completed when the send operations is done.. - * @see #send(EventData, String) - * @see PartitionSender#send(EventData) - */ - public final CompletableFuture send(final EventData data) - { - if (data == null) - { - throw new IllegalArgumentException("EventData cannot be empty."); - } - - return this.createInternalSender().thenComposeAsync(new Function>() - { - @Override - public CompletableFuture apply(Void voidArg) - { - return EventHubClient.this.sender.send(data.toAmqpMessage()); - } - }); - } - - /** - * Synchronous version of {@link #send(Iterable)}. - * @param eventDatas batch of events to send to EventHub - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) - */ - public final void sendSync(final Iterable eventDatas) - throws ServiceBusException - { - try - { - this.send(eventDatas).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send a batch of {@link EventData} to EventHub. The sent {@link EventData} will land on any arbitrarily chosen EventHubs partition. - * This is the most recommended way to Send to EventHubs. - * - *

There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData)}, which is used to send single {@link EventData}. - * Use this overload versus {@link #send(EventData)}, if you need to send a batch of {@link EventData}. - * - *

Sending a batch of {@link EventData}'s is useful in the following cases: - *

-	 * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs' service.
-	 * ii.	Send multiple {@link EventData}'s in a Transaction. To achieve ACID properties, the Gateway Service will forward all {@link EventData}'s in the batch to a single EventHubs' partition.
-	 * 
- *

- * Sample code (sample uses sync version of the api but concept are identical): - *

       
-	 * Gson gson = new GsonBuilder().create();
-	 * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
-	 *         
-	 * while (true)
-	 * {
-	 *     LinkedList{@literal<}EventData{@literal>} events = new LinkedList{@literal<}EventData{@literal>}();}
-	 *     for (int count = 1; count {@literal<} 11; count++)
-	 *     {
-	 *         PayloadEvent payload = new PayloadEvent(count);
-	 *         byte[] payloadBytes = gson.toJson(payload).getBytes(Charset.defaultCharset());
-	 *         EventData sendEvent = new EventData(payloadBytes);
-	 *         Map{@literal<}String, String{@literal>} applicationProperties = new HashMap{@literal<}String, String{@literal>}();
-	 *         applicationProperties.put("from", "javaClient");
-	 *         sendEvent.setProperties(applicationProperties);
-	 *         events.add(sendEvent);
-	 *     }
-	 *         
-	 *     client.sendSync(events);
-	 *     System.out.println(String.format("Sent Batch... Size: %s", events.size()));
-	 * }
-	 * 
- * - *

for Exceptions refer to {@link #sendSync(Iterable)} - * - * @param eventDatas batch of events to send to EventHub - * @return a CompletableFuture that can be completed when the send operations is done.. - * @see #send(EventData, String) - * @see PartitionSender#send(EventData) - */ - public final CompletableFuture send(final Iterable eventDatas) - { - if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) - { - throw new IllegalArgumentException("Empty batch of EventData cannot be sent."); - } - - return this.createInternalSender().thenComposeAsync(new Function>() - { - @Override - public CompletableFuture apply(Void voidArg) - { - return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas)); - } - }); - } - - /** - * Synchronous version of {@link #send(EventData, String)}. - * @param eventData the {@link EventData} to be sent. - * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final void sendSync(final EventData eventData, final String partitionKey) - throws ServiceBusException - { - try - { - this.send(eventData, partitionKey).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send an '{@link EventData} with a partitionKey' to EventHub. All {@link EventData}'s with a partitionKey are guaranteed to land on the same partition. - * This send pattern emphasize data correlation over general availability and latency. - *

- * There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): - *

-	 * i.   {@link #send(EventData)} or {@link #send(Iterable)}
-	 * ii.  {@link #send(EventData, String)} or {@link #send(Iterable, String)}
-	 * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
-	 * 
- * - * Use this type of Send, if: - *
-	 * i.  There is a need for correlation of events based on Sender instance; The sender can generate a UniqueId and set it as partitionKey - which on the received Message can be used for correlation
-	 * ii. The client wants to take control of distribution of data across partitions.
-	 * 
- *

- * Multiple PartitionKey's could be mapped to one Partition. EventHubs service uses a proprietary Hash algorithm to map the PartitionKey to a PartitionId. - * Using this type of Send (Sending using a specific partitionKey), could sometimes result in partitions which are not evenly distributed. - * - * @param eventData the {@link EventData} to be sent. - * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} - * @return a CompletableFuture that can be completed when the send operations is done.. - * @see #send(EventData) - * @see PartitionSender#send(EventData) - */ - public final CompletableFuture send(final EventData eventData, final String partitionKey) - { - if (eventData == null) - { - throw new IllegalArgumentException("EventData cannot be null."); - } - - if (partitionKey == null) - { - throw new IllegalArgumentException("partitionKey cannot be null"); - } - - return this.createInternalSender().thenComposeAsync(new Function>() - { - @Override - public CompletableFuture apply(Void voidArg) - { - return EventHubClient.this.sender.send(eventData.toAmqpMessage(partitionKey)); - } - }); - } - - /** - * Synchronous version of {@link #send(Iterable, String)}. - * @param eventDatas the batch of events to send to EventHub - * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) - */ - public final void sendSync(final Iterable eventDatas, final String partitionKey) - throws ServiceBusException - { - try - { - this.send(eventDatas, partitionKey).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send a 'batch of {@link EventData} with the same partitionKey' to EventHub. All {@link EventData}'s with a partitionKey are guaranteed to land on the same partition. - * Multiple PartitionKey's will be mapped to one Partition. - * - *

There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData, String)}, which is the same type of Send and is used to send single {@link EventData}. - * - *

Sending a batch of {@link EventData}'s is useful in the following cases: - *

-	 * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs service.
-	 * ii.	Send multiple events in One Transaction. This is the reason why all events sent in a batch needs to have same partitionKey (so that they are sent to one partition only).
-	 * 
- * - * @param eventDatas the batch of events to send to EventHub - * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} - * @return a CompletableFuture that can be completed when the send operations is done.. - * @see #send(EventData) - * @see PartitionSender#send(EventData) - */ - public final CompletableFuture send(final Iterable eventDatas, final String partitionKey) - { - if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) - { - throw new IllegalArgumentException("Empty batch of EventData cannot be sent."); - } - - if (partitionKey == null) - { - throw new IllegalArgumentException("partitionKey cannot be null"); - } - - if (partitionKey.length() > ClientConstants.MAX_PARTITION_KEY_LENGTH) - { - throw new IllegalArgumentException( - String.format(Locale.US, "PartitionKey exceeds the maximum allowed length of partitionKey: {0}", ClientConstants.MAX_PARTITION_KEY_LENGTH)); - } - - return this.createInternalSender().thenComposeAsync(new Function>() - { - @Override - public CompletableFuture apply(Void voidArg) - { - return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas, partitionKey)); - } - }); - } - - /** - * Synchronous version of {@link #createPartitionSender(String)}. - * @param partitionId partitionId of EventHub to send the {@link EventData}'s to - * @return PartitionSender which can be used to send events to a specific partition. - * @throws ServiceBusException if Service Bus service encountered problems during connection creation. - */ - public final PartitionSender createPartitionSenderSync(final String partitionId) - throws ServiceBusException, IllegalArgumentException - { - try - { - return this.createPartitionSender(partitionId).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a {@link PartitionSender} which can publish {@link EventData}'s directly to a specific EventHub partition (sender type iii. in the below list). - *

- * There are 3 patterns/ways to send to EventHubs: - *

-	 * i.   {@link #send(EventData)} or {@link #send(Iterable)}
-	 * ii.  {@link #send(EventData, String)} or {@link #send(Iterable, String)}
-	 * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
-	 * 
- * - * @param partitionId partitionId of EventHub to send the {@link EventData}'s to - * @return a CompletableFuture that would result in a PartitionSender when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during connection creation. - * @see PartitionSender - */ - public final CompletableFuture createPartitionSender(final String partitionId) - throws ServiceBusException - { - return PartitionSender.Create(this.underlyingFactory, this.eventHubName, partitionId); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, String)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * The receiver is created for a specific EventHub partition from the specific consumer group. - * - *

NOTE: There can be a maximum number of receivers that can run in parallel per ConsumerGroup per Partition. - * The limit is enforced by the Event Hub service - current limit is 5 receivers in parallel. Having multiple receivers - * reading from offsets that are far apart on the same consumer group / partition combo will have significant performance Impact. - * - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset) - throws ServiceBusException - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, false); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, String, boolean)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive) - throws ServiceBusException - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, null); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, Instant)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, dateTime).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime) - throws ServiceBusException - { - return this.createReceiver(consumerGroupName, partitionId, dateTime, null); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, String)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * The receiver is created for a specific EventHub partition from the specific consumer group. - * - *

NOTE: There can be a maximum number of receivers that can run in parallel per ConsumerGroup per Partition. - * The limit is enforced by the Event Hub service - current limit is 5 receivers in parallel. Having multiple receivers - * reading from offsets that are far apart on the same consumer group / partition combo will have significant performance Impact. - * - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, false, receiverOptions); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, String, boolean)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, null, PartitionReceiver.NULL_EPOCH, false, receiverOptions); - } - - /** - * Synchronous version of {@link #createReceiver(String, String, Instant)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createReceiver(consumerGroupName, partitionId, dateTime, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - */ - public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, null, false, dateTime, PartitionReceiver.NULL_EPOCH, false, receiverOptions); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, String, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, epoch).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch) - throws ServiceBusException - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, false, epoch); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, String, boolean, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch) - throws ServiceBusException - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch, null); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, Instant, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param epoch a unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch) - throws ServiceBusException - { - return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch, null); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, String, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, epoch, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, false, epoch, receiverOptions); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, String, boolean, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} - * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, null, epoch, true, receiverOptions); - } - - /** - * Synchronous version of {@link #createEpochReceiver(String, String, Instant, long)}. - * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return PartitionReceiver instance which can be used for receiving {@link EventData}. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - try - { - return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch, receiverOptions).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. - * The receiver is created for a specific EventHub Partition from the specific consumer group. - *

- * It is important to pay attention to the following when creating epoch based receiver: - *

    - *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. - *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. - *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. - *
- * @param consumerGroupName the consumer group name that this receiver should be grouped under. - * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. - * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. - * @param epoch a unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. - * @param receiverOptions the set of options to enable on the event hubs receiver - * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - * @see PartitionReceiver - * @see ReceiverDisconnectedException - */ - public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch, final ReceiverOptions receiverOptions) - throws ServiceBusException - { - return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, null, false, dateTime, epoch, true, receiverOptions); - } - - @Override - public CompletableFuture onClose() - { - if (this.underlyingFactory != null) - { - synchronized (this.senderCreateSync) - { - final CompletableFuture internalSenderClose = this.sender != null - ? this.sender.close().thenComposeAsync(new Function>() - { - @Override - public CompletableFuture apply(Void voidArg) - { - return EventHubClient.this.underlyingFactory.close(); - } - }) - : this.underlyingFactory.close(); - - return internalSenderClose; + return null; + } + + /** + * Factory method to create an instance of {@link EventHubClient} using the supplied connectionString. + * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. + *

+ *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. + * + * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws ServiceBusException If Service Bus service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static CompletableFuture createFromConnectionString(final String connectionString) + throws ServiceBusException, IOException { + return createFromConnectionString(connectionString, null); + } + + /** + * Factory method to create an instance of {@link EventHubClient} using the supplied connectionString. + * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. + *

+ *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. + * + * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. + * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws ServiceBusException If Service Bus service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static CompletableFuture createFromConnectionString(final String connectionString, final RetryPolicy retryPolicy) + throws ServiceBusException, IOException { + final ConnectionStringBuilder connStr = new ConnectionStringBuilder(connectionString); + final EventHubClient eventHubClient = new EventHubClient(connStr); + + return MessagingFactory.createFromConnectionString(connectionString.toString(), retryPolicy) + .thenApplyAsync(new Function() { + @Override + public EventHubClient apply(MessagingFactory factory) { + eventHubClient.underlyingFactory = factory; + return eventHubClient; } - } - - return CompletableFuture.completedFuture(null); - } - - private CompletableFuture createInternalSender() - { - if (!this.isSenderCreateStarted) - { - synchronized (this.senderCreateSync) - { - if (!this.isSenderCreateStarted) - { - this.createSender = MessageSender.create(this.underlyingFactory, StringUtil.getRandomString(), this.eventHubName) - .thenAcceptAsync(new Consumer() - { - public void accept(MessageSender a) { EventHubClient.this.sender = a;} - }); - - this.isSenderCreateStarted = true; - } - } - } - - return this.createSender; - } + }); + } + + /** + * Synchronous version of {@link #send(EventData)}. + * + * @param data the {@link EventData} to be sent. + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a predefined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) + */ + public final void sendSync(final EventData data) + throws ServiceBusException { + try { + this.send(data).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send {@link EventData} to EventHub. The sent {@link EventData} will land on any arbitrarily chosen EventHubs partition. + *

+ *

There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): + *

    + *
  • {@link #send(EventData)} or {@link #send(Iterable)} + *
  • {@link #send(EventData, String)} or {@link #send(Iterable, String)} + *
  • {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)} + *
+ *

Use this method to Send, if: + *

+     * a)  the send({@link EventData}) operation should be highly available and
+     * b)  the data needs to be evenly distributed among all partitions; exception being, when a subset of partitions are unavailable
+     * 
+ *

+ * {@link #send(EventData)} send's the {@link EventData} to a Service Gateway, which in-turn will forward the {@link EventData} to one of the EventHubs' partitions. Here's the message forwarding algorithm: + *

+     * i.  Forward the {@link EventData}'s to EventHub partitions, by equally distributing the data among all partitions (ex: Round-robin the {@link EventData}'s to all EventHubs' partitions)
+     * ii. If one of the EventHub partitions is unavailable for a moment, the Service Gateway will automatically detect it and forward the message to another available partition - making the Send operation highly-available.
+     * 
+ * + * @param data the {@link EventData} to be sent. + * @return a CompletableFuture that can be completed when the send operations is done.. + * @see #send(EventData, String) + * @see PartitionSender#send(EventData) + */ + public final CompletableFuture send(final EventData data) { + if (data == null) { + throw new IllegalArgumentException("EventData cannot be empty."); + } + + return this.createInternalSender().thenComposeAsync(new Function>() { + @Override + public CompletableFuture apply(Void voidArg) { + return EventHubClient.this.sender.send(data.toAmqpMessage()); + } + }); + } + + /** + * Synchronous version of {@link #send(Iterable)}. + * + * @param eventDatas batch of events to send to EventHub + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) + */ + public final void sendSync(final Iterable eventDatas) + throws ServiceBusException { + try { + this.send(eventDatas).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send a batch of {@link EventData} to EventHub. The sent {@link EventData} will land on any arbitrarily chosen EventHubs partition. + * This is the most recommended way to Send to EventHubs. + *

+ *

There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData)}, which is used to send single {@link EventData}. + * Use this overload versus {@link #send(EventData)}, if you need to send a batch of {@link EventData}. + *

+ *

Sending a batch of {@link EventData}'s is useful in the following cases: + *

+     * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs' service.
+     * ii.	Send multiple {@link EventData}'s in a Transaction. To achieve ACID properties, the Gateway Service will forward all {@link EventData}'s in the batch to a single EventHubs' partition.
+     * 
+ *

+ * Sample code (sample uses sync version of the api but concept are identical): + *

+     * Gson gson = new GsonBuilder().create();
+     * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
+     *
+     * while (true)
+     * {
+     *     LinkedList{@literal<}EventData{@literal>} events = new LinkedList{@literal<}EventData{@literal>}();}
+     *     for (int count = 1; count {@literal<} 11; count++)
+     *     {
+     *         PayloadEvent payload = new PayloadEvent(count);
+     *         byte[] payloadBytes = gson.toJson(payload).getBytes(Charset.defaultCharset());
+     *         EventData sendEvent = new EventData(payloadBytes);
+     *         Map{@literal<}String, String{@literal>} applicationProperties = new HashMap{@literal<}String, String{@literal>}();
+     *         applicationProperties.put("from", "javaClient");
+     *         sendEvent.setProperties(applicationProperties);
+     *         events.add(sendEvent);
+     *     }
+     *
+     *     client.sendSync(events);
+     *     System.out.println(String.format("Sent Batch... Size: %s", events.size()));
+     * }
+     * 
+ *

+ *

for Exceptions refer to {@link #sendSync(Iterable)} + * + * @param eventDatas batch of events to send to EventHub + * @return a CompletableFuture that can be completed when the send operations is done.. + * @see #send(EventData, String) + * @see PartitionSender#send(EventData) + */ + public final CompletableFuture send(final Iterable eventDatas) { + if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) { + throw new IllegalArgumentException("Empty batch of EventData cannot be sent."); + } + + return this.createInternalSender().thenComposeAsync(new Function>() { + @Override + public CompletableFuture apply(Void voidArg) { + return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas)); + } + }); + } + + /** + * Synchronous version of {@link #send(EventData, String)}. + * + * @param eventData the {@link EventData} to be sent. + * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final void sendSync(final EventData eventData, final String partitionKey) + throws ServiceBusException { + try { + this.send(eventData, partitionKey).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send an '{@link EventData} with a partitionKey' to EventHub. All {@link EventData}'s with a partitionKey are guaranteed to land on the same partition. + * This send pattern emphasize data correlation over general availability and latency. + *

+ * There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): + *

+     * i.   {@link #send(EventData)} or {@link #send(Iterable)}
+     * ii.  {@link #send(EventData, String)} or {@link #send(Iterable, String)}
+     * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
+     * 
+ *

+ * Use this type of Send, if: + *

+     * i.  There is a need for correlation of events based on Sender instance; The sender can generate a UniqueId and set it as partitionKey - which on the received Message can be used for correlation
+     * ii. The client wants to take control of distribution of data across partitions.
+     * 
+ *

+ * Multiple PartitionKey's could be mapped to one Partition. EventHubs service uses a proprietary Hash algorithm to map the PartitionKey to a PartitionId. + * Using this type of Send (Sending using a specific partitionKey), could sometimes result in partitions which are not evenly distributed. + * + * @param eventData the {@link EventData} to be sent. + * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} + * @return a CompletableFuture that can be completed when the send operations is done.. + * @see #send(EventData) + * @see PartitionSender#send(EventData) + */ + public final CompletableFuture send(final EventData eventData, final String partitionKey) { + if (eventData == null) { + throw new IllegalArgumentException("EventData cannot be null."); + } + + if (partitionKey == null) { + throw new IllegalArgumentException("partitionKey cannot be null"); + } + + return this.createInternalSender().thenComposeAsync(new Function>() { + @Override + public CompletableFuture apply(Void voidArg) { + return EventHubClient.this.sender.send(eventData.toAmqpMessage(partitionKey)); + } + }); + } + + /** + * Synchronous version of {@link #send(Iterable, String)}. + * + * @param eventDatas the batch of events to send to EventHub + * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @throws UnresolvedAddressException if there are Client to Service network connectivity issues, if the Azure DNS resolution of the ServiceBus Namespace fails (ex: namespace deleted etc.) + */ + public final void sendSync(final Iterable eventDatas, final String partitionKey) + throws ServiceBusException { + try { + this.send(eventDatas, partitionKey).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send a 'batch of {@link EventData} with the same partitionKey' to EventHub. All {@link EventData}'s with a partitionKey are guaranteed to land on the same partition. + * Multiple PartitionKey's will be mapped to one Partition. + *

+ *

There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData, String)}, which is the same type of Send and is used to send single {@link EventData}. + *

+ *

Sending a batch of {@link EventData}'s is useful in the following cases: + *

+     * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs service.
+     * ii.	Send multiple events in One Transaction. This is the reason why all events sent in a batch needs to have same partitionKey (so that they are sent to one partition only).
+     * 
+ * + * @param eventDatas the batch of events to send to EventHub + * @param partitionKey the partitionKey will be hash'ed to determine the partitionId to send the eventData to. On the Received message this can be accessed at {@link EventData.SystemProperties#getPartitionKey()} + * @return a CompletableFuture that can be completed when the send operations is done.. + * @see #send(EventData) + * @see PartitionSender#send(EventData) + */ + public final CompletableFuture send(final Iterable eventDatas, final String partitionKey) { + if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) { + throw new IllegalArgumentException("Empty batch of EventData cannot be sent."); + } + + if (partitionKey == null) { + throw new IllegalArgumentException("partitionKey cannot be null"); + } + + if (partitionKey.length() > ClientConstants.MAX_PARTITION_KEY_LENGTH) { + throw new IllegalArgumentException( + String.format(Locale.US, "PartitionKey exceeds the maximum allowed length of partitionKey: {0}", ClientConstants.MAX_PARTITION_KEY_LENGTH)); + } + + return this.createInternalSender().thenComposeAsync(new Function>() { + @Override + public CompletableFuture apply(Void voidArg) { + return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas, partitionKey)); + } + }); + } + + /** + * Synchronous version of {@link #createPartitionSender(String)}. + * + * @param partitionId partitionId of EventHub to send the {@link EventData}'s to + * @return PartitionSender which can be used to send events to a specific partition. + * @throws ServiceBusException if Service Bus service encountered problems during connection creation. + */ + public final PartitionSender createPartitionSenderSync(final String partitionId) + throws ServiceBusException, IllegalArgumentException { + try { + return this.createPartitionSender(partitionId).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a {@link PartitionSender} which can publish {@link EventData}'s directly to a specific EventHub partition (sender type iii. in the below list). + *

+ * There are 3 patterns/ways to send to EventHubs: + *

+     * i.   {@link #send(EventData)} or {@link #send(Iterable)}
+     * ii.  {@link #send(EventData, String)} or {@link #send(Iterable, String)}
+     * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
+     * 
+ * + * @param partitionId partitionId of EventHub to send the {@link EventData}'s to + * @return a CompletableFuture that would result in a PartitionSender when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during connection creation. + * @see PartitionSender + */ + public final CompletableFuture createPartitionSender(final String partitionId) + throws ServiceBusException { + return PartitionSender.Create(this.underlyingFactory, this.eventHubName, partitionId); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, String)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, startingOffset).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * The receiver is created for a specific EventHub partition from the specific consumer group. + *

+ *

NOTE: There can be a maximum number of receivers that can run in parallel per ConsumerGroup per Partition. + * The limit is enforced by the Event Hub service - current limit is 5 receivers in parallel. Having multiple receivers + * reading from offsets that are far apart on the same consumer group / partition combo will have significant performance Impact. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset) + throws ServiceBusException { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, false); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, String, boolean)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive) + throws ServiceBusException { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, null); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, Instant)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, dateTime).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime) + throws ServiceBusException { + return this.createReceiver(consumerGroupName, partitionId, dateTime, null); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, String)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * The receiver is created for a specific EventHub partition from the specific consumer group. + *

+ *

NOTE: There can be a maximum number of receivers that can run in parallel per ConsumerGroup per Partition. + * The limit is enforced by the Event Hub service - current limit is 5 receivers in parallel. Having multiple receivers + * reading from offsets that are far apart on the same consumer group / partition combo will have significant performance Impact. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, false, receiverOptions); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, String, boolean)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver instance when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, null, PartitionReceiver.NULL_EPOCH, false, receiverOptions); + } + + /** + * Synchronous version of {@link #createReceiver(String, String, Instant)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createReceiver(consumerGroupName, partitionId, dateTime, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create the EventHub receiver with given partition id and start receiving from the specified starting offset. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + */ + public final CompletableFuture createReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, null, false, dateTime, PartitionReceiver.NULL_EPOCH, false, receiverOptions); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, String, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, epoch).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch) + throws ServiceBusException { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, false, epoch); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, String, boolean, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch) + throws ServiceBusException { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch, null); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, Instant, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param epoch a unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch) + throws ServiceBusException { + return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch, null); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, String, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, epoch, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, false, epoch, receiverOptions); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, String, boolean, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, startingOffset, offsetInclusive, epoch, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param startingOffset the offset to start receiving the events from. To receive from start of the stream use: {@link PartitionReceiver#START_OF_STREAM} + * @param offsetInclusive if set to true, the startingOffset is treated as an inclusive offset - meaning the first event returned is the one that has the starting offset. Normally first event returned is the event after the starting offset. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final String startingOffset, boolean offsetInclusive, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, null, epoch, true, receiverOptions); + } + + /** + * Synchronous version of {@link #createEpochReceiver(String, String, Instant, long)}. + * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param epoch an unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return PartitionReceiver instance which can be used for receiving {@link EventData}. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final PartitionReceiver createEpochReceiverSync(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + try { + return this.createEpochReceiver(consumerGroupName, partitionId, dateTime, epoch, receiverOptions).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Create a Epoch based EventHub receiver with given partition id and start receiving from the beginning of the partition stream. + * The receiver is created for a specific EventHub Partition from the specific consumer group. + *

+ * It is important to pay attention to the following when creating epoch based receiver: + *

    + *
  • Ownership enforcement - Once you created an epoch based receiver, you cannot create a non-epoch receiver to the same consumerGroup-Partition combo until all receivers to the combo are closed. + *
  • Ownership stealing - If a receiver with higher epoch value is created for a consumerGroup-Partition combo, any older epoch receiver to that combo will be force closed. + *
  • Any receiver closed due to lost of ownership to a consumerGroup-Partition combo will get ReceiverDisconnectedException for all operations from that receiver. + *
+ * + * @param consumerGroupName the consumer group name that this receiver should be grouped under. + * @param partitionId the partition Id that the receiver belongs to. All data received will be from this partition only. + * @param dateTime the date time instant that receive operations will start receive events from. Events received will have {@link EventData.SystemProperties#getEnqueuedTime()} later than this Instant. + * @param epoch a unique identifier (epoch value) that the service uses, to enforce partition/lease ownership. + * @param receiverOptions the set of options to enable on the event hubs receiver + * @return a CompletableFuture that would result in a PartitionReceiver when it is completed. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + * @see PartitionReceiver + * @see ReceiverDisconnectedException + */ + public final CompletableFuture createEpochReceiver(final String consumerGroupName, final String partitionId, final Instant dateTime, final long epoch, final ReceiverOptions receiverOptions) + throws ServiceBusException { + return PartitionReceiver.create(this.underlyingFactory, this.eventHubName, consumerGroupName, partitionId, null, false, dateTime, epoch, true, receiverOptions); + } + + @Override + public CompletableFuture onClose() { + if (this.underlyingFactory != null) { + synchronized (this.senderCreateSync) { + final CompletableFuture internalSenderClose = this.sender != null + ? this.sender.close().thenComposeAsync(new Function>() { + @Override + public CompletableFuture apply(Void voidArg) { + return EventHubClient.this.underlyingFactory.close(); + } + }) + : this.underlyingFactory.close(); + + return internalSenderClose; + } + } + + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture createInternalSender() { + if (!this.isSenderCreateStarted) { + synchronized (this.senderCreateSync) { + if (!this.isSenderCreateStarted) { + this.createSender = MessageSender.create(this.underlyingFactory, StringUtil.getRandomString(), this.eventHubName) + .thenAcceptAsync(new Consumer() { + public void accept(MessageSender a) { + EventHubClient.this.sender = a; + } + }); + + this.isSenderCreateStarted = true; + } + } + } + + return this.createSender; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubRuntimeInformation.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubRuntimeInformation.java index 378fa26bc..07a8ffbfc 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubRuntimeInformation.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubRuntimeInformation.java @@ -4,31 +4,26 @@ */ package com.microsoft.azure.eventhubs; -final class EventHubRuntimeInformation -{ +final class EventHubRuntimeInformation { final String path; final int partitionCount; final String[] partitionIds; - - EventHubRuntimeInformation(final String path, final int partitionCount, final String[] partitionIds) - { + + EventHubRuntimeInformation(final String path, final int partitionCount, final String[] partitionIds) { this.path = path; this.partitionCount = partitionCount; this.partitionIds = partitionIds; } - - public String getPath() - { + + public String getPath() { return this.path; } - - public int getPartitionCount() - { + + public int getPartitionCount() { return this.partitionCount; } - - public String[] getPartitionIds() - { + + public String[] getPartitionIds() { return this.partitionIds; } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiveHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiveHandler.java index cd8cc7d10..ecc6e562d 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiveHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiveHandler.java @@ -5,44 +5,44 @@ package com.microsoft.azure.eventhubs; /** - * A handler class for the receive operation. Use any implementation of this abstract class to specify + * A handler class for the receive operation. Use any implementation of this abstract class to specify * user action when using PartitionReceiver's setReceiveHandler(). - * @see PartitionReceiver#setReceiveHandler + * + * @see PartitionReceiver#setReceiveHandler */ -public abstract class PartitionReceiveHandler -{ - private int maxEventCount; +public abstract class PartitionReceiveHandler { + private int maxEventCount; - protected PartitionReceiveHandler(final int maxEventCount) - { - this.maxEventCount = maxEventCount; - } + protected PartitionReceiveHandler(final int maxEventCount) { + this.maxEventCount = maxEventCount; + } - int getMaxEventCount() - { - return maxEventCount; - } + int getMaxEventCount() { + return maxEventCount; + } - /** - * implementor of {@link PartitionReceiveHandler#onReceive} can use this to set the limit on maximum {@link EventData}'s that - * can be received by the next {@link PartitionReceiveHandler#onReceive} call - * @param value maximum {@link EventData}'s to be received in the next {@link PartitionReceiveHandler#onReceive} call - */ - protected final void setMaxEventCount(final int value) - { - this.maxEventCount = value; - } + /** + * implementor of {@link PartitionReceiveHandler#onReceive} can use this to set the limit on maximum {@link EventData}'s that + * can be received by the next {@link PartitionReceiveHandler#onReceive} call + * + * @param value maximum {@link EventData}'s to be received in the next {@link PartitionReceiveHandler#onReceive} call + */ + protected final void setMaxEventCount(final int value) { + this.maxEventCount = value; + } - /** - * user should implement this method to specify the action to be performed on the received events. - * @param events the list of fetched events from the corresponding PartitionReceiver. - * @see PartitionReceiver#receive - */ - public abstract void onReceive(Iterable events); + /** + * user should implement this method to specify the action to be performed on the received events. + * + * @param events the list of fetched events from the corresponding PartitionReceiver. + * @see PartitionReceiver#receive + */ + public abstract void onReceive(Iterable events); - /** - * Implement this method to Listen to errors which lead to Closure of the {@link PartitionReceiveHandler} pump. - * @param error fatal error encountered while running the {@link PartitionReceiveHandler} pump - */ - public abstract void onError(Throwable error); + /** + * Implement this method to Listen to errors which lead to Closure of the {@link PartitionReceiveHandler} pump. + * + * @param error fatal error encountered while running the {@link PartitionReceiveHandler} pump + */ + public abstract void onError(Throwable error); } \ No newline at end of file diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java index 2cbf03f45..420d6ecc0 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java @@ -39,461 +39,422 @@ * A {@link PartitionReceiver} is tied to a ConsumerGroup + EventHub Partition combination. *
    *
  • If an epoch based {@link PartitionReceiver} (i.e., PartitionReceiver.getEpoch != 0) is created, EventHubs service will guarantee only 1 active receiver exists per ConsumerGroup + Partition combo. - * This is the recommended approach to create a {@link PartitionReceiver}. + * This is the recommended approach to create a {@link PartitionReceiver}. *
  • Multiple receivers per ConsumerGroup + Partition combo can be created using non-epoch receivers. *
+ * * @see EventHubClient#createReceiver - * @see EventHubClient#createEpochReceiver + * @see EventHubClient#createEpochReceiver */ -public final class PartitionReceiver extends ClientEntity implements IReceiverSettingsProvider -{ - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private static final int MINIMUM_PREFETCH_COUNT = 10; - private static final int MAXIMUM_PREFETCH_COUNT = 999; - - static final int DEFAULT_PREFETCH_COUNT = 999; - static final long NULL_EPOCH = 0; - - /** - * This is a constant defined to represent the start of a partition stream in EventHub. - */ - public static final String START_OF_STREAM = "-1"; - - private final String partitionId; - private final MessagingFactory underlyingFactory; - private final String eventHubName; - private final String consumerGroupName; - private final Object receiveHandlerLock; - - private String startingOffset; - private boolean offsetInclusive; - private Instant startingDateTime; - private MessageReceiver internalReceiver; - private Long epoch; - private boolean isEpochReceiver; - private ReceivePump receivePump; - private ReceiverOptions receiverOptions; - private ReceiverRuntimeInformation runtimeInformation; - - private PartitionReceiver(MessagingFactory factory, - final String eventHubName, - final String consumerGroupName, - final String partitionId, - final String startingOffset, - final boolean offsetInclusive, - final Instant dateTime, - final Long epoch, - final boolean isEpochReceiver, - final ReceiverOptions receiverOptions) - throws ServiceBusException - { - super(null, null); - - this.underlyingFactory = factory; - this.eventHubName = eventHubName; - this.consumerGroupName = consumerGroupName; - this.partitionId = partitionId; - this.startingOffset = startingOffset; - this.offsetInclusive = offsetInclusive; - this.startingDateTime = dateTime; - this.epoch = epoch; - this.isEpochReceiver = isEpochReceiver; - this.receiveHandlerLock = new Object(); - this.receiverOptions = receiverOptions; - - if (this.receiverOptions != null && this.receiverOptions.getReceiverRuntimeMetricEnabled()) - this.runtimeInformation = new ReceiverRuntimeInformation(partitionId); - } - - static CompletableFuture create(MessagingFactory factory, - final String eventHubName, - final String consumerGroupName, - final String partitionId, - final String startingOffset, - final boolean offsetInclusive, - final Instant dateTime, - final long epoch, - final boolean isEpochReceiver, - final ReceiverOptions receiverOptions) - throws ServiceBusException - { - if (epoch < NULL_EPOCH) - { - throw new IllegalArgumentException("epoch cannot be a negative value. Please specify a zero or positive long value."); - } - - if (StringUtil.isNullOrWhiteSpace(consumerGroupName)) - { - throw new IllegalArgumentException("specify valid string for argument - 'consumerGroupName'"); - } - - final PartitionReceiver receiver = new PartitionReceiver(factory, eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, dateTime, epoch, isEpochReceiver, receiverOptions); - return receiver.createInternalReceiver().thenApplyAsync(new Function() - { - public PartitionReceiver apply(Void a) - { - return receiver; - } - }); - } - - private CompletableFuture createInternalReceiver() throws ServiceBusException - { - return MessageReceiver.create(this.underlyingFactory, +public final class PartitionReceiver extends ClientEntity implements IReceiverSettingsProvider { + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private static final int MINIMUM_PREFETCH_COUNT = 10; + private static final int MAXIMUM_PREFETCH_COUNT = 999; + + static final int DEFAULT_PREFETCH_COUNT = 999; + static final long NULL_EPOCH = 0; + + /** + * This is a constant defined to represent the start of a partition stream in EventHub. + */ + public static final String START_OF_STREAM = "-1"; + + private final String partitionId; + private final MessagingFactory underlyingFactory; + private final String eventHubName; + private final String consumerGroupName; + private final Object receiveHandlerLock; + + private String startingOffset; + private boolean offsetInclusive; + private Instant startingDateTime; + private MessageReceiver internalReceiver; + private Long epoch; + private boolean isEpochReceiver; + private ReceivePump receivePump; + private ReceiverOptions receiverOptions; + private ReceiverRuntimeInformation runtimeInformation; + + private PartitionReceiver(MessagingFactory factory, + final String eventHubName, + final String consumerGroupName, + final String partitionId, + final String startingOffset, + final boolean offsetInclusive, + final Instant dateTime, + final Long epoch, + final boolean isEpochReceiver, + final ReceiverOptions receiverOptions) + throws ServiceBusException { + super(null, null); + + this.underlyingFactory = factory; + this.eventHubName = eventHubName; + this.consumerGroupName = consumerGroupName; + this.partitionId = partitionId; + this.startingOffset = startingOffset; + this.offsetInclusive = offsetInclusive; + this.startingDateTime = dateTime; + this.epoch = epoch; + this.isEpochReceiver = isEpochReceiver; + this.receiveHandlerLock = new Object(); + this.receiverOptions = receiverOptions; + + if (this.receiverOptions != null && this.receiverOptions.getReceiverRuntimeMetricEnabled()) + this.runtimeInformation = new ReceiverRuntimeInformation(partitionId); + } + + static CompletableFuture create(MessagingFactory factory, + final String eventHubName, + final String consumerGroupName, + final String partitionId, + final String startingOffset, + final boolean offsetInclusive, + final Instant dateTime, + final long epoch, + final boolean isEpochReceiver, + final ReceiverOptions receiverOptions) + throws ServiceBusException { + if (epoch < NULL_EPOCH) { + throw new IllegalArgumentException("epoch cannot be a negative value. Please specify a zero or positive long value."); + } + + if (StringUtil.isNullOrWhiteSpace(consumerGroupName)) { + throw new IllegalArgumentException("specify valid string for argument - 'consumerGroupName'"); + } + + final PartitionReceiver receiver = new PartitionReceiver(factory, eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, dateTime, epoch, isEpochReceiver, receiverOptions); + return receiver.createInternalReceiver().thenApplyAsync(new Function() { + public PartitionReceiver apply(Void a) { + return receiver; + } + }); + } + + private CompletableFuture createInternalReceiver() throws ServiceBusException { + return MessageReceiver.create(this.underlyingFactory, StringUtil.getRandomString(), String.format("%s/ConsumerGroups/%s/Partitions/%s", this.eventHubName, this.consumerGroupName, this.partitionId), PartitionReceiver.DEFAULT_PREFETCH_COUNT, this) - .thenAcceptAsync(new Consumer() - { - public void accept(MessageReceiver r) { PartitionReceiver.this.internalReceiver = r;} - }); - } - - /** - * @return The Cursor from which this Receiver started receiving from - */ - final String getStartingOffset() - { - return this.startingOffset; - } - - final boolean getOffsetInclusive() - { - return this.offsetInclusive; - } - - /** - * Get EventHubs partition identifier. - * @return The identifier representing the partition from which this receiver is fetching data - */ - public final String getPartitionId() - { - return this.partitionId; - } - - /** - * Get Prefetch Count configured on the Receiver. - * @return the upper limit of events this receiver will actively receive regardless of whether a receive operation is pending. - * @see #setPrefetchCount - */ - public final int getPrefetchCount() - { - return this.internalReceiver.getPrefetchCount(); - } - - public final Duration getReceiveTimeout() - { - return this.internalReceiver.getReceiveTimeout(); - } - - public void setReceiveTimeout(Duration value) - { - this.internalReceiver.setReceiveTimeout(value); - } - - /** - * Set the number of events that can be pre-fetched and cached at the {@link PartitionReceiver}. - *

By default the value is 300 - * @param prefetchCount the number of events to pre-fetch. value must be between 10 and 999. Default is 300. - * @throws ServiceBusException if setting prefetchCount encounters error - */ - public final void setPrefetchCount(final int prefetchCount) throws ServiceBusException - { - if (prefetchCount < PartitionReceiver.MINIMUM_PREFETCH_COUNT || prefetchCount > PartitionReceiver.MAXIMUM_PREFETCH_COUNT) - { - throw new IllegalArgumentException(String.format(Locale.US, - "PrefetchCount has to be between %s and %s", PartitionReceiver.MINIMUM_PREFETCH_COUNT, PartitionReceiver.MAXIMUM_PREFETCH_COUNT)); - } - - this.internalReceiver.setPrefetchCount(prefetchCount); - } - - /** - * Get the epoch value that this receiver is currently using for partition ownership. - *

- * A value of 0 means this receiver is not an epoch-based receiver. - * @return the epoch value that this receiver is currently using for partition ownership. - */ - public final long getEpoch() - { - return this.epoch; - } - - /** - * Gets the temporal {@link ReceiverRuntimeInformation} for this EventHub partition. - * In general, this information is a representation of, where this {@link PartitionReceiver}'s end of stream is, - * at the time {@link ReceiverRuntimeInformation#getRetrievalTime()}. - * @return receiver runtime information - */ - public final ReceiverRuntimeInformation getRuntimeInformation() { - - return this.runtimeInformation; + .thenAcceptAsync(new Consumer() { + public void accept(MessageReceiver r) { + PartitionReceiver.this.internalReceiver = r; + } + }); + } + + /** + * @return The Cursor from which this Receiver started receiving from + */ + final String getStartingOffset() { + return this.startingOffset; + } + + final boolean getOffsetInclusive() { + return this.offsetInclusive; + } + + /** + * Get EventHubs partition identifier. + * + * @return The identifier representing the partition from which this receiver is fetching data + */ + public final String getPartitionId() { + return this.partitionId; + } + + /** + * Get Prefetch Count configured on the Receiver. + * + * @return the upper limit of events this receiver will actively receive regardless of whether a receive operation is pending. + * @see #setPrefetchCount + */ + public final int getPrefetchCount() { + return this.internalReceiver.getPrefetchCount(); + } + + public final Duration getReceiveTimeout() { + return this.internalReceiver.getReceiveTimeout(); + } + + public void setReceiveTimeout(Duration value) { + this.internalReceiver.setReceiveTimeout(value); + } + + /** + * Set the number of events that can be pre-fetched and cached at the {@link PartitionReceiver}. + *

By default the value is 300 + * + * @param prefetchCount the number of events to pre-fetch. value must be between 10 and 999. Default is 300. + * @throws ServiceBusException if setting prefetchCount encounters error + */ + public final void setPrefetchCount(final int prefetchCount) throws ServiceBusException { + if (prefetchCount < PartitionReceiver.MINIMUM_PREFETCH_COUNT || prefetchCount > PartitionReceiver.MAXIMUM_PREFETCH_COUNT) { + throw new IllegalArgumentException(String.format(Locale.US, + "PrefetchCount has to be between %s and %s", PartitionReceiver.MINIMUM_PREFETCH_COUNT, PartitionReceiver.MAXIMUM_PREFETCH_COUNT)); + } + + this.internalReceiver.setPrefetchCount(prefetchCount); + } + + /** + * Get the epoch value that this receiver is currently using for partition ownership. + *

+ * A value of 0 means this receiver is not an epoch-based receiver. + * + * @return the epoch value that this receiver is currently using for partition ownership. + */ + public final long getEpoch() { + return this.epoch; + } + + /** + * Gets the temporal {@link ReceiverRuntimeInformation} for this EventHub partition. + * In general, this information is a representation of, where this {@link PartitionReceiver}'s end of stream is, + * at the time {@link ReceiverRuntimeInformation#getRetrievalTime()}. + * + * @return receiver runtime information + */ + public final ReceiverRuntimeInformation getRuntimeInformation() { + + return this.runtimeInformation; + } + + /** + * Synchronous version of {@link #receive}. + * + * @param maxEventCount maximum number of {@link EventData}'s that this call should return + * @return Batch of {@link EventData}'s from the partition on which this receiver is created. Returns 'null' if no {@link EventData} is present. + * @throws ServiceBusException if ServiceBus client encountered any unrecoverable/non-transient problems during {@link #receive} + */ + public final Iterable receiveSync(final int maxEventCount) + throws ServiceBusException { + try { + return this.receive(maxEventCount).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + + return null; + } + + /** + * Receive a batch of {@link EventData}'s from an EventHub partition + *

+ * Sample code (sample uses sync version of the api but concept are identical): + *

+     * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
+     * PartitionReceiver receiver = client.createPartitionReceiverSync("ConsumerGroup1", "1");
+     * Iterable{@literal<}EventData{@literal>} receivedEvents = receiver.receiveSync();
+     *
+     * while (true)
+     * {
+     *     int batchSize = 0;
+     *     if (receivedEvents != null)
+     *     {
+     *         for(EventData receivedEvent: receivedEvents)
+     *         {
+     *             System.out.println(String.format("Message Payload: %s", new String(receivedEvent.getBytes(), Charset.defaultCharset())));
+     *             System.out.println(String.format("Offset: %s, SeqNo: %s, EnqueueTime: %s",
+     *                 receivedEvent.getSystemProperties().getOffset(),
+     *                 receivedEvent.getSystemProperties().getSequenceNumber(),
+     *                 receivedEvent.getSystemProperties().getEnqueuedTime()));
+     *             batchSize++;
+     *         }
+     *     }
+     *
+     *     System.out.println(String.format("ReceivedBatch Size: %s", batchSize));
+     *     receivedEvents = receiver.receiveSync();
+     * }
+     * 
+ * + * @param maxEventCount maximum number of {@link EventData}'s that this call should return + * @return A completableFuture that will yield a batch of {@link EventData}'s from the partition on which this receiver is created. Returns 'null' if no {@link EventData} is present. + */ + public CompletableFuture> receive(final int maxEventCount) { + return this.internalReceiver.receive(maxEventCount).thenApply(new Function, Iterable>() { + @Override + public Iterable apply(Collection amqpMessages) { + PassByRef lastMessageRef = null; + if (PartitionReceiver.this.receiverOptions != null && PartitionReceiver.this.receiverOptions.getReceiverRuntimeMetricEnabled()) + lastMessageRef = new PassByRef<>(); + + Iterable events = EventDataUtil.toEventDataCollection(amqpMessages, lastMessageRef); + + if (lastMessageRef != null && lastMessageRef.get() != null) { + + DeliveryAnnotations deliveryAnnotations = lastMessageRef.get().getDeliveryAnnotations(); + if (deliveryAnnotations != null && deliveryAnnotations.getValue() != null) { + + Map deliveryAnnotationsMap = deliveryAnnotations.getValue(); + PartitionReceiver.this.runtimeInformation.setRuntimeInformation( + (long) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_SEQUENCE_NUMBER), + ((Date) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_TIME_UTC)).toInstant(), + (String) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_OFFSET)); + } + } + + return events; + } + }); + } + + /** + * Register a receive handler that will be called when an event is available. A + * {@link PartitionReceiveHandler} is a handler that allows user to specify a callback + * for event processing and error handling in a receive pump model. + * + * @param receiveHandler An implementation of {@link PartitionReceiveHandler}. Setting this handler to null will stop the receive pump. + * @return A completableFuture which sets receiveHandler + */ + public CompletableFuture setReceiveHandler(final PartitionReceiveHandler receiveHandler) { + return this.setReceiveHandler(receiveHandler, false); + } + + /** + * Register a receive handler that will be called when an event is available. A + * {@link PartitionReceiveHandler} is a handler that allows user to specify a callback + * for event processing and error handling in a receive pump model. + * + * @param receiveHandler An implementation of {@link PartitionReceiveHandler} + * @param invokeWhenNoEvents flag to indicate whether the {@link PartitionReceiveHandler#onReceive(Iterable)} should be invoked when the receive call times out + * @return A completableFuture which sets receiveHandler + */ + public CompletableFuture setReceiveHandler(final PartitionReceiveHandler receiveHandler, final boolean invokeWhenNoEvents) { + synchronized (this.receiveHandlerLock) { + // user setting receiveHandler==null should stop the pump if its running + if (receiveHandler == null) { + if (this.receivePump != null && this.receivePump.isRunning()) { + return this.receivePump.stop(); + } + } else { + if (this.receivePump != null && this.receivePump.isRunning()) + throw new IllegalArgumentException( + "Unexpected value for parameter 'receiveHandler'. PartitionReceiver was already registered with a PartitionReceiveHandler instance. Only 1 instance can be registered."); + + this.receivePump = new ReceivePump( + new ReceivePump.IPartitionReceiver() { + @Override + public Iterable receive(int maxBatchSize) throws ServiceBusException { + return PartitionReceiver.this.receiveSync(maxBatchSize); + } + + @Override + public String getPartitionId() { + return PartitionReceiver.this.getPartitionId(); + } + }, + receiveHandler, + invokeWhenNoEvents); + + final Thread onReceivePumpThread = new Thread(new Runnable() { + @Override + public void run() { + receivePump.run(); + } + }); + + onReceivePumpThread.start(); + } + + return CompletableFuture.completedFuture(null); + } + } + + @Override + public CompletableFuture onClose() { + if (this.receivePump != null && this.receivePump.isRunning()) { + // set the state of receivePump to StopEventRaised + // - but don't actually wait until the current user-code completes + // if user intends to stop everything - setReceiveHandler(null) should be invoked before close + this.receivePump.stop(); } - /** - * Synchronous version of {@link #receive}. - * @param maxEventCount maximum number of {@link EventData}'s that this call should return - * @return Batch of {@link EventData}'s from the partition on which this receiver is created. Returns 'null' if no {@link EventData} is present. - * @throws ServiceBusException if ServiceBus client encountered any unrecoverable/non-transient problems during {@link #receive} - */ - public final Iterable receiveSync(final int maxEventCount) - throws ServiceBusException - { - try - { - return this.receive(maxEventCount).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - - return null; - } - - /** - * Receive a batch of {@link EventData}'s from an EventHub partition - *

- * Sample code (sample uses sync version of the api but concept are identical): - *

-	 * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
-	 * PartitionReceiver receiver = client.createPartitionReceiverSync("ConsumerGroup1", "1");
-	 * Iterable{@literal<}EventData{@literal>} receivedEvents = receiver.receiveSync();
-	 *      
-	 * while (true)
-	 * {
-	 *     int batchSize = 0;
-	 *     if (receivedEvents != null)
-	 *     {
-	 *         for(EventData receivedEvent: receivedEvents)
-	 *         {
-	 *             System.out.println(String.format("Message Payload: %s", new String(receivedEvent.getBytes(), Charset.defaultCharset())));
-	 *             System.out.println(String.format("Offset: %s, SeqNo: %s, EnqueueTime: %s", 
-	 *                 receivedEvent.getSystemProperties().getOffset(), 
-	 *                 receivedEvent.getSystemProperties().getSequenceNumber(), 
-	 *                 receivedEvent.getSystemProperties().getEnqueuedTime()));
-	 *             batchSize++;
-	 *         }
-	 *     }
-	 *          
-	 *     System.out.println(String.format("ReceivedBatch Size: %s", batchSize));
-	 *     receivedEvents = receiver.receiveSync();
-	 * }
-	 * 
- * @param maxEventCount maximum number of {@link EventData}'s that this call should return - * @return A completableFuture that will yield a batch of {@link EventData}'s from the partition on which this receiver is created. Returns 'null' if no {@link EventData} is present. - */ - public CompletableFuture> receive(final int maxEventCount) - { - return this.internalReceiver.receive(maxEventCount).thenApply(new Function, Iterable>() - { - @Override - public Iterable apply(Collection amqpMessages) - { - PassByRef lastMessageRef = null; - if (PartitionReceiver.this.receiverOptions != null && PartitionReceiver.this.receiverOptions.getReceiverRuntimeMetricEnabled()) - lastMessageRef = new PassByRef<>(); - - Iterable events = EventDataUtil.toEventDataCollection(amqpMessages, lastMessageRef); - - if (lastMessageRef != null && lastMessageRef.get() != null) { - - DeliveryAnnotations deliveryAnnotations = lastMessageRef.get().getDeliveryAnnotations(); - if (deliveryAnnotations != null && deliveryAnnotations.getValue() != null) { - - Map deliveryAnnotationsMap = deliveryAnnotations.getValue(); - PartitionReceiver.this.runtimeInformation.setRuntimeInformation( - (long) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_SEQUENCE_NUMBER), - ((Date) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_TIME_UTC)).toInstant(), - (String) deliveryAnnotationsMap.get(ClientConstants.LAST_ENQUEUED_OFFSET)); - } - } - - return events; - } - }); - } - - /** - * Register a receive handler that will be called when an event is available. A - * {@link PartitionReceiveHandler} is a handler that allows user to specify a callback - * for event processing and error handling in a receive pump model. - * - * @param receiveHandler An implementation of {@link PartitionReceiveHandler}. Setting this handler to null will stop the receive pump. - * @return A completableFuture which sets receiveHandler - */ - public CompletableFuture setReceiveHandler(final PartitionReceiveHandler receiveHandler) - { - return this.setReceiveHandler(receiveHandler, false); - } - - /** - * Register a receive handler that will be called when an event is available. A - * {@link PartitionReceiveHandler} is a handler that allows user to specify a callback - * for event processing and error handling in a receive pump model. - * @param receiveHandler An implementation of {@link PartitionReceiveHandler} - * @param invokeWhenNoEvents flag to indicate whether the {@link PartitionReceiveHandler#onReceive(Iterable)} should be invoked when the receive call times out - * @return A completableFuture which sets receiveHandler - */ - public CompletableFuture setReceiveHandler(final PartitionReceiveHandler receiveHandler, final boolean invokeWhenNoEvents) - { - synchronized (this.receiveHandlerLock) - { - // user setting receiveHandler==null should stop the pump if its running - if (receiveHandler == null) - { - if (this.receivePump != null && this.receivePump.isRunning()) - { - return this.receivePump.stop(); - } - } - else - { - if (this.receivePump != null && this.receivePump.isRunning()) - throw new IllegalArgumentException( - "Unexpected value for parameter 'receiveHandler'. PartitionReceiver was already registered with a PartitionReceiveHandler instance. Only 1 instance can be registered."); - - this.receivePump = new ReceivePump( - new ReceivePump.IPartitionReceiver() - { - @Override - public Iterable receive(int maxBatchSize) throws ServiceBusException - { - return PartitionReceiver.this.receiveSync(maxBatchSize); - } - - @Override - public String getPartitionId() - { - return PartitionReceiver.this.getPartitionId(); - } - }, - receiveHandler, - invokeWhenNoEvents); - - final Thread onReceivePumpThread = new Thread(new Runnable() - { - @Override - public void run() - { - receivePump.run(); - } - }); - - onReceivePumpThread.start(); - } - - return CompletableFuture.completedFuture(null); - } - } - - @Override - public CompletableFuture onClose() - { - if (this.receivePump != null && this.receivePump.isRunning()) - { - // set the state of receivePump to StopEventRaised - // - but don't actually wait until the current user-code completes - // if user intends to stop everything - setReceiveHandler(null) should be invoked before close - this.receivePump.stop(); - } - - if (this.internalReceiver != null) - { - return this.internalReceiver.close(); - } - else - { - return CompletableFuture.completedFuture(null); - } - } - - - @Override - public Map getFilter(final Message lastReceivedMessage) - { - final UnknownDescribedType filter; - if (lastReceivedMessage == null && this.startingOffset == null) - { - long totalMilliSeconds; - try - { - totalMilliSeconds = this.startingDateTime.toEpochMilli(); - } - catch(ArithmeticException ex) - { - totalMilliSeconds = Long.MAX_VALUE; - if(TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - String.format("receiverPath[%s], action[createReceiveLink], warning[starting receiver from epoch+Long.Max]", this.internalReceiver.getReceivePath())); - } - } - - filter = new UnknownDescribedType(AmqpConstants.STRING_FILTER, - String.format(AmqpConstants.AMQP_ANNOTATION_FORMAT, AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME, StringUtil.EMPTY, totalMilliSeconds)); - } - else - { - final String lastReceivedOffset; - final boolean offsetInclusiveFlag; - if (lastReceivedMessage != null) - { - offsetInclusiveFlag = false; - lastReceivedOffset = lastReceivedMessage.getMessageAnnotations().getValue().get(AmqpConstants.OFFSET).toString(); - } - else - { - offsetInclusiveFlag = this.offsetInclusive; - lastReceivedOffset = this.startingOffset; - } - - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], action[createReceiveLink], offset[%s], offsetInclusive[%s]", this.internalReceiver.getReceivePath(), lastReceivedOffset, offsetInclusiveFlag)); - } - - filter = new UnknownDescribedType(AmqpConstants.STRING_FILTER, - String.format(AmqpConstants.AMQP_ANNOTATION_FORMAT, - AmqpConstants.OFFSET_ANNOTATION_NAME, - offsetInclusiveFlag ? "=" : StringUtil.EMPTY, - lastReceivedOffset)); - } - - return Collections.singletonMap(AmqpConstants.STRING_FILTER, filter); - } - - @Override - public Map getProperties() { - - return this.isEpochReceiver ? Collections.singletonMap(AmqpConstants.EPOCH, (Object) this.epoch) : null; - } - - @Override - public Symbol[] getDesiredCapabilities() { - - return this.receiverOptions != null && this.receiverOptions.getReceiverRuntimeMetricEnabled() - ? new Symbol[] { AmqpConstants.ENABLE_RECEIVER_RUNTIME_METRIC_NAME } - : null; + if (this.internalReceiver != null) { + return this.internalReceiver.close(); + } else { + return CompletableFuture.completedFuture(null); } + } + + + @Override + public Map getFilter(final Message lastReceivedMessage) { + final UnknownDescribedType filter; + if (lastReceivedMessage == null && this.startingOffset == null) { + long totalMilliSeconds; + try { + totalMilliSeconds = this.startingDateTime.toEpochMilli(); + } catch (ArithmeticException ex) { + totalMilliSeconds = Long.MAX_VALUE; + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format("receiverPath[%s], action[createReceiveLink], warning[starting receiver from epoch+Long.Max]", this.internalReceiver.getReceivePath())); + } + } + + filter = new UnknownDescribedType(AmqpConstants.STRING_FILTER, + String.format(AmqpConstants.AMQP_ANNOTATION_FORMAT, AmqpConstants.ENQUEUED_TIME_UTC_ANNOTATION_NAME, StringUtil.EMPTY, totalMilliSeconds)); + } else { + final String lastReceivedOffset; + final boolean offsetInclusiveFlag; + if (lastReceivedMessage != null) { + offsetInclusiveFlag = false; + lastReceivedOffset = lastReceivedMessage.getMessageAnnotations().getValue().get(AmqpConstants.OFFSET).toString(); + } else { + offsetInclusiveFlag = this.offsetInclusive; + lastReceivedOffset = this.startingOffset; + } + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + String logReceivePath = ""; + if (this.internalReceiver == null) { + // During startup, internalReceiver is still null. Need to handle this special case when logging during startup + // or the reactor thread crashes with NPE when calling internalReceiver.getReceivePath() and no receiving occurs. + logReceivePath = "receiverPath[RECEIVER IS NULL]"; + } else { + logReceivePath = "receiverPath[" + this.internalReceiver.getReceivePath() + "]"; + } + TRACE_LOGGER.log(Level.FINE, String.format("%s, action[createReceiveLink], offset[%s], offsetInclusive[%s]", logReceivePath, lastReceivedOffset, offsetInclusiveFlag)); + } + + filter = new UnknownDescribedType(AmqpConstants.STRING_FILTER, + String.format(AmqpConstants.AMQP_ANNOTATION_FORMAT, + AmqpConstants.OFFSET_ANNOTATION_NAME, + offsetInclusiveFlag ? "=" : StringUtil.EMPTY, + lastReceivedOffset)); + } + + return Collections.singletonMap(AmqpConstants.STRING_FILTER, filter); + } + + @Override + public Map getProperties() { + + return this.isEpochReceiver ? Collections.singletonMap(AmqpConstants.EPOCH, (Object) this.epoch) : null; + } + + @Override + public Symbol[] getDesiredCapabilities() { + + return this.receiverOptions != null && this.receiverOptions.getReceiverRuntimeMetricEnabled() + ? new Symbol[]{AmqpConstants.ENABLE_RECEIVER_RUNTIME_METRIC_NAME} + : null; + } } \ No newline at end of file diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionRuntimeInformation.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionRuntimeInformation.java index 888566af2..b2583e810 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionRuntimeInformation.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionRuntimeInformation.java @@ -6,23 +6,21 @@ import java.time.Instant; -public final class PartitionRuntimeInformation -{ +public final class PartitionRuntimeInformation { private final String eventHubPath; private final String partitionId; private final long beginSequenceNumber; private final long lastEnqueuedSequenceNumber; private final String lastEnqueuedOffset; private final Instant lastEnqueuedTimeUtc; - + PartitionRuntimeInformation( - final String eventHubPath, - final String partitionId, - final long beginSequenceNumber, - final long lastEnqueuedSequenceNumber, - final String lastEnqueuedOffset, - final Instant lastEnqueuedTimeUtc) - { + final String eventHubPath, + final String partitionId, + final long beginSequenceNumber, + final long lastEnqueuedSequenceNumber, + final String lastEnqueuedOffset, + final Instant lastEnqueuedTimeUtc) { this.eventHubPath = eventHubPath; this.partitionId = partitionId; this.beginSequenceNumber = beginSequenceNumber; @@ -30,34 +28,28 @@ public final class PartitionRuntimeInformation this.lastEnqueuedOffset = lastEnqueuedOffset; this.lastEnqueuedTimeUtc = lastEnqueuedTimeUtc; } - - public String getEventHubPath() - { + + public String getEventHubPath() { return this.eventHubPath; } - - public String getPartitionId() - { + + public String getPartitionId() { return this.partitionId; } - - public long getBeginSequenceNumber() - { + + public long getBeginSequenceNumber() { return this.beginSequenceNumber; } - - public long getLastEnqueuedSequenceNumber() - { + + public long getLastEnqueuedSequenceNumber() { return this.lastEnqueuedSequenceNumber; } - - public String getLastEnqueuedOffset() - { + + public String getLastEnqueuedOffset() { return this.lastEnqueuedOffset; } - - public Instant getLastEnqueuedTimeUtc() - { + + public Instant getLastEnqueuedTimeUtc() { return this.lastEnqueuedTimeUtc; } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java index 56a2e452a..7abd5ece3 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java @@ -10,217 +10,193 @@ import com.microsoft.azure.servicebus.*; /** - * This sender class is a logical representation of sending events to a specific EventHub partition. Do not use this class + * This sender class is a logical representation of sending events to a specific EventHub partition. Do not use this class * if you do not care about sending events to specific partitions. Instead, use {@link EventHubClient#send} method. + * * @see EventHubClient#createPartitionSender(String) * @see EventHubClient#createFromConnectionString(String) */ -public final class PartitionSender extends ClientEntity -{ - private final String partitionId; - private final String eventHubName; - private final MessagingFactory factory; - - private MessageSender internalSender; - - private PartitionSender(MessagingFactory factory, String eventHubName, String partitionId) - { - super(null, null); - - this.partitionId = partitionId; - this.eventHubName = eventHubName; - this.factory = factory; - } - - /** - * Internal-Only: factory pattern to Create EventHubSender - */ - static CompletableFuture Create(MessagingFactory factory, String eventHubName, String partitionId) throws ServiceBusException - { - final PartitionSender sender = new PartitionSender(factory, eventHubName, partitionId); - return sender.createInternalSender() - .thenApplyAsync(new Function() - { - public PartitionSender apply(Void a) - { - return sender; - } - }); - } - - private CompletableFuture createInternalSender() throws ServiceBusException - { - return MessageSender.create(this.factory, StringUtil.getRandomString(), - String.format("%s/Partitions/%s", this.eventHubName, this.partitionId)) - .thenAcceptAsync(new Consumer() - { - public void accept(MessageSender a) { PartitionSender.this.internalSender = a;} - }); - } - - /** - * Synchronous version of {@link #send(EventData)} Api. - * @param data the {@link EventData} to be sent. - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final void sendSync(final EventData data) - throws ServiceBusException - { - try - { - this.send(data).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send {@link EventData} to a specific EventHub partition. The target partition is pre-determined when this PartitionSender was created. - * This send pattern emphasize data correlation over general availability and latency. - *

- * There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): - *

-	 * i.   {@link EventHubClient#send(EventData)} or {@link EventHubClient#send(Iterable)}
-	 * ii.  {@link EventHubClient#send(EventData, String)} or {@link EventHubClient#send(Iterable, String)}
-	 * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
-	 * 
- * - * Use this type of Send, if: - *
-	 * i. The client wants to take direct control of distribution of data across partitions. In this case client is responsible for making sure there is at least one sender per event hub partition.
-	 * ii. User cannot use partition key as a mean to direct events to specific partition, yet there is a need for data correlation with partitioning scheme. 
-	 * 
- * - * @param data the {@link EventData} to be sent. - * @return a CompletableFuture that can be completed when the send operations is done.. - */ - public final CompletableFuture send(EventData data) - { - return this.internalSender.send(data.toAmqpMessage()); - } - - /** - * Synchronous version of {@link #send(Iterable)} . - * @param eventDatas batch of events to send to EventHub - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final void sendSync(final Iterable eventDatas) - throws ServiceBusException - { - try - { - this.send(eventDatas).get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { - // Re-assert the thread's interrupted status - Thread.currentThread().interrupt(); - } - - Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; - } - - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; - } - - throw new ServiceBusException(true, throwable); - } - } - } - - /** - * Send {@link EventData} to a specific EventHub partition. The targeted partition is pre-determined when this PartitionSender was created. - *

- * There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData)}, which is the same type of Send and is used to send single {@link EventData}. - *

- * Sending a batch of {@link EventData}'s is useful in the following cases: - *

-	 * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs' service.
-	 * ii.	Send multiple {@link EventData}'s in a Transaction. To achieve ACID properties, the Gateway Service will forward all {@link EventData}'s in the batch to a single EventHubs' partition.
-	 * 
- *

- * Sample code (sample uses sync version of the api but concept are identical): - *

-	 * Gson gson = new GsonBuilder().create();
-	 * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
-	 * PartitionSender senderToPartitionOne = client.createPartitionSenderSync("1");
-	 *         
-	 * while (true)
-	 * {
-	 *     LinkedList{@literal<}EventData{@literal>} events = new LinkedList{@literal<}EventData{@literal>}();
-	 *     for (int count = 1; count {@literal<} 11; count++)
-	 *     {
-	 *         PayloadEvent payload = new PayloadEvent(count);
-	 *         byte[] payloadBytes = gson.toJson(payload).getBytes(Charset.defaultCharset());
-	 *         EventData sendEvent = new EventData(payloadBytes);
-	 *         Map{@literal<}String, String{@literal>} applicationProperties = new HashMap{@literal<}String, String{@literal>}();
-	 *         applicationProperties.put("from", "javaClient");
-	 *         sendEvent.setProperties(applicationProperties);
-	 *         events.add(sendEvent);
-	 *     }
-	 *         
-	 *     senderToPartitionOne.sendSync(events);
-	 *     System.out.println(String.format("Sent Batch... Size: %s", events.size()));
-	 * }		
-	 * 
- * @param eventDatas batch of events to send to EventHub - * @return a CompletableFuture that can be completed when the send operations is done.. - * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. - * @throws ServiceBusException if Service Bus service encountered problems during the operation. - */ - public final CompletableFuture send(Iterable eventDatas) - throws ServiceBusException - { - if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) - { - throw new IllegalArgumentException("EventData batch cannot be empty."); - } - - return this.internalSender.send(EventDataUtil.toAmqpMessages(eventDatas)); - } - - @Override - public CompletableFuture onClose() - { - if (this.internalSender == null) - { - return CompletableFuture.completedFuture(null); - } - else - { - return this.internalSender.close(); - } - } +public final class PartitionSender extends ClientEntity { + private final String partitionId; + private final String eventHubName; + private final MessagingFactory factory; + + private MessageSender internalSender; + + private PartitionSender(MessagingFactory factory, String eventHubName, String partitionId) { + super(null, null); + + this.partitionId = partitionId; + this.eventHubName = eventHubName; + this.factory = factory; + } + + /** + * Internal-Only: factory pattern to Create EventHubSender + */ + static CompletableFuture Create(MessagingFactory factory, String eventHubName, String partitionId) throws ServiceBusException { + final PartitionSender sender = new PartitionSender(factory, eventHubName, partitionId); + return sender.createInternalSender() + .thenApplyAsync(new Function() { + public PartitionSender apply(Void a) { + return sender; + } + }); + } + + private CompletableFuture createInternalSender() throws ServiceBusException { + return MessageSender.create(this.factory, StringUtil.getRandomString(), + String.format("%s/Partitions/%s", this.eventHubName, this.partitionId)) + .thenAcceptAsync(new Consumer() { + public void accept(MessageSender a) { + PartitionSender.this.internalSender = a; + } + }); + } + + /** + * Synchronous version of {@link #send(EventData)} Api. + * + * @param data the {@link EventData} to be sent. + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final void sendSync(final EventData data) + throws ServiceBusException { + try { + this.send(data).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send {@link EventData} to a specific EventHub partition. The target partition is pre-determined when this PartitionSender was created. + * This send pattern emphasize data correlation over general availability and latency. + *

+ * There are 3 ways to send to EventHubs, each exposed as a method (along with its sendBatch overload): + *

+     * i.   {@link EventHubClient#send(EventData)} or {@link EventHubClient#send(Iterable)}
+     * ii.  {@link EventHubClient#send(EventData, String)} or {@link EventHubClient#send(Iterable, String)}
+     * iii. {@link PartitionSender#send(EventData)} or {@link PartitionSender#send(Iterable)}
+     * 
+ *

+ * Use this type of Send, if: + *

+     * i. The client wants to take direct control of distribution of data across partitions. In this case client is responsible for making sure there is at least one sender per event hub partition.
+     * ii. User cannot use partition key as a mean to direct events to specific partition, yet there is a need for data correlation with partitioning scheme.
+     * 
+ * + * @param data the {@link EventData} to be sent. + * @return a CompletableFuture that can be completed when the send operations is done.. + */ + public final CompletableFuture send(EventData data) { + return this.internalSender.send(data.toAmqpMessage()); + } + + /** + * Synchronous version of {@link #send(Iterable)} . + * + * @param eventDatas batch of events to send to EventHub + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final void sendSync(final Iterable eventDatas) + throws ServiceBusException { + try { + this.send(eventDatas).get(); + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { + // Re-assert the thread's interrupted status + Thread.currentThread().interrupt(); + } + + Throwable throwable = exception.getCause(); + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; + } + + throw new ServiceBusException(true, throwable); + } + } + } + + /** + * Send {@link EventData} to a specific EventHub partition. The targeted partition is pre-determined when this PartitionSender was created. + *

+ * There are 3 ways to send to EventHubs, to understand this particular type of Send refer to the overload {@link #send(EventData)}, which is the same type of Send and is used to send single {@link EventData}. + *

+ * Sending a batch of {@link EventData}'s is useful in the following cases: + *

+     * i.	Efficient send - sending a batch of {@link EventData} maximizes the overall throughput by optimally using the number of sessions created to EventHubs' service.
+     * ii.	Send multiple {@link EventData}'s in a Transaction. To achieve ACID properties, the Gateway Service will forward all {@link EventData}'s in the batch to a single EventHubs' partition.
+     * 
+ *

+ * Sample code (sample uses sync version of the api but concept are identical): + *

+     * Gson gson = new GsonBuilder().create();
+     * EventHubClient client = EventHubClient.createFromConnectionStringSync("__connection__");
+     * PartitionSender senderToPartitionOne = client.createPartitionSenderSync("1");
+     *
+     * while (true)
+     * {
+     *     LinkedList{@literal<}EventData{@literal>} events = new LinkedList{@literal<}EventData{@literal>}();
+     *     for (int count = 1; count {@literal<} 11; count++)
+     *     {
+     *         PayloadEvent payload = new PayloadEvent(count);
+     *         byte[] payloadBytes = gson.toJson(payload).getBytes(Charset.defaultCharset());
+     *         EventData sendEvent = new EventData(payloadBytes);
+     *         Map{@literal<}String, String{@literal>} applicationProperties = new HashMap{@literal<}String, String{@literal>}();
+     *         applicationProperties.put("from", "javaClient");
+     *         sendEvent.setProperties(applicationProperties);
+     *         events.add(sendEvent);
+     *     }
+     *
+     *     senderToPartitionOne.sendSync(events);
+     *     System.out.println(String.format("Sent Batch... Size: %s", events.size()));
+     * }
+     * 
+ * + * @param eventDatas batch of events to send to EventHub + * @return a CompletableFuture that can be completed when the send operations is done.. + * @throws PayloadSizeExceededException if the total size of the {@link EventData} exceeds a pre-defined limit set by the service. Default is 256k bytes. + * @throws ServiceBusException if Service Bus service encountered problems during the operation. + */ + public final CompletableFuture send(Iterable eventDatas) + throws ServiceBusException { + if (eventDatas == null || IteratorUtil.sizeEquals(eventDatas, 0)) { + throw new IllegalArgumentException("EventData batch cannot be empty."); + } + + return this.internalSender.send(EventDataUtil.toAmqpMessages(eventDatas)); + } + + @Override + public CompletableFuture onClose() { + if (this.internalSender == null) { + return CompletableFuture.completedFuture(null); + } else { + return this.internalSender.close(); + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceivePump.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceivePump.java index 0142954a7..fb7c55936 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceivePump.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceivePump.java @@ -12,99 +12,80 @@ import com.microsoft.azure.servicebus.ClientConstants; import com.microsoft.azure.servicebus.ServiceBusException; -public class ReceivePump -{ - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - private final IPartitionReceiver receiver; - private final PartitionReceiveHandler onReceiveHandler; - private final boolean invokeOnTimeout; - private final CompletableFuture stopPump; - - private AtomicBoolean stopPumpRaised; - - public ReceivePump( - final IPartitionReceiver receiver, - final PartitionReceiveHandler receiveHandler, - final boolean invokeOnReceiveWithNoEvents) - { - this.receiver = receiver; - this.onReceiveHandler = receiveHandler; - this.invokeOnTimeout = invokeOnReceiveWithNoEvents; - this.stopPump = new CompletableFuture(); - - this.stopPumpRaised = new AtomicBoolean(false); - } - - public void run() - { - boolean isPumpHealthy = true; - while(isPumpHealthy && !this.stopPumpRaised.get()) - { - Iterable receivedEvents = null; - - try - { - receivedEvents = this.receiver.receive(this.onReceiveHandler.getMaxEventCount()); - } - catch (Throwable clientException) - { - isPumpHealthy = false; - this.onReceiveHandler.onError(clientException); - - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, String.format("Receive pump for partition (%s) exiting after receive exception %s", this.receiver.getPartitionId(), clientException.toString())); - } - } - - try - { - if (receivedEvents != null || (receivedEvents == null && this.invokeOnTimeout && isPumpHealthy)) - { - this.onReceiveHandler.onReceive(receivedEvents); - } - } - catch (Throwable userCodeError) - { - isPumpHealthy = false; - this.onReceiveHandler.onError(userCodeError); - - if (userCodeError instanceof InterruptedException) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format("Interrupting receive pump for partition (%s)", this.receiver.getPartitionId())); - } - - Thread.currentThread().interrupt(); - } - else if (TRACE_LOGGER.isLoggable(Level.SEVERE)) - { - TRACE_LOGGER.log(Level.SEVERE, String.format("Receive pump for partition (%s) exiting after user exception %s", this.receiver.getPartitionId(), userCodeError.toString())); - } - } - } - - this.stopPump.complete(null); - } - - public CompletableFuture stop() - { - this.stopPumpRaised.set(true); - return this.stopPump; - } - - public boolean isRunning() - { - return !this.stopPump.isDone(); - } - - // partition receiver contract against which this pump works - public static interface IPartitionReceiver - { - public String getPartitionId(); - - public Iterable receive(final int maxBatchSize) throws ServiceBusException; - } +public class ReceivePump { + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + + private final IPartitionReceiver receiver; + private final PartitionReceiveHandler onReceiveHandler; + private final boolean invokeOnTimeout; + private final CompletableFuture stopPump; + + private AtomicBoolean stopPumpRaised; + + public ReceivePump( + final IPartitionReceiver receiver, + final PartitionReceiveHandler receiveHandler, + final boolean invokeOnReceiveWithNoEvents) { + this.receiver = receiver; + this.onReceiveHandler = receiveHandler; + this.invokeOnTimeout = invokeOnReceiveWithNoEvents; + this.stopPump = new CompletableFuture(); + + this.stopPumpRaised = new AtomicBoolean(false); + } + + public void run() { + boolean isPumpHealthy = true; + while (isPumpHealthy && !this.stopPumpRaised.get()) { + Iterable receivedEvents = null; + + try { + receivedEvents = this.receiver.receive(this.onReceiveHandler.getMaxEventCount()); + } catch (Throwable clientException) { + isPumpHealthy = false; + this.onReceiveHandler.onError(clientException); + + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, String.format("Receive pump for partition (%s) exiting after receive exception %s", this.receiver.getPartitionId(), clientException.toString())); + } + } + + try { + if (receivedEvents != null || (receivedEvents == null && this.invokeOnTimeout && isPumpHealthy)) { + this.onReceiveHandler.onReceive(receivedEvents); + } + } catch (Throwable userCodeError) { + isPumpHealthy = false; + this.onReceiveHandler.onError(userCodeError); + + if (userCodeError instanceof InterruptedException) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format("Interrupting receive pump for partition (%s)", this.receiver.getPartitionId())); + } + + Thread.currentThread().interrupt(); + } else if (TRACE_LOGGER.isLoggable(Level.SEVERE)) { + TRACE_LOGGER.log(Level.SEVERE, String.format("Receive pump for partition (%s) exiting after user exception %s", this.receiver.getPartitionId(), userCodeError.toString())); + } + } + } + + this.stopPump.complete(null); + } + + public CompletableFuture stop() { + this.stopPumpRaised.set(true); + return this.stopPump; + } + + public boolean isRunning() { + return !this.stopPump.isDone(); + } + + // partition receiver contract against which this pump works + public static interface IPartitionReceiver { + public String getPartitionId(); + + public Iterable receive(final int maxBatchSize) throws ServiceBusException; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverOptions.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverOptions.java index c9e14dfb9..414613aa7 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverOptions.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverOptions.java @@ -8,30 +8,32 @@ * Represents various optional behaviors which can be turned on or off during the creation of a {@link PartitionReceiver}. */ public final class ReceiverOptions { - + private boolean receiverRuntimeMetricEnabled; - + /** * Knob to enable/disable runtime metric of the receiver. If this is set to true and is passed to {@link EventHubClient#createReceiver}, * after the first {@link PartitionReceiver#receive(int)} call, {@link PartitionReceiver#getRuntimeInformation()} is populated. *

* Enabling this knob will add 3 additional properties to all {@link EventData}'s received on the {@link EventHubClient#createReceiver}. + * * @return the {@link boolean} indicating, whether, the runtime metric of the receiver was enabled */ public boolean getReceiverRuntimeMetricEnabled() { - + return this.receiverRuntimeMetricEnabled; } - + /** * Knob to enable/disable runtime metric of the receiver. If this is set to true and is passed to {@link EventHubClient#createReceiver}, * after the first {@link PartitionReceiver#receive(int)} call, {@link PartitionReceiver#getRuntimeInformation()} is populated. *

* Enabling this knob will add 3 additional properties to all {@link EventData}'s received on the {@link EventHubClient#createReceiver}. + * * @param value the {@link boolean} to indicate, whether, the runtime metric of the receiver should be enabled */ public void setReceiverRuntimeMetricEnabled(boolean value) { - + this.receiverRuntimeMetricEnabled = value; } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverRuntimeInformation.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverRuntimeInformation.java index eb3e63193..b359f5158 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverRuntimeInformation.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ReceiverRuntimeInformation.java @@ -11,70 +11,75 @@ * Current received {@link EventData} and {@link ReceiverRuntimeInformation} can be used to find approximate value of pending events (which are not processed yet). */ public final class ReceiverRuntimeInformation { - + private final String partitionId; - + private long lastSequenceNumber; private Instant lastEnqueuedTime; private String lastEnqueuedOffset; private Instant retrievalTime; - + public ReceiverRuntimeInformation(final String partitionId) { - + this.partitionId = partitionId; } - + /** * Get PartitionId of the {@link PartitionReceiver} for which the {@link ReceiverRuntimeInformation} is returned. + * * @return Partition Identifier */ public String getPartitionId() { - + return this.partitionId; } - + /** * Get sequence number of the {@link EventData}, that is written at the end of the Partition Stream. + * * @return last sequence number */ public long getLastSequenceNumber() { - + return this.lastSequenceNumber; } - + /** * Get enqueued time of the {@link EventData}, that is written at the end of the Partition Stream. + * * @return last enqueued time */ public Instant getLastEnqueuedTime() { - + return this.lastEnqueuedTime; } - + /** * Get offset of the {@link EventData}, that is written at the end of the Partition Stream. + * * @return last enqueued offset */ public String getLastEnqueuedOffset() { - + return this.lastEnqueuedOffset; } - + /** * Get the timestamp at which this {@link ReceiverRuntimeInformation} was constructed. + * * @return retrieval time */ public Instant getRetrievalTime() { - + return this.retrievalTime; } - + void setRuntimeInformation(final long sequenceNumber, final Instant enqueuedTime, final String offset) { - + this.lastSequenceNumber = sequenceNumber; this.lastEnqueuedTime = enqueuedTime; this.lastEnqueuedOffset = offset; - + this.retrievalTime = Instant.now(); } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java index 77766dc89..7d79a21e5 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ActiveClientTokenManager.java @@ -11,60 +11,59 @@ import java.util.logging.Logger; public class ActiveClientTokenManager { - + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); private ScheduledFuture timer; - + private final Object timerLock; private final Runnable sendTokenTask; private final ClientEntity clientEntity; private final Duration tokenRefreshInterval; - + public ActiveClientTokenManager( final ClientEntity clientEntity, final Runnable sendTokenAsync, final Duration tokenRefreshInterval) { - + this.sendTokenTask = sendTokenAsync; this.clientEntity = clientEntity; this.tokenRefreshInterval = tokenRefreshInterval; this.timerLock = new Object(); - + synchronized (this.timerLock) { this.timer = Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); } } - + public void cancel() { - + synchronized (this.timerLock) { this.timer.cancel(false); } } - + private class TimerCallback implements Runnable { @Override public void run() { - + if (!clientEntity.getIsClosingOrClosed()) { - + sendTokenTask.run(); - + synchronized (timerLock) { timer = Timer.schedule(new TimerCallback(), tokenRefreshInterval, TimerType.OneTimeRun); } - } - else { - + } else { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, - "clientEntity[%s] - closing ActiveClientLinkManager", clientEntity.getClientId())); + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, + "clientEntity[%s] - closing ActiveClientLinkManager", clientEntity.getClientId())); } } } - + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/AuthorizationFailedException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/AuthorizationFailedException.java index d773a81a1..4e317c15b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/AuthorizationFailedException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/AuthorizationFailedException.java @@ -6,36 +6,33 @@ /** * Authorization failed exception is thrown when error is encountered during authorizing user's permission to run the intended operations. - * When encountered this exception user should check whether the token/key provided in the connection string (e.g. one passed to - * {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)}) is valid, and has correct execution right for the intended operations (e.g. + * When encountered this exception user should check whether the token/key provided in the connection string (e.g. one passed to + * {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)}) is valid, and has correct execution right for the intended operations (e.g. * Receive call will need Listen claim associated with the key/token). + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class AuthorizationFailedException extends ServiceBusException -{ - private static final long serialVersionUID = 5384872132102860710L; +public class AuthorizationFailedException extends ServiceBusException { + private static final long serialVersionUID = 5384872132102860710L; - AuthorizationFailedException() - { - super(false); - } + AuthorizationFailedException() { + super(false); + } - /** - * Constructor for the exception class - * @param message the actual error message detailing the reason for the failure - */ - public AuthorizationFailedException(final String message) - { - super(false, message); - } + /** + * Constructor for the exception class + * + * @param message the actual error message detailing the reason for the failure + */ + public AuthorizationFailedException(final String message) { + super(false, message); + } - AuthorizationFailedException(final Throwable cause) - { - super(false, cause); - } + AuthorizationFailedException(final Throwable cause) { + super(false, cause); + } - AuthorizationFailedException(final String message, final Throwable cause) - { - super(false, message, cause); - } + AuthorizationFailedException(final String message, final Throwable cause) { + super(false, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java index 3b576fbb5..dc7f23796 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java @@ -27,18 +27,18 @@ public class CBSChannel { final FaultTolerantObject innerChannel; final ISessionProvider sessionProvider; final IAmqpConnection connectionEventDispatcher; - + public CBSChannel( - final ISessionProvider sessionProvider, - final IAmqpConnection connection, + final ISessionProvider sessionProvider, + final IAmqpConnection connection, final String linkName) { this.sessionProvider = sessionProvider; this.connectionEventDispatcher = connection; this.innerChannel = new FaultTolerantObject<>( - new OpenRequestResponseChannel(), - new CloseRequestResponseChannel()); + new OpenRequestResponseChannel(), + new CloseRequestResponseChannel()); } public void sendToken( @@ -46,8 +46,8 @@ public void sendToken( final String token, final String tokenAudience, final IOperationResult sendTokenCallback) { - - final Message request= Proton.message(); + + final Message request = Proton.message(); final Map properties = new HashMap<>(); properties.put(ClientConstants.PUT_TOKEN_OPERATION, ClientConstants.PUT_TOKEN_OPERATION_VALUE); properties.put(ClientConstants.PUT_TOKEN_TYPE, ClientConstants.SAS_TOKEN_TYPE); @@ -55,32 +55,31 @@ public void sendToken( final ApplicationProperties applicationProperties = new ApplicationProperties(properties); request.setApplicationProperties(applicationProperties); request.setBody(new AmqpValue(token)); - - this.innerChannel.runOnOpenedObject(dispatcher, + + this.innerChannel.runOnOpenedObject(dispatcher, new IOperationResult() { @Override public void onComplete(final RequestResponseChannel result) { result.request(dispatcher, request, - new IOperationResult() { - @Override - public void onComplete(final Message response) { - - final int statusCode = (int) response.getApplicationProperties().getValue().get(ClientConstants.PUT_TOKEN_STATUS_CODE); - final String statusDescription = (String) response.getApplicationProperties().getValue().get(ClientConstants.PUT_TOKEN_STATUS_DESCRIPTION); - - if (statusCode == AmqpResponseCode.ACCEPTED.getValue() || statusCode == AmqpResponseCode.OK.getValue()) { - sendTokenCallback.onComplete(null); - } - else { - this.onError(ExceptionUtil.amqpResponseCodeToException(statusCode, statusDescription)); + new IOperationResult() { + @Override + public void onComplete(final Message response) { + + final int statusCode = (int) response.getApplicationProperties().getValue().get(ClientConstants.PUT_TOKEN_STATUS_CODE); + final String statusDescription = (String) response.getApplicationProperties().getValue().get(ClientConstants.PUT_TOKEN_STATUS_DESCRIPTION); + + if (statusCode == AmqpResponseCode.ACCEPTED.getValue() || statusCode == AmqpResponseCode.OK.getValue()) { + sendTokenCallback.onComplete(null); + } else { + this.onError(ExceptionUtil.amqpResponseCodeToException(statusCode, statusDescription)); + } } - } - @Override - public void onError(final Exception error) { - sendTokenCallback.onError(error); - } - }); + @Override + public void onError(final Exception error) { + sendTokenCallback.onError(error); + } + }); } @Override @@ -89,60 +88,62 @@ public void onError(Exception error) { } }); } - + public void close( final ReactorDispatcher reactorDispatcher, final IOperationResult closeCallback) { - + this.innerChannel.close(reactorDispatcher, closeCallback); } - + private class OpenRequestResponseChannel implements IOperation { @Override public void run(IOperationResult operationCallback) { final RequestResponseChannel requestResponseChannel = new RequestResponseChannel( - "cbs", - ClientConstants.CBS_ADDRESS, - CBSChannel.this.sessionProvider.getSession( - "cbs-session", - null, - new BiConsumer() { + "cbs", + ClientConstants.CBS_ADDRESS, + CBSChannel.this.sessionProvider.getSession( + "cbs-session", + null, + new BiConsumer() { + @Override + public void accept(ErrorCondition error, Exception exception) { + if (error != null) + operationCallback.onError(new AmqpException(error)); + else if (exception != null) + operationCallback.onError(exception); + } + })); + + requestResponseChannel.open( + new IOperationResult() { @Override - public void accept(ErrorCondition error, Exception exception) { - if (error != null) - operationCallback.onError(new AmqpException(error)); - else if (exception != null) - operationCallback.onError(exception); + public void onComplete(Void result) { + connectionEventDispatcher.registerForConnectionError(requestResponseChannel.getSendLink()); + connectionEventDispatcher.registerForConnectionError(requestResponseChannel.getReceiveLink()); + + operationCallback.onComplete(requestResponseChannel); } - })); - requestResponseChannel.open( - new IOperationResult() { - @Override - public void onComplete(Void result) { - connectionEventDispatcher.registerForConnectionError(requestResponseChannel.getSendLink()); - connectionEventDispatcher.registerForConnectionError(requestResponseChannel.getReceiveLink()); + @Override + public void onError(Exception error) { + operationCallback.onError(error); + } + }, + new IOperationResult() { + @Override + public void onComplete(Void result) { + connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getSendLink()); + connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getReceiveLink()); + } - operationCallback.onComplete(requestResponseChannel); - } - @Override - public void onError(Exception error) { - operationCallback.onError(error); - } - }, - new IOperationResult() { - @Override - public void onComplete(Void result) { - connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getSendLink()); - connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getReceiveLink()); - } - @Override - public void onError(Exception error) { - connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getSendLink()); - connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getReceiveLink()); - } - }); + @Override + public void onError(Exception error) { + connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getSendLink()); + connectionEventDispatcher.deregisterForConnectionError(requestResponseChannel.getReceiveLink()); + } + }); } } @@ -150,14 +151,13 @@ private class CloseRequestResponseChannel implements IOperation { @Override public void run(IOperationResult closeOperationCallback) { - + final RequestResponseChannel channelToBeClosed = innerChannel.unsafeGetIfOpened(); if (channelToBeClosed == null) { - + closeOperationCallback.onComplete(null); - } - else { - + } else { + channelToBeClosed.close(new IOperationResult() { @Override public void onComplete(Void result) { @@ -169,7 +169,7 @@ public void onError(Exception error) { closeOperationCallback.onError(error); } }); - } - } + } + } } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java index cb2d6c057..7667e023b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientConstants.java @@ -10,105 +10,104 @@ import com.microsoft.azure.servicebus.amqp.AmqpConstants; -public final class ClientConstants -{ - private ClientConstants() { } - - public final static int AMQPS_PORT = 5671; - public final static int MAX_PARTITION_KEY_LENGTH = 128; - - public final static Symbol SERVER_BUSY_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":server-busy"); - public final static Symbol ARGUMENT_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":argument-error"); - public final static Symbol ARGUMENT_OUT_OF_RANGE_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":argument-out-of-range"); - public final static Symbol ENTITY_DISABLED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":entity-disabled"); - public final static Symbol PARTITION_NOT_OWNED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":partition-not-owned"); - public final static Symbol STORE_LOCK_LOST_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":store-lock-lost"); - public final static Symbol PUBLISHER_REVOKED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":publisher-revoked"); - public final static Symbol TIMEOUT_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":timeout"); - public final static Symbol TRACKING_ID_PROPERTY = Symbol.getSymbol(AmqpConstants.VENDOR + ":tracking-id"); - - public static final int MAX_MESSAGE_LENGTH_BYTES = 256 * 1024; - public static final int MAX_FRAME_SIZE_BYTES = 64 * 1024; - public static final int MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES = 512; - - public final static Duration TIMER_TOLERANCE = Duration.ofSeconds(1); - - public final static Duration DEFAULT_RERTRY_MIN_BACKOFF = Duration.ofSeconds(0); - public final static Duration DEFAULT_RERTRY_MAX_BACKOFF = Duration.ofSeconds(30); - public final static Duration TOKEN_REFRESH_INTERVAL = Duration.ofMinutes(20); - - public final static int DEFAULT_MAX_RETRY_COUNT = 10; - - public final static String SERVICEBUS_CLIENT_TRACE = "servicebus.trace"; - - public final static boolean DEFAULT_IS_TRANSIENT = true; - - public final static int SESSION_OPEN_TIMEOUT_IN_MS = 15000; - - public final static int REACTOR_IO_POLL_TIMEOUT = 20; - public final static int SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS = 4; - - public final static String NO_RETRY = "NoRetry"; - public final static String DEFAULT_RETRY = "Default"; - - public final static String PRODUCT_NAME = "MSJavaClient"; - public final static String CURRENT_JAVACLIENT_VERSION = "0.13.1"; - - public static final String PLATFORM_INFO = getPlatformInfo(); - - public static final String CBS_ADDRESS = "$cbs"; - public static final String PUT_TOKEN_OPERATION = "operation"; - public static final String PUT_TOKEN_OPERATION_VALUE = "put-token"; - public static final String PUT_TOKEN_TYPE = "type"; - public static final String SAS_TOKEN_TYPE = "servicebus.windows.net:sastoken"; - public static final String PUT_TOKEN_AUDIENCE = "name"; - public static final String PUT_TOKEN_EXPIRY = "expiration"; - public static final String PUT_TOKEN_STATUS_CODE = "status-code"; - public static final String PUT_TOKEN_STATUS_DESCRIPTION = "status-description"; - - public static final String MANAGEMENT_ADDRESS = "$management"; - public static final String MANAGEMENT_EVENTHUB_ENTITY_TYPE = AmqpConstants.VENDOR + ":eventhub"; - public static final String MANAGEMENT_PARTITION_ENTITY_TYPE = AmqpConstants.VENDOR + ":partition"; - public static final String MANAGEMENT_OPERATION_KEY = "operation"; - public static final String READ_OPERATION_VALUE = "READ"; - public static final String MANAGEMENT_ENTITY_TYPE_KEY = "type"; - public static final String MANAGEMENT_ENTITY_NAME_KEY = "name"; - public static final String MANAGEMENT_PARTITION_NAME_KEY = "partition"; - public static final String MANAGEMENT_SECURITY_TOKEN_KEY = "security_token"; - public static final String MANAGEMENT_RESULT_PARTITION_IDS = "partition_ids"; - public static final String MANAGEMENT_RESULT_PARTITION_COUNT = "partition_count"; - public static final String MANAGEMENT_RESULT_BEGIN_SEQUENCE_NUMBER = "begin_sequence_number"; - public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER = "last_enqueued_sequence_number"; - public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET = "last_enqueued_offset"; - public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC = "last_enqueued_time_utc"; - public static final String MANAGEMENT_STATUS_CODE_KEY = "status-code"; - public static final String MANAGEMENT_STATUS_DESCRIPTION_KEY = "status-description"; - public static final String MANAGEMENT_RESPONSE_ERROR_CONDITION = "error-condition"; - - public static final Symbol LAST_ENQUEUED_SEQUENCE_NUMBER = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER); - public static final Symbol LAST_ENQUEUED_OFFSET = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET); - public static final Symbol LAST_ENQUEUED_TIME_UTC = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC); - - public static final String AMQP_PUT_TOKEN_FAILED_ERROR = "Put token failed. status-code: %s, status-description: %s"; - public static final String TOKEN_AUDIENCE_FORMAT = "amqp://%s/%s"; - - private static String getPlatformInfo() - { - final Package javaRuntimeClassPkg = Runtime.class.getPackage(); - final StringBuilder patformInfo = new StringBuilder(); - patformInfo.append("jre:"); - patformInfo.append(javaRuntimeClassPkg.getImplementationVersion()); - patformInfo.append(";vendor:"); - patformInfo.append(javaRuntimeClassPkg.getImplementationVendor()); - patformInfo.append(";jvm:"); - patformInfo.append(System.getProperty("java.vm.version")); - patformInfo.append(";arch:"); - patformInfo.append(System.getProperty("os.arch")); - patformInfo.append(";os:"); - patformInfo.append(System.getProperty("os.name")); - patformInfo.append(";os version:"); - patformInfo.append(System.getProperty("os.version")); - - return patformInfo.toString(); - } +public final class ClientConstants { + private ClientConstants() { + } + + public final static int AMQPS_PORT = 5671; + public final static int MAX_PARTITION_KEY_LENGTH = 128; + + public final static Symbol SERVER_BUSY_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":server-busy"); + public final static Symbol ARGUMENT_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":argument-error"); + public final static Symbol ARGUMENT_OUT_OF_RANGE_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":argument-out-of-range"); + public final static Symbol ENTITY_DISABLED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":entity-disabled"); + public final static Symbol PARTITION_NOT_OWNED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":partition-not-owned"); + public final static Symbol STORE_LOCK_LOST_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":store-lock-lost"); + public final static Symbol PUBLISHER_REVOKED_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":publisher-revoked"); + public final static Symbol TIMEOUT_ERROR = Symbol.getSymbol(AmqpConstants.VENDOR + ":timeout"); + public final static Symbol TRACKING_ID_PROPERTY = Symbol.getSymbol(AmqpConstants.VENDOR + ":tracking-id"); + + public static final int MAX_MESSAGE_LENGTH_BYTES = 256 * 1024; + public static final int MAX_FRAME_SIZE_BYTES = 64 * 1024; + public static final int MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES = 512; + + public final static Duration TIMER_TOLERANCE = Duration.ofSeconds(1); + + public final static Duration DEFAULT_RERTRY_MIN_BACKOFF = Duration.ofSeconds(0); + public final static Duration DEFAULT_RERTRY_MAX_BACKOFF = Duration.ofSeconds(30); + public final static Duration TOKEN_REFRESH_INTERVAL = Duration.ofMinutes(20); + + public final static int DEFAULT_MAX_RETRY_COUNT = 10; + + public final static String SERVICEBUS_CLIENT_TRACE = "servicebus.trace"; + + public final static boolean DEFAULT_IS_TRANSIENT = true; + + public final static int SESSION_OPEN_TIMEOUT_IN_MS = 15000; + + public final static int REACTOR_IO_POLL_TIMEOUT = 20; + public final static int SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS = 4; + + public final static String NO_RETRY = "NoRetry"; + public final static String DEFAULT_RETRY = "Default"; + + public final static String PRODUCT_NAME = "MSJavaClient"; + public final static String CURRENT_JAVACLIENT_VERSION = "0.13.1"; + + public static final String PLATFORM_INFO = getPlatformInfo(); + + public static final String CBS_ADDRESS = "$cbs"; + public static final String PUT_TOKEN_OPERATION = "operation"; + public static final String PUT_TOKEN_OPERATION_VALUE = "put-token"; + public static final String PUT_TOKEN_TYPE = "type"; + public static final String SAS_TOKEN_TYPE = "servicebus.windows.net:sastoken"; + public static final String PUT_TOKEN_AUDIENCE = "name"; + public static final String PUT_TOKEN_EXPIRY = "expiration"; + public static final String PUT_TOKEN_STATUS_CODE = "status-code"; + public static final String PUT_TOKEN_STATUS_DESCRIPTION = "status-description"; + + public static final String MANAGEMENT_ADDRESS = "$management"; + public static final String MANAGEMENT_EVENTHUB_ENTITY_TYPE = AmqpConstants.VENDOR + ":eventhub"; + public static final String MANAGEMENT_PARTITION_ENTITY_TYPE = AmqpConstants.VENDOR + ":partition"; + public static final String MANAGEMENT_OPERATION_KEY = "operation"; + public static final String READ_OPERATION_VALUE = "READ"; + public static final String MANAGEMENT_ENTITY_TYPE_KEY = "type"; + public static final String MANAGEMENT_ENTITY_NAME_KEY = "name"; + public static final String MANAGEMENT_PARTITION_NAME_KEY = "partition"; + public static final String MANAGEMENT_SECURITY_TOKEN_KEY = "security_token"; + public static final String MANAGEMENT_RESULT_PARTITION_IDS = "partition_ids"; + public static final String MANAGEMENT_RESULT_PARTITION_COUNT = "partition_count"; + public static final String MANAGEMENT_RESULT_BEGIN_SEQUENCE_NUMBER = "begin_sequence_number"; + public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER = "last_enqueued_sequence_number"; + public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET = "last_enqueued_offset"; + public static final String MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC = "last_enqueued_time_utc"; + public static final String MANAGEMENT_STATUS_CODE_KEY = "status-code"; + public static final String MANAGEMENT_STATUS_DESCRIPTION_KEY = "status-description"; + public static final String MANAGEMENT_RESPONSE_ERROR_CONDITION = "error-condition"; + + public static final Symbol LAST_ENQUEUED_SEQUENCE_NUMBER = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER); + public static final Symbol LAST_ENQUEUED_OFFSET = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET); + public static final Symbol LAST_ENQUEUED_TIME_UTC = Symbol.valueOf(MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC); + + public static final String AMQP_PUT_TOKEN_FAILED_ERROR = "Put token failed. status-code: %s, status-description: %s"; + public static final String TOKEN_AUDIENCE_FORMAT = "amqp://%s/%s"; + + private static String getPlatformInfo() { + final Package javaRuntimeClassPkg = Runtime.class.getPackage(); + final StringBuilder patformInfo = new StringBuilder(); + patformInfo.append("jre:"); + patformInfo.append(javaRuntimeClassPkg.getImplementationVersion()); + patformInfo.append(";vendor:"); + patformInfo.append(javaRuntimeClassPkg.getImplementationVendor()); + patformInfo.append(";jvm:"); + patformInfo.append(System.getProperty("java.vm.version")); + patformInfo.append(";arch:"); + patformInfo.append(System.getProperty("os.arch")); + patformInfo.append(";os:"); + patformInfo.append(System.getProperty("os.name")); + patformInfo.append(";os version:"); + patformInfo.append(System.getProperty("os.version")); + + return patformInfo.toString(); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java index eb63b4af0..2dfdac07d 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java @@ -9,12 +9,11 @@ import java.util.concurrent.ExecutionException; /** - * Contract for all client entities with Open-Close/Abort state m/c - * main-purpose: closeAll related entities - * Internal-class + * Contract for all client entities with Open-Close/Abort state m/c + * main-purpose: closeAll related entities + * Internal-class */ -public abstract class ClientEntity -{ +public abstract class ClientEntity { private final String clientId; private final Object syncClose; private final ClientEntity parent; @@ -23,8 +22,7 @@ public abstract class ClientEntity private boolean isClosing; private boolean isClosed; - protected ClientEntity(final String clientId, final ClientEntity parent) - { + protected ClientEntity(final String clientId, final ClientEntity parent) { this.clientId = clientId; this.parent = parent; @@ -33,89 +31,70 @@ protected ClientEntity(final String clientId, final ClientEntity parent) protected abstract CompletableFuture onClose(); - public String getClientId() - { + public String getClientId() { return this.clientId; } - boolean getIsClosed() - { + boolean getIsClosed() { final boolean isParentClosed = this.parent != null && this.parent.getIsClosed(); - synchronized (this.syncClose) - { + synchronized (this.syncClose) { return isParentClosed || this.isClosed; } } // returns true even if the Parent is (being) Closed - boolean getIsClosingOrClosed() - { + boolean getIsClosingOrClosed() { final boolean isParentClosingOrClosed = this.parent != null && this.parent.getIsClosingOrClosed(); - synchronized (this.syncClose) - { + synchronized (this.syncClose) { return isParentClosingOrClosed || this.isClosing || this.isClosed; } } // used to force close when entity is faulted - protected final void setClosed() - { - synchronized (this.syncClose) - { + protected final void setClosed() { + synchronized (this.syncClose) { this.isClosed = true; } } - public final CompletableFuture close() - { - synchronized (this.syncClose) - { + public final CompletableFuture close() { + synchronized (this.syncClose) { if (this.isClosed || this.isClosing) return this.closeTask == null ? CompletableFuture.completedFuture(null) : this.closeTask; this.isClosing = true; } - this.closeTask = this.onClose().thenRunAsync(new Runnable() - { - @Override - public void run() - { - synchronized (ClientEntity.this.syncClose) - { - ClientEntity.this.isClosing = false; - ClientEntity.this.isClosed = true; - } - }}); + this.closeTask = this.onClose().thenRunAsync(new Runnable() { + @Override + public void run() { + synchronized (ClientEntity.this.syncClose) { + ClientEntity.this.isClosing = false; + ClientEntity.this.isClosed = true; + } + } + }); return this.closeTask; } - public final void closeSync() throws ServiceBusException - { - try - { + public final void closeSync() throws ServiceBusException { + try { this.close().get(); - } - catch (InterruptedException|ExecutionException exception) - { - if (exception instanceof InterruptedException) - { + } catch (InterruptedException | ExecutionException exception) { + if (exception instanceof InterruptedException) { // Re-assert the thread's interrupted status Thread.currentThread().interrupt(); } Throwable throwable = exception.getCause(); - if (throwable != null) - { - if (throwable instanceof RuntimeException) - { - throw (RuntimeException)throwable; + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; } - if (throwable instanceof ServiceBusException) - { - throw (ServiceBusException)throwable; + if (throwable instanceof ServiceBusException) { + throw (ServiceBusException) throwable; } throw new ServiceBusException(true, throwable); @@ -123,10 +102,8 @@ public final void closeSync() throws ServiceBusException } } - protected final void throwIfClosed(Throwable cause) - { - if (this.getIsClosingOrClosed()) - { + protected final void throwIfClosed(Throwable cause) { + if (this.getIsClosingOrClosed()) { throw new IllegalStateException(String.format(Locale.US, "Operation not allowed after the %s instance is Closed.", this.getClass().getName()), cause); } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CommunicationException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CommunicationException.java index 93f270b15..b650a3092 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CommunicationException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CommunicationException.java @@ -13,29 +13,25 @@ *

  • Check for any firewall settings that can block amqp ports *
  • Check for any general network connectivity issues, as well as network latency. * + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class CommunicationException extends ServiceBusException -{ - private static final long serialVersionUID = 7968596830506494332L; +public class CommunicationException extends ServiceBusException { + private static final long serialVersionUID = 7968596830506494332L; - CommunicationException() - { - super(true); - } + CommunicationException() { + super(true); + } - CommunicationException(final String message) - { - super(true, message); - } + CommunicationException(final String message) { + super(true, message); + } - CommunicationException(final Throwable cause) - { - super(true, cause); - } + CommunicationException(final Throwable cause) { + super(true, cause); + } - CommunicationException(final String message, final Throwable cause) - { - super(true, message, cause); - } + CommunicationException(final String message, final Throwable cause) { + super(true, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ConnectionStringBuilder.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ConnectionStringBuilder.java index 51b5024c4..14312b832 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ConnectionStringBuilder.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ConnectionStringBuilder.java @@ -18,15 +18,15 @@ *

    Sample Code: *

    {@code
      * 	ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder(
    - *     "ServiceBusNamespaceName", 
    - *     "ServiceBusEntityName", // eventHubName or QueueName or TopicName 
    - *     "SharedAccessSignatureKeyName", 
    + *     "ServiceBusNamespaceName",
    + *     "ServiceBusEntityName", // eventHubName or QueueName or TopicName
    + *     "SharedAccessSignatureKeyName",
      *     "SharedAccessSignatureKey");
    - *  
    + *
      * String connectionString = connectionStringBuilder.toString();
      * }
    *

    - * A connection string is basically a string consisted of key-value pair separated by ";". + * A connection string is basically a string consisted of key-value pair separated by ";". * Basic format is {{@literal <}key{@literal >}={@literal <}value{@literal >}[;{@literal <}key{@literal >}={@literal <}value{@literal >}]} where supported key name are as follow: *

      *
    • Endpoint - the URL that contains the servicebus namespace @@ -35,411 +35,363 @@ *
    • SharedAccessKey - the key for the corresponding shared access policy rule of the namespace or entity. *
    */ -public class ConnectionStringBuilder -{ - final static String endpointFormat = "amqps://%s.servicebus.windows.net"; - final static String endpointRawFormat = "amqps://%s"; - - final static String HostnameConfigName = "Hostname"; - final static String EndpointConfigName = "Endpoint"; - final static String SharedAccessKeyNameConfigName = "SharedAccessKeyName"; - final static String SharedAccessKeyConfigName = "SharedAccessKey"; - final static String SharedAccessSignatureConfigName = "SharedAccessSignature"; - final static String EntityPathConfigName = "EntityPath"; - final static String OperationTimeoutConfigName = "OperationTimeout"; - final static String RetryPolicyConfigName = "RetryPolicy"; - final static String KeyValueSeparator = "="; - final static String KeyValuePairDelimiter = ";"; - - private static final String AllKeyEnumerateRegex = "(" + HostnameConfigName + "|" + EndpointConfigName + "|" + SharedAccessKeyNameConfigName - + "|" + SharedAccessKeyConfigName + "|" + SharedAccessSignatureConfigName + "|" + EntityPathConfigName + "|" + OperationTimeoutConfigName - + "|" + RetryPolicyConfigName + ")"; - - private static final String KeysWithDelimitersRegex = KeyValuePairDelimiter + AllKeyEnumerateRegex - + KeyValueSeparator; - - private URI endpoint; - private String sharedAccessKeyName; - private String sharedAccessKey; - private String entityPath; - private String sharedAccessSignature; - private Duration operationTimeout; - private RetryPolicy retryPolicy; - - private ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, - final String sharedAccessKeyName, - final String sharedAccessKey, - final Duration operationTimeout, - final RetryPolicy retryPolicy) - { - this.endpoint = endpointAddress; - this.sharedAccessKey = sharedAccessKey; - this.sharedAccessKeyName = sharedAccessKeyName; - this.operationTimeout = operationTimeout; - this.retryPolicy = retryPolicy; - this.entityPath = entityPath; - } - - private ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, - final String sharedAccessSignature, - final Duration operationTimeout, - final RetryPolicy retryPolicy) - { - this.endpoint = endpointAddress; - this.sharedAccessSignature = sharedAccessSignature; - this.operationTimeout = operationTimeout; - this.retryPolicy = retryPolicy; - this.entityPath = entityPath; - } - - private ConnectionStringBuilder( - final String namespaceName, - final String entityPath, - final String sharedAccessKeyName, - final String sharedAccessKey, - final Duration operationTimeout, - final RetryPolicy retryPolicy) - { - try - { - this.endpoint = new URI(String.format(Locale.US, endpointFormat, namespaceName)); - } - catch(URISyntaxException exception) - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Invalid namespace name: %s", namespaceName), - exception); - } - - this.sharedAccessKey = sharedAccessKey; - this.sharedAccessKeyName = sharedAccessKeyName; - this.operationTimeout = operationTimeout; - this.retryPolicy = retryPolicy; - this.entityPath = entityPath; - } - - /** - * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} - * @param namespaceName Namespace name (dns suffix - ex: .servicebus.windows.net is not required) - * @param entityPath Entity path. For eventHubs case specify - eventHub name. - * @param sharedAccessKeyName Shared Access Key name - * @param sharedAccessKey Shared Access Key - */ - public ConnectionStringBuilder( - final String namespaceName, - final String entityPath, - final String sharedAccessKeyName, - final String sharedAccessKey) - { - this(namespaceName, entityPath, sharedAccessKeyName, sharedAccessKey, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); - } - - - /** - * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} - * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName - * @param entityPath Entity path. For eventHubs case specify - eventHub name. - * @param sharedAccessKeyName Shared Access Key name - * @param sharedAccessKey Shared Access Key - */ - public ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, - final String sharedAccessKeyName, - final String sharedAccessKey) - { - this(endpointAddress, entityPath, sharedAccessKeyName, sharedAccessKey, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); - } - - /** - * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} - * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName - * @param entityPath Entity path. For eventHubs case specify - eventHub name. - * @param sharedAccessSignature Shared Access Signature - */ - public ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, - final String sharedAccessSignature) - { - this(endpointAddress, entityPath, sharedAccessSignature, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); - } - - /** - * ConnectionString format: - * Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY - * @param connectionString ServiceBus ConnectionString - * @throws IllegalConnectionStringFormatException when the format of the ConnectionString is not valid - */ - public ConnectionStringBuilder(String connectionString) - { - this.parseConnectionString(connectionString); - } - - /** - * Get the endpoint which can be used to connect to the ServiceBus Namespace - * @return Endpoint - */ - public URI getEndpoint() - { - return this.endpoint; - } - - /** - * Get the shared access policy key value from the connection string - * @return Shared Access Signature key - */ - public String getSasKey() - { - return this.sharedAccessKey; - } - - /** - * Get the shared access policy owner name from the connection string - * @return Shared Access Signature key name. - */ - public String getSasKeyName() - { - return this.sharedAccessKeyName; - } - - /** - * Get the shared access signature (also referred as SAS Token) from the connection string - * @return Shared Access Signature - */ - public String getSharedAccessSignature() - { - return this.sharedAccessSignature; +public class ConnectionStringBuilder { + final static String endpointFormat = "amqps://%s.servicebus.windows.net"; + final static String endpointRawFormat = "amqps://%s"; + + final static String HostnameConfigName = "Hostname"; + final static String EndpointConfigName = "Endpoint"; + final static String SharedAccessKeyNameConfigName = "SharedAccessKeyName"; + final static String SharedAccessKeyConfigName = "SharedAccessKey"; + final static String SharedAccessSignatureConfigName = "SharedAccessSignature"; + final static String EntityPathConfigName = "EntityPath"; + final static String OperationTimeoutConfigName = "OperationTimeout"; + final static String RetryPolicyConfigName = "RetryPolicy"; + final static String KeyValueSeparator = "="; + final static String KeyValuePairDelimiter = ";"; + + private static final String AllKeyEnumerateRegex = "(" + HostnameConfigName + "|" + EndpointConfigName + "|" + SharedAccessKeyNameConfigName + + "|" + SharedAccessKeyConfigName + "|" + SharedAccessSignatureConfigName + "|" + EntityPathConfigName + "|" + OperationTimeoutConfigName + + "|" + RetryPolicyConfigName + ")"; + + private static final String KeysWithDelimitersRegex = KeyValuePairDelimiter + AllKeyEnumerateRegex + + KeyValueSeparator; + + private URI endpoint; + private String sharedAccessKeyName; + private String sharedAccessKey; + private String entityPath; + private String sharedAccessSignature; + private Duration operationTimeout; + private RetryPolicy retryPolicy; + + private ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, + final String sharedAccessKeyName, + final String sharedAccessKey, + final Duration operationTimeout, + final RetryPolicy retryPolicy) { + this.endpoint = endpointAddress; + this.sharedAccessKey = sharedAccessKey; + this.sharedAccessKeyName = sharedAccessKeyName; + this.operationTimeout = operationTimeout; + this.retryPolicy = retryPolicy; + this.entityPath = entityPath; + } + + private ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, + final String sharedAccessSignature, + final Duration operationTimeout, + final RetryPolicy retryPolicy) { + this.endpoint = endpointAddress; + this.sharedAccessSignature = sharedAccessSignature; + this.operationTimeout = operationTimeout; + this.retryPolicy = retryPolicy; + this.entityPath = entityPath; + } + + private ConnectionStringBuilder( + final String namespaceName, + final String entityPath, + final String sharedAccessKeyName, + final String sharedAccessKey, + final Duration operationTimeout, + final RetryPolicy retryPolicy) { + try { + this.endpoint = new URI(String.format(Locale.US, endpointFormat, namespaceName)); + } catch (URISyntaxException exception) { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Invalid namespace name: %s", namespaceName), + exception); } - /** - * Get the entity path value from the connection string - * @return Entity Path - */ - public String getEntityPath() - { - return this.entityPath; - } - - /** - * OperationTimeout is applied in erroneous situations to notify the caller about the relevant {@link ServiceBusException} - * @return operationTimeout - */ - public Duration getOperationTimeout() - { - return (this.operationTimeout == null ? MessagingFactory.DefaultOperationTimeout : this.operationTimeout); - } - - /** - * Set the OperationTimeout value in the Connection String. This value will be used by all operations which uses this {@link ConnectionStringBuilder}, unless explicitly over-ridden. - *

    ConnectionString with operationTimeout is not inter-operable between java and clients in other platforms. - * @param operationTimeout Operation Timeout - */ - public void setOperationTimeout(final Duration operationTimeout) - { - this.operationTimeout = operationTimeout; - } - - /** - * Get the retry policy instance that was created as part of this builder's creation. - * @return RetryPolicy applied for any operation performed using this ConnectionString - */ - @Deprecated - public RetryPolicy getRetryPolicy() - { - return (this.retryPolicy == null ? RetryPolicy.getDefault() : this.retryPolicy); - } - - /** - * Set the retry policy. - *

    RetryPolicy is not inter-operable with ServiceBus clients in other platforms. - * @param retryPolicy RetryPolicy applied for any operation performed using this ConnectionString - */ - @Deprecated - public void setRetryPolicy(final RetryPolicy retryPolicy) - { - this.retryPolicy = retryPolicy; - } - - /** - * Returns an inter-operable connection string that can be used to connect to ServiceBus Namespace - * @return connection string - */ - @Override - public String toString() - { - final StringBuilder connectionStringBuilder = new StringBuilder(); - if (this.endpoint != null) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EndpointConfigName, KeyValueSeparator, - this.endpoint.toString(), KeyValuePairDelimiter)); - } - - if (!StringUtil.isNullOrWhiteSpace(this.entityPath)) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EntityPathConfigName, - KeyValueSeparator, this.entityPath, KeyValuePairDelimiter)); - } - - if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKeyName)) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessKeyNameConfigName, - KeyValueSeparator, this.sharedAccessKeyName, KeyValuePairDelimiter)); - } - - if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKey)) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessKeyConfigName, - KeyValueSeparator, this.sharedAccessKey, KeyValuePairDelimiter)); - } - - if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessSignature)) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessSignatureConfigName, - KeyValueSeparator, this.sharedAccessSignature, KeyValuePairDelimiter)); - } - - if (this.operationTimeout != null) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", OperationTimeoutConfigName, - KeyValueSeparator, this.operationTimeout.toString(), KeyValuePairDelimiter)); - } - - if (this.retryPolicy != null) - { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", RetryPolicyConfigName, - KeyValueSeparator, this.retryPolicy.toString(), KeyValuePairDelimiter)); - } - - connectionStringBuilder.deleteCharAt(connectionStringBuilder.length() - 1); - return connectionStringBuilder.toString(); - } - - private void parseConnectionString(final String connectionString) - { - if (StringUtil.isNullOrWhiteSpace(connectionString)) - { - throw new IllegalConnectionStringFormatException(String.format("connectionString cannot be empty")); - } - - final String connection = KeyValuePairDelimiter + connectionString; - - final Pattern keyValuePattern = Pattern.compile(KeysWithDelimitersRegex, Pattern.CASE_INSENSITIVE); - final String[] values = keyValuePattern.split(connection); - final Matcher keys = keyValuePattern.matcher(connection); - - if (values == null || values.length <= 1 || keys.groupCount() == 0) - { - throw new IllegalConnectionStringFormatException("Connection String cannot be parsed."); - } - - if (!StringUtil.isNullOrWhiteSpace((values[0]))) - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Cannot parse part of ConnectionString: %s", values[0])); - } - - int valueIndex = 0; - while (keys.find()) - { - valueIndex++; - - String key = keys.group(); - key = key.substring(1, key.length() - 1); - - if (values.length < valueIndex + 1) - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Value for the connection string parameter name: %s, not found", key)); - } - - if (key.equalsIgnoreCase(EndpointConfigName)) - { - if (this.endpoint != null) - { - // we have parsed the endpoint once, which means we have multiple config which is not allowed - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", EndpointConfigName, HostnameConfigName)); - } - - try - { - this.endpoint = new URI(values[valueIndex]); - } - catch(URISyntaxException exception) - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "%s should be in format scheme://fullyQualifiedServiceBusNamespaceEndpointName", EndpointConfigName), - exception); - } - } - else if (key.equalsIgnoreCase(HostnameConfigName)) - { - if (this.endpoint != null) - { - // we have parsed the endpoint once, which means we have multiple config which is not allowed - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", EndpointConfigName, HostnameConfigName)); - } - - try - { - this.endpoint = new URI(String.format(Locale.US, endpointRawFormat, values[valueIndex])); - } - catch(URISyntaxException exception) - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "%s should be a fully quantified host name address", HostnameConfigName), - exception); - } - } - else if(key.equalsIgnoreCase(SharedAccessKeyNameConfigName)) - { - this.sharedAccessKeyName = values[valueIndex]; - } - else if(key.equalsIgnoreCase(SharedAccessKeyConfigName)) - { - this.sharedAccessKey = values[valueIndex]; - } - else if(key.equalsIgnoreCase(SharedAccessSignatureConfigName)) - { - this.sharedAccessSignature = values[valueIndex]; - } - else if (key.equalsIgnoreCase(EntityPathConfigName)) - { - this.entityPath = values[valueIndex]; - } - else if (key.equalsIgnoreCase(OperationTimeoutConfigName)) - { - try - { - this.operationTimeout = Duration.parse(values[valueIndex]); - } - catch(DateTimeParseException exception) - { - throw new IllegalConnectionStringFormatException("Invalid value specified for property 'Duration' in the ConnectionString.", exception); - } - } - else if (key.equalsIgnoreCase(RetryPolicyConfigName)) - { - this.retryPolicy = values[valueIndex].equals(ClientConstants.DEFAULT_RETRY) - ? RetryPolicy.getDefault() - : (values[valueIndex].equals(ClientConstants.NO_RETRY) ? RetryPolicy.getNoRetry() : null); - - if (this.retryPolicy == null) - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Connection string parameter '%s'='%s' is not recognized", - RetryPolicyConfigName, values[valueIndex])); - } - else - { - throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "Illegal connection string parameter name: %s", key)); - } - } - } + this.sharedAccessKey = sharedAccessKey; + this.sharedAccessKeyName = sharedAccessKeyName; + this.operationTimeout = operationTimeout; + this.retryPolicy = retryPolicy; + this.entityPath = entityPath; + } + + /** + * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} + * + * @param namespaceName Namespace name (dns suffix - ex: .servicebus.windows.net is not required) + * @param entityPath Entity path. For eventHubs case specify - eventHub name. + * @param sharedAccessKeyName Shared Access Key name + * @param sharedAccessKey Shared Access Key + */ + public ConnectionStringBuilder( + final String namespaceName, + final String entityPath, + final String sharedAccessKeyName, + final String sharedAccessKey) { + this(namespaceName, entityPath, sharedAccessKeyName, sharedAccessKey, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); + } + + + /** + * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} + * + * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName + * @param entityPath Entity path. For eventHubs case specify - eventHub name. + * @param sharedAccessKeyName Shared Access Key name + * @param sharedAccessKey Shared Access Key + */ + public ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, + final String sharedAccessKeyName, + final String sharedAccessKey) { + this(endpointAddress, entityPath, sharedAccessKeyName, sharedAccessKey, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); + } + + /** + * Build a connection string consumable by {@link com.microsoft.azure.eventhubs.EventHubClient#createFromConnectionString(String)} + * + * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName + * @param entityPath Entity path. For eventHubs case specify - eventHub name. + * @param sharedAccessSignature Shared Access Signature + */ + public ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, + final String sharedAccessSignature) { + this(endpointAddress, entityPath, sharedAccessSignature, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); + } + + /** + * ConnectionString format: + * Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY + * + * @param connectionString ServiceBus ConnectionString + * @throws IllegalConnectionStringFormatException when the format of the ConnectionString is not valid + */ + public ConnectionStringBuilder(String connectionString) { + this.parseConnectionString(connectionString); + } + + /** + * Get the endpoint which can be used to connect to the ServiceBus Namespace + * + * @return Endpoint + */ + public URI getEndpoint() { + return this.endpoint; + } + + /** + * Get the shared access policy key value from the connection string + * + * @return Shared Access Signature key + */ + public String getSasKey() { + return this.sharedAccessKey; + } + + /** + * Get the shared access policy owner name from the connection string + * + * @return Shared Access Signature key name. + */ + public String getSasKeyName() { + return this.sharedAccessKeyName; + } + + /** + * Get the shared access signature (also referred as SAS Token) from the connection string + * + * @return Shared Access Signature + */ + public String getSharedAccessSignature() { + return this.sharedAccessSignature; + } + + /** + * Get the entity path value from the connection string + * + * @return Entity Path + */ + public String getEntityPath() { + return this.entityPath; + } + + /** + * OperationTimeout is applied in erroneous situations to notify the caller about the relevant {@link ServiceBusException} + * + * @return operationTimeout + */ + public Duration getOperationTimeout() { + return (this.operationTimeout == null ? MessagingFactory.DefaultOperationTimeout : this.operationTimeout); + } + + /** + * Set the OperationTimeout value in the Connection String. This value will be used by all operations which uses this {@link ConnectionStringBuilder}, unless explicitly over-ridden. + *

    ConnectionString with operationTimeout is not inter-operable between java and clients in other platforms. + * + * @param operationTimeout Operation Timeout + */ + public void setOperationTimeout(final Duration operationTimeout) { + this.operationTimeout = operationTimeout; + } + + /** + * Get the retry policy instance that was created as part of this builder's creation. + * + * @return RetryPolicy applied for any operation performed using this ConnectionString + */ + @Deprecated + public RetryPolicy getRetryPolicy() { + return (this.retryPolicy == null ? RetryPolicy.getDefault() : this.retryPolicy); + } + + /** + * Set the retry policy. + *

    RetryPolicy is not inter-operable with ServiceBus clients in other platforms. + * + * @param retryPolicy RetryPolicy applied for any operation performed using this ConnectionString + */ + @Deprecated + public void setRetryPolicy(final RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + /** + * Returns an inter-operable connection string that can be used to connect to ServiceBus Namespace + * + * @return connection string + */ + @Override + public String toString() { + final StringBuilder connectionStringBuilder = new StringBuilder(); + if (this.endpoint != null) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EndpointConfigName, KeyValueSeparator, + this.endpoint.toString(), KeyValuePairDelimiter)); + } + + if (!StringUtil.isNullOrWhiteSpace(this.entityPath)) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EntityPathConfigName, + KeyValueSeparator, this.entityPath, KeyValuePairDelimiter)); + } + + if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKeyName)) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessKeyNameConfigName, + KeyValueSeparator, this.sharedAccessKeyName, KeyValuePairDelimiter)); + } + + if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKey)) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessKeyConfigName, + KeyValueSeparator, this.sharedAccessKey, KeyValuePairDelimiter)); + } + + if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessSignature)) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessSignatureConfigName, + KeyValueSeparator, this.sharedAccessSignature, KeyValuePairDelimiter)); + } + + if (this.operationTimeout != null) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", OperationTimeoutConfigName, + KeyValueSeparator, this.operationTimeout.toString(), KeyValuePairDelimiter)); + } + + if (this.retryPolicy != null) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", RetryPolicyConfigName, + KeyValueSeparator, this.retryPolicy.toString(), KeyValuePairDelimiter)); + } + + connectionStringBuilder.deleteCharAt(connectionStringBuilder.length() - 1); + return connectionStringBuilder.toString(); + } + + private void parseConnectionString(final String connectionString) { + if (StringUtil.isNullOrWhiteSpace(connectionString)) { + throw new IllegalConnectionStringFormatException(String.format("connectionString cannot be empty")); + } + + final String connection = KeyValuePairDelimiter + connectionString; + + final Pattern keyValuePattern = Pattern.compile(KeysWithDelimitersRegex, Pattern.CASE_INSENSITIVE); + final String[] values = keyValuePattern.split(connection); + final Matcher keys = keyValuePattern.matcher(connection); + + if (values == null || values.length <= 1 || keys.groupCount() == 0) { + throw new IllegalConnectionStringFormatException("Connection String cannot be parsed."); + } + + if (!StringUtil.isNullOrWhiteSpace((values[0]))) { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Cannot parse part of ConnectionString: %s", values[0])); + } + + int valueIndex = 0; + while (keys.find()) { + valueIndex++; + + String key = keys.group(); + key = key.substring(1, key.length() - 1); + + if (values.length < valueIndex + 1) { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Value for the connection string parameter name: %s, not found", key)); + } + + if (key.equalsIgnoreCase(EndpointConfigName)) { + if (this.endpoint != null) { + // we have parsed the endpoint once, which means we have multiple config which is not allowed + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", EndpointConfigName, HostnameConfigName)); + } + + try { + this.endpoint = new URI(values[valueIndex]); + } catch (URISyntaxException exception) { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "%s should be in format scheme://fullyQualifiedServiceBusNamespaceEndpointName", EndpointConfigName), + exception); + } + } else if (key.equalsIgnoreCase(HostnameConfigName)) { + if (this.endpoint != null) { + // we have parsed the endpoint once, which means we have multiple config which is not allowed + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", EndpointConfigName, HostnameConfigName)); + } + + try { + this.endpoint = new URI(String.format(Locale.US, endpointRawFormat, values[valueIndex])); + } catch (URISyntaxException exception) { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "%s should be a fully quantified host name address", HostnameConfigName), + exception); + } + } else if (key.equalsIgnoreCase(SharedAccessKeyNameConfigName)) { + this.sharedAccessKeyName = values[valueIndex]; + } else if (key.equalsIgnoreCase(SharedAccessKeyConfigName)) { + this.sharedAccessKey = values[valueIndex]; + } else if (key.equalsIgnoreCase(SharedAccessSignatureConfigName)) { + this.sharedAccessSignature = values[valueIndex]; + } else if (key.equalsIgnoreCase(EntityPathConfigName)) { + this.entityPath = values[valueIndex]; + } else if (key.equalsIgnoreCase(OperationTimeoutConfigName)) { + try { + this.operationTimeout = Duration.parse(values[valueIndex]); + } catch (DateTimeParseException exception) { + throw new IllegalConnectionStringFormatException("Invalid value specified for property 'Duration' in the ConnectionString.", exception); + } + } else if (key.equalsIgnoreCase(RetryPolicyConfigName)) { + this.retryPolicy = values[valueIndex].equals(ClientConstants.DEFAULT_RETRY) + ? RetryPolicy.getDefault() + : (values[valueIndex].equals(ClientConstants.NO_RETRY) ? RetryPolicy.getNoRetry() : null); + + if (this.retryPolicy == null) + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Connection string parameter '%s'='%s' is not recognized", + RetryPolicyConfigName, values[valueIndex])); + } else { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Illegal connection string parameter name: %s", key)); + } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ErrorContext.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ErrorContext.java index 1616086e6..1a56dd896 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ErrorContext.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ErrorContext.java @@ -6,23 +6,19 @@ import java.util.Locale; -public abstract class ErrorContext -{ - private final String namespaceName; +public abstract class ErrorContext { + private final String namespaceName; - ErrorContext(final String namespaceName) - { - this.namespaceName = namespaceName; - } + ErrorContext(final String namespaceName) { + this.namespaceName = namespaceName; + } - protected String getNamespaceName() - { - return this.namespaceName; - } + protected String getNamespaceName() { + return this.namespaceName; + } - @Override - public String toString() - { - return StringUtil.isNullOrEmpty(this.namespaceName) ? null : String.format(Locale.US, "NS: %s", this.namespaceName); - } + @Override + public String toString() { + return StringUtil.isNullOrEmpty(this.namespaceName) ? null : String.format(Locale.US, "NS: %s", this.namespaceName); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ExceptionUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ExceptionUtil.java index 8b3891ce2..79dd403a5 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ExceptionUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ExceptionUtil.java @@ -17,157 +17,113 @@ import com.microsoft.azure.servicebus.amqp.AmqpException; import com.microsoft.azure.servicebus.amqp.AmqpResponseCode; -final class ExceptionUtil -{ - static Exception toException(ErrorCondition errorCondition) - { - if (errorCondition == null) - { - throw new IllegalArgumentException("'null' errorCondition cannot be translated to ServiceBusException"); - } - - if (errorCondition.getCondition() == ClientConstants.TIMEOUT_ERROR) - { - return new ServiceBusException(ClientConstants.DEFAULT_IS_TRANSIENT, new TimeoutException(errorCondition.getDescription())); - } - else if (errorCondition.getCondition() == ClientConstants.SERVER_BUSY_ERROR) - { - return new ServerBusyException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.NotFound) - { - return new IllegalEntityException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == ClientConstants.ENTITY_DISABLED_ERROR) - { - return new IllegalEntityException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.Stolen) - { - return new ReceiverDisconnectedException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.UnauthorizedAccess) - { - return new AuthorizationFailedException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.PayloadSizeExceeded) - { - return new PayloadSizeExceededException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.InternalError) - { - return new ServiceBusException(true, new AmqpException(errorCondition)); - } - else if (errorCondition.getCondition() == ClientConstants.ARGUMENT_ERROR) - { - return new ServiceBusException(false, errorCondition.getDescription(), new AmqpException(errorCondition)); - } - else if (errorCondition.getCondition() == ClientConstants.ARGUMENT_OUT_OF_RANGE_ERROR) - { - return new ServiceBusException(false, errorCondition.getDescription(), new AmqpException(errorCondition)); - } - else if (errorCondition.getCondition() == AmqpErrorCode.NotImplemented) - { - return new UnsupportedOperationException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.NotAllowed) - { - return new UnsupportedOperationException(errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == ClientConstants.PARTITION_NOT_OWNED_ERROR) - { - return new ServiceBusException(false, errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == ClientConstants.STORE_LOCK_LOST_ERROR) - { - return new ServiceBusException(false, errorCondition.getDescription()); - } - else if (errorCondition.getCondition() == AmqpErrorCode.AmqpLinkDetachForced) - { - return new ServiceBusException(true, new AmqpException(errorCondition)); - } - else if (errorCondition.getCondition() == AmqpErrorCode.ResourceLimitExceeded) - { - return new QuotaExceededException(new AmqpException(errorCondition)); - } - - return new ServiceBusException(ClientConstants.DEFAULT_IS_TRANSIENT, errorCondition.getDescription()); - } - - static Exception amqpResponseCodeToException(final int statusCode, final String statusDescription) - { - final AmqpResponseCode amqpResponseCode = AmqpResponseCode.valueOf(statusCode); - if (amqpResponseCode == null) +final class ExceptionUtil { + static Exception toException(ErrorCondition errorCondition) { + if (errorCondition == null) { + throw new IllegalArgumentException("'null' errorCondition cannot be translated to ServiceBusException"); + } + + if (errorCondition.getCondition() == ClientConstants.TIMEOUT_ERROR) { + return new ServiceBusException(ClientConstants.DEFAULT_IS_TRANSIENT, new TimeoutException(errorCondition.getDescription())); + } else if (errorCondition.getCondition() == ClientConstants.SERVER_BUSY_ERROR) { + return new ServerBusyException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.NotFound) { + return new IllegalEntityException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == ClientConstants.ENTITY_DISABLED_ERROR) { + return new IllegalEntityException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.Stolen) { + return new ReceiverDisconnectedException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.UnauthorizedAccess) { + return new AuthorizationFailedException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.PayloadSizeExceeded) { + return new PayloadSizeExceededException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.InternalError) { + return new ServiceBusException(true, new AmqpException(errorCondition)); + } else if (errorCondition.getCondition() == ClientConstants.ARGUMENT_ERROR) { + return new ServiceBusException(false, errorCondition.getDescription(), new AmqpException(errorCondition)); + } else if (errorCondition.getCondition() == ClientConstants.ARGUMENT_OUT_OF_RANGE_ERROR) { + return new ServiceBusException(false, errorCondition.getDescription(), new AmqpException(errorCondition)); + } else if (errorCondition.getCondition() == AmqpErrorCode.NotImplemented) { + return new UnsupportedOperationException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.NotAllowed) { + return new UnsupportedOperationException(errorCondition.getDescription()); + } else if (errorCondition.getCondition() == ClientConstants.PARTITION_NOT_OWNED_ERROR) { + return new ServiceBusException(false, errorCondition.getDescription()); + } else if (errorCondition.getCondition() == ClientConstants.STORE_LOCK_LOST_ERROR) { + return new ServiceBusException(false, errorCondition.getDescription()); + } else if (errorCondition.getCondition() == AmqpErrorCode.AmqpLinkDetachForced) { + return new ServiceBusException(true, new AmqpException(errorCondition)); + } else if (errorCondition.getCondition() == AmqpErrorCode.ResourceLimitExceeded) { + return new QuotaExceededException(new AmqpException(errorCondition)); + } + + return new ServiceBusException(ClientConstants.DEFAULT_IS_TRANSIENT, errorCondition.getDescription()); + } + + static Exception amqpResponseCodeToException(final int statusCode, final String statusDescription) { + final AmqpResponseCode amqpResponseCode = AmqpResponseCode.valueOf(statusCode); + if (amqpResponseCode == null) + return new ServiceBusException(true, String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + + switch (amqpResponseCode) { + case BAD_REQUEST: + return new IllegalArgumentException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + case NOT_FOUND: + return new AmqpException(new ErrorCondition(AmqpErrorCode.NotFound, statusDescription)); + case FORBIDDEN: + return new QuotaExceededException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + case UNAUTHORIZED: + return new AuthorizationFailedException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + default: return new ServiceBusException(true, String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + } + } + + static void completeExceptionally(CompletableFuture future, Exception exception, IErrorContextProvider contextProvider) { + if (exception != null && exception instanceof ServiceBusException) { + ErrorContext errorContext = contextProvider.getContext(); + ((ServiceBusException) exception).setContext(errorContext); + } + + future.completeExceptionally(exception); + } + + // not a specific message related error + static boolean isGeneralSendError(Symbol amqpError) { + return (amqpError == ClientConstants.SERVER_BUSY_ERROR + || amqpError == ClientConstants.TIMEOUT_ERROR + || amqpError == AmqpErrorCode.ResourceLimitExceeded); + } + + static String getTrackingIDAndTimeToLog() { + return String.format(Locale.US, "TrackingId: %s, at: %s", UUID.randomUUID().toString(), ZonedDateTime.now()); + } - switch (amqpResponseCode) { - case BAD_REQUEST: - return new IllegalArgumentException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); - case NOT_FOUND: - return new AmqpException(new ErrorCondition(AmqpErrorCode.NotFound, statusDescription)); - case FORBIDDEN: - return new QuotaExceededException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); - case UNAUTHORIZED: - return new AuthorizationFailedException(String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); - default: - return new ServiceBusException(true, String.format(ClientConstants.AMQP_PUT_TOKEN_FAILED_ERROR, statusCode, statusDescription)); + static String toStackTraceString(final Throwable exception, final String customErrorMessage) { + final StringBuilder builder = new StringBuilder(); + + if (!StringUtil.isNullOrEmpty(customErrorMessage)) { + builder.append(customErrorMessage); + builder.append(System.lineSeparator()); + } + + builder.append(exception.getMessage()); + if (exception.getStackTrace() != null) + for (StackTraceElement ste : exception.getStackTrace()) { + builder.append(System.lineSeparator()); + builder.append(ste.toString()); } + + Throwable innerException = exception.getCause(); + if (innerException != null) { + builder.append("Cause: " + innerException.getMessage()); + if (innerException.getStackTrace() != null) + for (StackTraceElement ste : innerException.getStackTrace()) { + builder.append(System.lineSeparator()); + builder.append(ste.toString()); + } } - static void completeExceptionally(CompletableFuture future, Exception exception, IErrorContextProvider contextProvider) - { - if (exception != null && exception instanceof ServiceBusException) - { - ErrorContext errorContext = contextProvider.getContext(); - ((ServiceBusException) exception).setContext(errorContext); - } - - future.completeExceptionally(exception); - } - - // not a specific message related error - static boolean isGeneralSendError(Symbol amqpError) - { - return (amqpError == ClientConstants.SERVER_BUSY_ERROR - || amqpError == ClientConstants.TIMEOUT_ERROR - || amqpError == AmqpErrorCode.ResourceLimitExceeded); - } - - static String getTrackingIDAndTimeToLog() - { - return String.format(Locale.US, "TrackingId: %s, at: %s", UUID.randomUUID().toString(), ZonedDateTime.now()); - } - - static String toStackTraceString(final Throwable exception, final String customErrorMessage) - { - final StringBuilder builder = new StringBuilder(); - - if (!StringUtil.isNullOrEmpty(customErrorMessage)) - { - builder.append(customErrorMessage); - builder.append(System.lineSeparator()); - } - - builder.append(exception.getMessage()); - if (exception.getStackTrace() != null) - for (StackTraceElement ste: exception.getStackTrace()) - { - builder.append(System.lineSeparator()); - builder.append(ste.toString()); - } - - Throwable innerException = exception.getCause(); - if (innerException != null) - { - builder.append("Cause: " + innerException.getMessage()); - if (innerException.getStackTrace() != null) - for (StackTraceElement ste: innerException.getStackTrace()) - { - builder.append(System.lineSeparator()); - builder.append(ste.toString()); - } - } - - return builder.toString(); - } + return builder.toString(); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/FaultTolerantObject.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/FaultTolerantObject.java index c6fd9cb49..ca769720e 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/FaultTolerantObject.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/FaultTolerantObject.java @@ -15,7 +15,7 @@ import com.microsoft.azure.servicebus.amqp.ReactorDispatcher; public class FaultTolerantObject { - + final IOperation openTask; final IOperation closeTask; final Queue> openCallbacks; @@ -24,35 +24,35 @@ public class FaultTolerantObject { T innerObject; boolean creatingNewInnerObject; boolean closingInnerObject; - + public FaultTolerantObject( - final IOperation openAsync, - final IOperation closeAsync) { - + final IOperation openAsync, + final IOperation closeAsync) { + this.openTask = openAsync; this.closeTask = closeAsync; this.openCallbacks = new ConcurrentLinkedQueue<>(); this.closeCallbacks = new ConcurrentLinkedQueue<>(); } - + // should be invoked from reactor thread public T unsafeGetIfOpened() { if (innerObject != null && innerObject.getState() == IIOObject.IOObjectState.OPENED) return innerObject; - + return null; } - + public void runOnOpenedObject( - final ReactorDispatcher dispatcher, - final IOperationResult openCallback) { - + final ReactorDispatcher dispatcher, + final IOperationResult openCallback) { + try { dispatcher.invoke(new DispatchHandler() { @Override public void onEvent() { - if (!creatingNewInnerObject + if (!creatingNewInnerObject && (innerObject == null || innerObject.getState() == IIOObject.IOObjectState.CLOSED || innerObject.getState() == IIOObject.IOObjectState.CLOSING)) { creatingNewInnerObject = true; openCallbacks.offer(openCallback); @@ -61,25 +61,24 @@ public void onEvent() { public void onComplete(T result) { creatingNewInnerObject = false; innerObject = result; - for (IOperationResult callback: openCallbacks) + for (IOperationResult callback : openCallbacks) callback.onComplete(result); - + openCallbacks.clear(); } + @Override public void onError(Exception error) { creatingNewInnerObject = false; - for (IOperationResult callback: openCallbacks) + for (IOperationResult callback : openCallbacks) callback.onError(error); - + openCallbacks.clear(); } }); - } - else if (innerObject != null && innerObject.getState() == IIOObject.IOObjectState.OPENED) { + } else if (innerObject != null && innerObject.getState() == IIOObject.IOObjectState.OPENED) { openCallback.onComplete(innerObject); - } - else { + } else { openCallbacks.offer(openCallback); } } @@ -88,42 +87,40 @@ else if (innerObject != null && innerObject.getState() == IIOObject.IOObjectStat openCallback.onError(ioException); } } - + public void close( final ReactorDispatcher dispatcher, final IOperationResult closeCallback) { - + try { dispatcher.invoke(new DispatchHandler() { @Override public void onEvent() { if (innerObject == null || innerObject.getState() == IIOObject.IOObjectState.CLOSED) { closeCallback.onComplete(null); - } - else if (!closingInnerObject && (innerObject.getState() == IIOObject.IOObjectState.OPENED || innerObject.getState() == IIOObject.IOObjectState.OPENING)) { + } else if (!closingInnerObject && (innerObject.getState() == IIOObject.IOObjectState.OPENED || innerObject.getState() == IIOObject.IOObjectState.OPENING)) { closingInnerObject = true; closeCallbacks.offer(closeCallback); closeTask.run(new IOperationResult() { @Override public void onComplete(Void result) { closingInnerObject = false; - for (IOperationResult callback: closeCallbacks) + for (IOperationResult callback : closeCallbacks) callback.onComplete(result); - + closeCallbacks.clear(); } @Override public void onError(Exception error) { closingInnerObject = false; - for (IOperationResult callback: closeCallbacks) + for (IOperationResult callback : closeCallbacks) callback.onError(error); - + closeCallbacks.clear(); } }); - } - else { + } else { closeCallbacks.offer(closeCallback); } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IConnectionFactory.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IConnectionFactory.java index 211dfb584..2493becce 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IConnectionFactory.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IConnectionFactory.java @@ -6,7 +6,6 @@ import org.apache.qpid.proton.engine.Connection; -interface IConnectionFactory -{ - Connection getConnection(); +interface IConnectionFactory { + Connection getConnection(); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IErrorContextProvider.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IErrorContextProvider.java index 31217c5f1..ec4f0c357 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IErrorContextProvider.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IErrorContextProvider.java @@ -4,7 +4,6 @@ */ package com.microsoft.azure.servicebus; -interface IErrorContextProvider -{ - ErrorContext getContext(); +interface IErrorContextProvider { + ErrorContext getContext(); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IReceiverSettingsProvider.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IReceiverSettingsProvider.java index 421e831bb..d1c4e04f1 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IReceiverSettingsProvider.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IReceiverSettingsProvider.java @@ -10,11 +10,10 @@ import org.apache.qpid.proton.amqp.UnknownDescribedType; import org.apache.qpid.proton.message.Message; -public interface IReceiverSettingsProvider -{ - public Map getFilter(final Message lastReceivedMessage); +public interface IReceiverSettingsProvider { + public Map getFilter(final Message lastReceivedMessage); - public Map getProperties(); - - public Symbol[] getDesiredCapabilities(); + public Map getProperties(); + + public Symbol[] getDesiredCapabilities(); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java index 32eab7184..4f11ce5ee 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ISessionProvider.java @@ -10,10 +10,9 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.engine.Session; -interface ISessionProvider -{ - Session getSession( - final String path, - final Consumer onSessionOpen, - final BiConsumer onSessionOpenError); +interface ISessionProvider { + Session getSession( + final String path, + final Consumer onSessionOpen, + final BiConsumer onSessionOpenError); } \ No newline at end of file diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ITimeoutErrorHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ITimeoutErrorHandler.java index b82f4145a..6a4a5573e 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ITimeoutErrorHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ITimeoutErrorHandler.java @@ -8,9 +8,8 @@ // https://issues.apache.org/jira/browse/PROTON-1185 // https://issues.apache.org/jira/browse/PROTON-1171 // This handler is built to - recover client from the underlying TransportStack-Stuck situation -public interface ITimeoutErrorHandler -{ - public void reportTimeoutError(); +public interface ITimeoutErrorHandler { + public void reportTimeoutError(); - public void resetTimeoutErrorTracking(); + public void resetTimeoutErrorTracking(); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalConnectionStringFormatException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalConnectionStringFormatException.java index b4475eea3..ae98a8f32 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalConnectionStringFormatException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalConnectionStringFormatException.java @@ -7,27 +7,22 @@ /** * This exception is thrown when the connection string provided does not meet the requirement for connection. */ -public class IllegalConnectionStringFormatException extends IllegalArgumentException -{ - private static final long serialVersionUID = 2514898858133972030L; +public class IllegalConnectionStringFormatException extends IllegalArgumentException { + private static final long serialVersionUID = 2514898858133972030L; - IllegalConnectionStringFormatException() - { - } + IllegalConnectionStringFormatException() { + } - IllegalConnectionStringFormatException(String detail) - { - super(detail); - } + IllegalConnectionStringFormatException(String detail) { + super(detail); + } - IllegalConnectionStringFormatException(Throwable cause) - { - super(cause); - } + IllegalConnectionStringFormatException(Throwable cause) { + super(cause); + } - IllegalConnectionStringFormatException(String detail, Throwable cause) - { - super(detail, cause); - } + IllegalConnectionStringFormatException(String detail, Throwable cause) { + super(detail, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalEntityException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalEntityException.java index 02be77a86..acb2fedbc 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalEntityException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IllegalEntityException.java @@ -10,30 +10,25 @@ *

  • When the entity user attempted to connect does not exist *
  • The entity user wants to connect is disabled * - * + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class IllegalEntityException extends ServiceBusException -{ - private static final long serialVersionUID = 1842057379278310290L; +public class IllegalEntityException extends ServiceBusException { + private static final long serialVersionUID = 1842057379278310290L; - IllegalEntityException() - { - super(false); - } + IllegalEntityException() { + super(false); + } - public IllegalEntityException(final String message) - { - super(false, message); - } + public IllegalEntityException(final String message) { + super(false, message); + } - public IllegalEntityException(final Throwable cause) - { - super(false, cause); - } + public IllegalEntityException(final Throwable cause) { + super(false, cause); + } - public IllegalEntityException(final String message, final Throwable cause) - { - super(false, message, cause); - } + public IllegalEntityException(final String message, final Throwable cause) { + super(false, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IteratorUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IteratorUtil.java index c9170b312..57e98c06f 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IteratorUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/IteratorUtil.java @@ -6,58 +6,46 @@ import java.util.Iterator; -public final class IteratorUtil -{ - private IteratorUtil() - { - } - - public static boolean sizeEquals(Iterable iterable, int expectedSize) - { - Iterator iterator = iterable.iterator(); - - int currentSize = 0; - while(iterator.hasNext()) - { - if (expectedSize > currentSize) - { - currentSize++; - iterator.next(); - continue; - } - else - { - return false; - } - } - - return true; - } - - public static T getLast(Iterator iterator) - { - T last = null; - while(iterator.hasNext()) - { - last = iterator.next(); - } - - return last; - } - - public static T getFirst(final Iterable iterable) - { - if (iterable == null) - { - return null; - } - - final Iterator iterator = iterable.iterator(); - if (iterator == null) - { - return null; - } - - return iterator.hasNext() ? iterator.next() : null; - } +public final class IteratorUtil { + private IteratorUtil() { + } + + public static boolean sizeEquals(Iterable iterable, int expectedSize) { + Iterator iterator = iterable.iterator(); + + int currentSize = 0; + while (iterator.hasNext()) { + if (expectedSize > currentSize) { + currentSize++; + iterator.next(); + continue; + } else { + return false; + } + } + + return true; + } + + public static T getLast(Iterator iterator) { + T last = null; + while (iterator.hasNext()) { + last = iterator.next(); + } + + return last; + } + + public static T getFirst(final Iterable iterable) { + if (iterable == null) { + return null; + } + + final Iterator iterator = iterable.iterator(); + if (iterator == null) { + return null; + } + + return iterator.hasNext() ? iterator.next() : null; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java index ec0f1d78b..7500d4d05 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java @@ -46,84 +46,75 @@ * Common Receiver that abstracts all amqp related details * translates event-driven reactor model into async receive Api */ -public final class MessageReceiver extends ClientEntity implements IAmqpReceiver, IErrorContextProvider -{ - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private static final int MIN_TIMEOUT_DURATION_MILLIS = 20; - - private final ConcurrentLinkedQueue pendingReceives; - private final MessagingFactory underlyingFactory; - private final String receivePath; - private final Runnable onOperationTimedout; - private final Duration operationTimeout; - private final CompletableFuture linkClose; - private final Object prefetchCountSync; - private final IReceiverSettingsProvider settingsProvider; +public final class MessageReceiver extends ClientEntity implements IAmqpReceiver, IErrorContextProvider { + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private static final int MIN_TIMEOUT_DURATION_MILLIS = 20; + + private final ConcurrentLinkedQueue pendingReceives; + private final MessagingFactory underlyingFactory; + private final String receivePath; + private final Runnable onOperationTimedout; + private final Duration operationTimeout; + private final CompletableFuture linkClose; + private final Object prefetchCountSync; + private final IReceiverSettingsProvider settingsProvider; private final String tokenAudience; private final ActiveClientTokenManager activeClientTokenManager; private final WorkItem linkOpen; - private final ConcurrentLinkedQueue prefetchedMessages; + private final ConcurrentLinkedQueue prefetchedMessages; private final ReceiveWork receiveWork; private final CreateAndReceive createAndReceive; - - private int prefetchCount; + + private int prefetchCount; private Receiver receiveLink; - private Duration receiveTimeout; + private Duration receiveTimeout; private Message lastReceivedMessage; - private Exception lastKnownLinkError; - private int nextCreditToFlow; + private Exception lastKnownLinkError; + private int nextCreditToFlow; private boolean creatingLink; private ScheduledFuture openTimer; private ScheduledFuture closeTimer; - private MessageReceiver(final MessagingFactory factory, - final String name, - final String recvPath, - final int prefetchCount, - final IReceiverSettingsProvider settingsProvider) - { - super(name, factory); - - this.underlyingFactory = factory; - this.operationTimeout = factory.getOperationTimeout(); - this.receivePath = recvPath; - this.prefetchCount = prefetchCount; - this.prefetchedMessages = new ConcurrentLinkedQueue<>(); - this.linkClose = new CompletableFuture<>(); - this.lastKnownLinkError = null; - this.receiveTimeout = factory.getOperationTimeout(); - this.prefetchCountSync = new Object(); + private MessageReceiver(final MessagingFactory factory, + final String name, + final String recvPath, + final int prefetchCount, + final IReceiverSettingsProvider settingsProvider) { + super(name, factory); + + this.underlyingFactory = factory; + this.operationTimeout = factory.getOperationTimeout(); + this.receivePath = recvPath; + this.prefetchCount = prefetchCount; + this.prefetchedMessages = new ConcurrentLinkedQueue<>(); + this.linkClose = new CompletableFuture<>(); + this.lastKnownLinkError = null; + this.receiveTimeout = factory.getOperationTimeout(); + this.prefetchCountSync = new Object(); this.settingsProvider = settingsProvider; this.linkOpen = new WorkItem<>(new CompletableFuture<>(), factory.getOperationTimeout()); - - this.pendingReceives = new ConcurrentLinkedQueue<>(); - - // onOperationTimeout delegate - per receive call - this.onOperationTimedout = new Runnable() - { - public void run() - { + + this.pendingReceives = new ConcurrentLinkedQueue<>(); + + // onOperationTimeout delegate - per receive call + this.onOperationTimedout = new Runnable() { + public void run() { WorkItem> topWorkItem = null; - while((topWorkItem = MessageReceiver.this.pendingReceives.peek()) != null) - { - if (topWorkItem.getTimeoutTracker().remaining().toMillis() <= MessageReceiver.MIN_TIMEOUT_DURATION_MILLIS) - { + while ((topWorkItem = MessageReceiver.this.pendingReceives.peek()) != null) { + if (topWorkItem.getTimeoutTracker().remaining().toMillis() <= MessageReceiver.MIN_TIMEOUT_DURATION_MILLIS) { WorkItem> dequedWorkItem = MessageReceiver.this.pendingReceives.poll(); if (dequedWorkItem != null && dequedWorkItem.getWork() != null && !dequedWorkItem.getWork().isDone()) { - dequedWorkItem.getWork().complete(null); - } - else - break; - } - else - { + dequedWorkItem.getWork().complete(null); + } else + break; + } else { MessageReceiver.this.scheduleOperationTimer(topWorkItem.getTimeoutTracker()); break; } } } - }; - + }; + this.receiveWork = new ReceiveWork(); this.createAndReceive = new CreateAndReceive(); @@ -135,7 +126,7 @@ public void run() @Override public void run() { try { - underlyingFactory.getCBSChannel().sendToken( + underlyingFactory.getCBSChannel().sendToken( underlyingFactory.getReactorScheduler(), underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), tokenAudience, @@ -144,619 +135,527 @@ public void run() { public void onComplete(Void result) { if (TRACE_LOGGER.isLoggable(Level.FINE)) { TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, - "path[%s], linkName[%s] - token renewed", receivePath, receiveLink.getName())); + String.format(Locale.US, + "path[%s], linkName[%s] - token renewed", receivePath, receiveLink.getName())); } } + @Override public void onError(Exception error) { if (TRACE_LOGGER.isLoggable(Level.WARNING)) { TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, - "path[%s], linkName[%s], tokenRenewalFailure[%s]", receivePath, receiveLink.getName(), error.getMessage())); + String.format(Locale.US, + "path[%s], linkName[%s], tokenRenewalFailure[%s]", receivePath, receiveLink.getName(), error.getMessage())); } } }); - } - catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, - "path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", receivePath, receiveLink.getName(), exception.getMessage())); - } + } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", receivePath, receiveLink.getName(), exception.getMessage())); } } - }, - ClientConstants.TOKEN_REFRESH_INTERVAL); - } - - // @param connection Connection on which the MessageReceiver's receive Amqp link need to be created on. - // Connection has to be associated with Reactor before Creating a receiver on it. - public static CompletableFuture create( - final MessagingFactory factory, - final String name, - final String recvPath, - final int prefetchCount, - final IReceiverSettingsProvider settingsProvider) - { - MessageReceiver msgReceiver = new MessageReceiver( - factory, - name, - recvPath, - prefetchCount, - settingsProvider); - return msgReceiver.createLink(); - } - - public String getReceivePath() - { + } + }, + ClientConstants.TOKEN_REFRESH_INTERVAL); + } + + // @param connection Connection on which the MessageReceiver's receive Amqp link need to be created on. + // Connection has to be associated with Reactor before Creating a receiver on it. + public static CompletableFuture create( + final MessagingFactory factory, + final String name, + final String recvPath, + final int prefetchCount, + final IReceiverSettingsProvider settingsProvider) { + MessageReceiver msgReceiver = new MessageReceiver( + factory, + name, + recvPath, + prefetchCount, + settingsProvider); + return msgReceiver.createLink(); + } + + public String getReceivePath() { return this.receivePath; } - private CompletableFuture createLink() - { - this.scheduleLinkOpenTimeout(this.linkOpen.getTimeoutTracker()); - try - { - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - MessageReceiver.this.createReceiveLink(); - } - }); - } - catch (IOException ioException) - { - this.linkOpen.getWork().completeExceptionally(new ServiceBusException(false, "Failed to create Receiver, see cause for more details.", ioException)); - } - - return this.linkOpen.getWork(); - } - - private List receiveCore(final int messageCount) - { - List returnMessages = null; - Message currentMessage; - - while ((currentMessage = this.pollPrefetchQueue()) != null) - { - if (returnMessages == null) - { - returnMessages = new LinkedList<>(); - } - - returnMessages.add(currentMessage); - if (returnMessages.size() >= messageCount) - { - break; - } - } - - return returnMessages; - } - - public int getPrefetchCount() - { - synchronized (this.prefetchCountSync) - { - return this.prefetchCount; - } - } - - public void setPrefetchCount(final int value) throws ServiceBusException - { - final int deltaPrefetchCount; - synchronized (this.prefetchCountSync) - { - deltaPrefetchCount = this.prefetchCount - value; - this.prefetchCount = value; - } - - try - { - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - sendFlow(deltaPrefetchCount); - } - }); - } - catch (IOException ioException) - { - throw new ServiceBusException(false, "Setting prefetch count failed, see cause for more details", ioException); - } - } - - public Duration getReceiveTimeout() - { - return this.receiveTimeout; - } - - public void setReceiveTimeout(final Duration value) - { - this.receiveTimeout = value; - } - - public CompletableFuture> receive(final int maxMessageCount) - { - this.throwIfClosed(this.lastKnownLinkError); - - if (maxMessageCount <= 0 || maxMessageCount > this.prefetchCount) - { - throw new IllegalArgumentException(String.format(Locale.US, "parameter 'maxMessageCount' should be a positive number and should be less than prefetchCount(%s)", this.prefetchCount)); - } - - if (this.pendingReceives.isEmpty()) - { - this.scheduleOperationTimer(TimeoutTracker.create(this.receiveTimeout)); - } - - CompletableFuture> onReceive = new CompletableFuture<>(); + private CompletableFuture createLink() { + this.scheduleLinkOpenTimeout(this.linkOpen.getTimeoutTracker()); + try { + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + @Override + public void onEvent() { + MessageReceiver.this.createReceiveLink(); + } + }); + } catch (IOException ioException) { + this.linkOpen.getWork().completeExceptionally(new ServiceBusException(false, "Failed to create Receiver, see cause for more details.", ioException)); + } + + return this.linkOpen.getWork(); + } + + private List receiveCore(final int messageCount) { + List returnMessages = null; + Message currentMessage; + + while ((currentMessage = this.pollPrefetchQueue()) != null) { + if (returnMessages == null) { + returnMessages = new LinkedList<>(); + } + + returnMessages.add(currentMessage); + if (returnMessages.size() >= messageCount) { + break; + } + } + + return returnMessages; + } + + public int getPrefetchCount() { + synchronized (this.prefetchCountSync) { + return this.prefetchCount; + } + } + + public void setPrefetchCount(final int value) throws ServiceBusException { + final int deltaPrefetchCount; + synchronized (this.prefetchCountSync) { + deltaPrefetchCount = this.prefetchCount - value; + this.prefetchCount = value; + } + + try { + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + @Override + public void onEvent() { + sendFlow(deltaPrefetchCount); + } + }); + } catch (IOException ioException) { + throw new ServiceBusException(false, "Setting prefetch count failed, see cause for more details", ioException); + } + } + + public Duration getReceiveTimeout() { + return this.receiveTimeout; + } + + public void setReceiveTimeout(final Duration value) { + this.receiveTimeout = value; + } + + public CompletableFuture> receive(final int maxMessageCount) { + this.throwIfClosed(this.lastKnownLinkError); + + if (maxMessageCount <= 0 || maxMessageCount > this.prefetchCount) { + throw new IllegalArgumentException(String.format(Locale.US, "parameter 'maxMessageCount' should be a positive number and should be less than prefetchCount(%s)", this.prefetchCount)); + } + + if (this.pendingReceives.isEmpty()) { + this.scheduleOperationTimer(TimeoutTracker.create(this.receiveTimeout)); + } + + CompletableFuture> onReceive = new CompletableFuture<>(); pendingReceives.offer(new ReceiveWorkItem(onReceive, receiveTimeout, maxMessageCount)); - - try { + + try { this.underlyingFactory.scheduleOnReactorThread(this.createAndReceive); - } - catch (IOException ioException) { + } catch (IOException ioException) { onReceive.completeExceptionally(new OperationCancelledException("Receive failed while dispatching to Reactor, see cause for more details.", ioException)); - } - - return onReceive; - } + } - @Override - public void onOpenComplete(Exception exception) - { + return onReceive; + } + + @Override + public void onOpenComplete(Exception exception) { this.creatingLink = false; - - if (exception == null) - { + + if (exception == null) { if (this.getIsClosingOrClosed()) { this.receiveLink.close(); return; } - - if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) - { - this.linkOpen.getWork().complete(this); + + if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) { + this.linkOpen.getWork().complete(this); if (this.openTimer != null) this.openTimer.cancel(false); - } - - this.lastKnownLinkError = null; - - this.underlyingFactory.getRetryPolicy().resetRetryCount(this.underlyingFactory.getClientId()); - - this.nextCreditToFlow = 0; - this.sendFlow(this.prefetchCount - this.prefetchedMessages.size()); - - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s]", - this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), this.prefetchCount)); - } - } - else - { - if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) - { - this.setClosed(); - ExceptionUtil.completeExceptionally(this.linkOpen.getWork(), exception, this); + } + + this.lastKnownLinkError = null; + + this.underlyingFactory.getRetryPolicy().resetRetryCount(this.underlyingFactory.getClientId()); + + this.nextCreditToFlow = 0; + this.sendFlow(this.prefetchCount - this.prefetchedMessages.size()); + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s]", + this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), this.prefetchCount)); + } + } else { + if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) { + this.setClosed(); + ExceptionUtil.completeExceptionally(this.linkOpen.getWork(), exception, this); if (this.openTimer != null) this.openTimer.cancel(false); - } - - this.lastKnownLinkError = exception; - } - } - - @Override - public void onReceiveComplete(Delivery delivery) - { - int msgSize = delivery.pending(); - byte[] buffer = new byte[msgSize]; - - int read = receiveLink.recv(buffer, 0, msgSize); - - Message message = Proton.message(); - message.decode(buffer, 0, read); - - delivery.settle(); - - this.prefetchedMessages.add(message); - this.underlyingFactory.getRetryPolicy().resetRetryCount(this.getClientId()); - + } + + this.lastKnownLinkError = exception; + } + } + + @Override + public void onReceiveComplete(Delivery delivery) { + int msgSize = delivery.pending(); + byte[] buffer = new byte[msgSize]; + + int read = receiveLink.recv(buffer, 0, msgSize); + + Message message = Proton.message(); + message.decode(buffer, 0, read); + + delivery.settle(); + + this.prefetchedMessages.add(message); + this.underlyingFactory.getRetryPolicy().resetRetryCount(this.getClientId()); + this.receiveWork.onEvent(); - } + } - public void onError(final ErrorCondition error) - { - final Exception completionException = ExceptionUtil.toException(error); - this.onError(completionException); - } + public void onError(final ErrorCondition error) { + final Exception completionException = ExceptionUtil.toException(error); + this.onError(completionException); + } - @Override - public void onError(final Exception exception) - { + @Override + public void onError(final Exception exception) { this.prefetchedMessages.clear(); - this.underlyingFactory.deregisterForConnectionError(this.receiveLink); + this.underlyingFactory.deregisterForConnectionError(this.receiveLink); - if (this.getIsClosingOrClosed()) - { + if (this.getIsClosingOrClosed()) { if (this.closeTimer != null) this.closeTimer.cancel(false); - WorkItem> workItem = null; - final boolean isTransientException = exception == null || - (exception instanceof ServiceBusException && ((ServiceBusException) exception).getIsTransient()); - while ((workItem = this.pendingReceives.poll()) != null) - { - final CompletableFuture> future = workItem.getWork(); - if (isTransientException) - { - future.complete(null); - } - else - { - ExceptionUtil.completeExceptionally(future, exception, this); - } - } - + WorkItem> workItem = null; + final boolean isTransientException = exception == null || + (exception instanceof ServiceBusException && ((ServiceBusException) exception).getIsTransient()); + while ((workItem = this.pendingReceives.poll()) != null) { + final CompletableFuture> future = workItem.getWork(); + if (isTransientException) { + future.complete(null); + } else { + ExceptionUtil.completeExceptionally(future, exception, this); + } + } + this.linkClose.complete(null); - } - else - { - this.lastKnownLinkError = exception == null ? this.lastKnownLinkError : exception; - + } else { + this.lastKnownLinkError = exception == null ? this.lastKnownLinkError : exception; + final Exception completionException = exception == null ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : exception; this.onOpenComplete(completionException); - - final WorkItem> workItem = this.pendingReceives.peek(); - final Duration nextRetryInterval = workItem != null && workItem.getTimeoutTracker() != null - ? this.underlyingFactory.getRetryPolicy().getNextRetryInterval(this.getClientId(), completionException, workItem.getTimeoutTracker().remaining()) - : null; - + + final WorkItem> workItem = this.pendingReceives.peek(); + final Duration nextRetryInterval = workItem != null && workItem.getTimeoutTracker() != null + ? this.underlyingFactory.getRetryPolicy().getNextRetryInterval(this.getClientId(), completionException, workItem.getTimeoutTracker().remaining()) + : null; + boolean recreateScheduled = true; - if (nextRetryInterval != null) - { - try - { - this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler() - { - @Override - public void onEvent() - { - if (!MessageReceiver.this.getIsClosingOrClosed() - && (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED)) - { - createReceiveLink(); - underlyingFactory.getRetryPolicy().incrementRetryCount(getClientId()); - } - } - }); - } - catch (IOException ignore) - { + if (nextRetryInterval != null) { + try { + this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler() { + @Override + public void onEvent() { + if (!MessageReceiver.this.getIsClosingOrClosed() + && (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED)) { + createReceiveLink(); + underlyingFactory.getRetryPolicy().incrementRetryCount(getClientId()); + } + } + }); + } catch (IOException ignore) { recreateScheduled = false; - } + } } - - if (nextRetryInterval == null || !recreateScheduled) - { - WorkItem> pendingReceive = null; - while ((pendingReceive = this.pendingReceives.poll()) != null) - { - ExceptionUtil.completeExceptionally(pendingReceive.getWork(), completionException, this); - } - } - } - } - - private void scheduleOperationTimer(final TimeoutTracker tracker) - { - if (tracker != null) - { - Timer.schedule(this.onOperationTimedout, tracker.remaining(), TimerType.OneTimeRun); - } - } - - private void createReceiveLink() - { - if (creatingLink) - return; - - this.creatingLink = true; - - final Consumer onSessionOpen = new Consumer() - { - @Override - public void accept(Session session) - { - // if the MessageReceiver is closed - we no-longer need to create the link - if (MessageReceiver.this.getIsClosingOrClosed()) { - - session.close(); - return; - } - - final Source source = new Source(); - source.setAddress(receivePath); - - final Map filterMap = MessageReceiver.this.settingsProvider.getFilter(MessageReceiver.this.lastReceivedMessage); - if (filterMap != null) - source.setFilter(filterMap); - - final Receiver receiver = session.receiver(TrackingUtil.getLinkName(session)); - receiver.setSource(source); - - final Target target = new Target(); - - receiver.setTarget(target); - - // use explicit settlement via dispositions (not pre-settled) - receiver.setSenderSettleMode(SenderSettleMode.UNSETTLED); - receiver.setReceiverSettleMode(ReceiverSettleMode.SECOND); - - final Map linkProperties = MessageReceiver.this.settingsProvider.getProperties(); - if (linkProperties != null) - receiver.setProperties(linkProperties); - - final Symbol[] desiredCapabilities = MessageReceiver.this.settingsProvider.getDesiredCapabilities(); - if (desiredCapabilities != null) - receiver.setDesiredCapabilities(desiredCapabilities); - - final ReceiveLinkHandler handler = new ReceiveLinkHandler(MessageReceiver.this); - BaseHandler.setHandler(receiver, handler); - MessageReceiver.this.underlyingFactory.registerForConnectionError(receiver); - - receiver.open(); - - MessageReceiver.this.receiveLink = receiver; + + if (nextRetryInterval == null || !recreateScheduled) { + WorkItem> pendingReceive = null; + while ((pendingReceive = this.pendingReceives.poll()) != null) { + ExceptionUtil.completeExceptionally(pendingReceive.getWork(), completionException, this); } - }; - - final BiConsumer onSessionOpenFailed = new BiConsumer() - { - @Override - public void accept(ErrorCondition t, Exception u) - { - if (t != null) - onError(t); - else if (u != null) - onError(u); + } + } + } + + private void scheduleOperationTimer(final TimeoutTracker tracker) { + if (tracker != null) { + Timer.schedule(this.onOperationTimedout, tracker.remaining(), TimerType.OneTimeRun); + } + } + + private void createReceiveLink() { + if (creatingLink) + return; + + this.creatingLink = true; + + final Consumer onSessionOpen = new Consumer() { + @Override + public void accept(Session session) { + // if the MessageReceiver is closed - we no-longer need to create the link + if (MessageReceiver.this.getIsClosingOrClosed()) { + + session.close(); + return; } - }; - - try { - this.underlyingFactory.getCBSChannel().sendToken( + + final Source source = new Source(); + source.setAddress(receivePath); + + final Map filterMap = MessageReceiver.this.settingsProvider.getFilter(MessageReceiver.this.lastReceivedMessage); + if (filterMap != null) + source.setFilter(filterMap); + + final Receiver receiver = session.receiver(TrackingUtil.getLinkName(session)); + receiver.setSource(source); + + final Target target = new Target(); + + receiver.setTarget(target); + + // use explicit settlement via dispositions (not pre-settled) + receiver.setSenderSettleMode(SenderSettleMode.UNSETTLED); + receiver.setReceiverSettleMode(ReceiverSettleMode.SECOND); + + final Map linkProperties = MessageReceiver.this.settingsProvider.getProperties(); + if (linkProperties != null) + receiver.setProperties(linkProperties); + + final Symbol[] desiredCapabilities = MessageReceiver.this.settingsProvider.getDesiredCapabilities(); + if (desiredCapabilities != null) + receiver.setDesiredCapabilities(desiredCapabilities); + + final ReceiveLinkHandler handler = new ReceiveLinkHandler(MessageReceiver.this); + BaseHandler.setHandler(receiver, handler); + MessageReceiver.this.underlyingFactory.registerForConnectionError(receiver); + + receiver.open(); + + MessageReceiver.this.receiveLink = receiver; + } + }; + + final BiConsumer onSessionOpenFailed = new BiConsumer() { + @Override + public void accept(ErrorCondition t, Exception u) { + if (t != null) + onError(t); + else if (u != null) + onError(u); + } + }; + + try { + this.underlyingFactory.getCBSChannel().sendToken( this.underlyingFactory.getReactorScheduler(), - this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), - tokenAudience, + this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), + tokenAudience, new IOperationResult() { @Override public void onComplete(Void result) { if (MessageReceiver.this.getIsClosingOrClosed()) return; - + underlyingFactory.getSession( receivePath, onSessionOpen, onSessionOpenFailed); } + @Override public void onError(Exception error) { MessageReceiver.this.onError(error); } }); - } - catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { - MessageReceiver.this.onError(exception); + } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { + MessageReceiver.this.onError(exception); + } + } + + // CONTRACT: message should be delivered to the caller of MessageReceiver.receive() only via Poll on prefetchqueue + private Message pollPrefetchQueue() { + final Message message = this.prefetchedMessages.poll(); + if (message != null) { + // message lastReceivedOffset should be up-to-date upon each poll - as recreateLink will depend on this + this.lastReceivedMessage = message; + this.sendFlow(1); + } + + return message; + } + + private void sendFlow(final int credits) { + // slow down sending the flow - to make the protocol less-chat'y + this.nextCreditToFlow += credits; + if (this.nextCreditToFlow >= this.prefetchCount || this.nextCreditToFlow >= 100) { + final int tempFlow = this.nextCreditToFlow; + this.receiveLink.flow(tempFlow); + this.nextCreditToFlow = 0; + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s], ThreadId[%s]", + this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow, Thread.currentThread().getId())); } } + } - // CONTRACT: message should be delivered to the caller of MessageReceiver.receive() only via Poll on prefetchqueue - private Message pollPrefetchQueue() - { - final Message message = this.prefetchedMessages.poll(); - if (message != null) - { - // message lastReceivedOffset should be up-to-date upon each poll - as recreateLink will depend on this - this.lastReceivedMessage = message; - this.sendFlow(1); - } - - return message; - } - - private void sendFlow(final int credits) - { - // slow down sending the flow - to make the protocol less-chat'y - this.nextCreditToFlow += credits; - if (this.nextCreditToFlow >= this.prefetchCount || this.nextCreditToFlow >= 100) - { - final int tempFlow = this.nextCreditToFlow; - this.receiveLink.flow(tempFlow); - this.nextCreditToFlow = 0; - - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s], ThreadId[%s]", - this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow, Thread.currentThread().getId())); - } - } - } - - private void scheduleLinkOpenTimeout(final TimeoutTracker timeout) - { - // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - this.openTimer = Timer.schedule( - new Runnable() - { - public void run() - { - if (!linkOpen.getWork().isDone()) - { - Exception operationTimedout = new TimeoutException( - String.format(Locale.US, "%s operation on ReceiveLink(%s) to path(%s) timed out at %s.", "Open", MessageReceiver.this.receiveLink.getName(), MessageReceiver.this.receivePath, ZonedDateTime.now()), - MessageReceiver.this.lastKnownLinkError); - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Open"), - operationTimedout); - } - - ExceptionUtil.completeExceptionally(linkOpen.getWork(), operationTimedout, MessageReceiver.this); - } - } - } - , timeout.remaining() - , TimerType.OneTimeRun); - } - - private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) - { - // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - this.closeTimer = Timer.schedule( - new Runnable() - { - public void run() - { - if (!linkClose.isDone()) - { - Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageReceiver.this.receiveLink.getName(), ZonedDateTime.now())); - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Close"), - operationTimedout); - } - - ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageReceiver.this); - MessageReceiver.this.onError((Exception) null); - } - } - } - , timeout.remaining() - , TimerType.OneTimeRun); - } - - @Override - public void onClose(ErrorCondition condition) - { - if (condition == null || condition.getCondition() == null) - { - this.onError((Exception) null); - } - else - { - this.onError(condition); - } - } - - @Override - public ErrorContext getContext() - { - final boolean isLinkOpened = this.linkOpen != null && this.linkOpen.getWork().isDone(); - final String referenceId = this.receiveLink != null && this.receiveLink.getRemoteProperties() != null && this.receiveLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) - ? this.receiveLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() - : ((this.receiveLink != null) ? this.receiveLink.getName(): null); - - ReceiverContext errorContext = new ReceiverContext(this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, - this.receivePath, - referenceId, - isLinkOpened ? this.prefetchCount : null, - isLinkOpened && this.receiveLink != null ? this.receiveLink.getCredit(): null, - isLinkOpened && this.prefetchedMessages != null ? this.prefetchedMessages.size(): null); - - return errorContext; - } - - private static class ReceiveWorkItem extends WorkItem> - { - private final int maxMessageCount; - - public ReceiveWorkItem(CompletableFuture> completableFuture, Duration timeout, final int maxMessageCount) - { - super(completableFuture, timeout); - this.maxMessageCount = maxMessageCount; - } - } - - @Override - protected CompletableFuture onClose() - { - if (!this.getIsClosed()) - { - try - { - this.activeClientTokenManager.cancel(); - scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); - - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - if (receiveLink != null && receiveLink.getLocalState() != EndpointState.CLOSED) - { - receiveLink.close(); - } - else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CLOSED) - { - if (closeTimer != null) - closeTimer.cancel(false); - - linkClose.complete(null); - } + private void scheduleLinkOpenTimeout(final TimeoutTracker timeout) { + // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory + this.openTimer = Timer.schedule( + new Runnable() { + public void run() { + if (!linkOpen.getWork().isDone()) { + Exception operationTimedout = new TimeoutException( + String.format(Locale.US, "%s operation on ReceiveLink(%s) to path(%s) timed out at %s.", "Open", MessageReceiver.this.receiveLink.getName(), MessageReceiver.this.receivePath, ZonedDateTime.now()), + MessageReceiver.this.lastKnownLinkError); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Open"), + operationTimedout); } - }); + + ExceptionUtil.completeExceptionally(linkOpen.getWork(), operationTimedout, MessageReceiver.this); + } + } } - catch(IOException ioException) - { - this.linkClose.completeExceptionally(new ServiceBusException(false, "Scheduling close failed with error. See cause for more details.", ioException)); + , timeout.remaining() + , TimerType.OneTimeRun); + } + + private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { + // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory + this.closeTimer = Timer.schedule( + new Runnable() { + public void run() { + if (!linkClose.isDone()) { + Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageReceiver.this.receiveLink.getName(), ZonedDateTime.now())); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Close"), + operationTimedout); + } + + ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageReceiver.this); + MessageReceiver.this.onError((Exception) null); + } + } } - } + , timeout.remaining() + , TimerType.OneTimeRun); + } - return this.linkClose; - } - - private final class ReceiveWork extends DispatchHandler { - - @Override - public void onEvent() { + @Override + public void onClose(ErrorCondition condition) { + if (condition == null || condition.getCondition() == null) { + this.onError((Exception) null); + } else { + this.onError(condition); + } + } + + @Override + public ErrorContext getContext() { + final boolean isLinkOpened = this.linkOpen != null && this.linkOpen.getWork().isDone(); + final String referenceId = this.receiveLink != null && this.receiveLink.getRemoteProperties() != null && this.receiveLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) + ? this.receiveLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() + : ((this.receiveLink != null) ? this.receiveLink.getName() : null); + + ReceiverContext errorContext = new ReceiverContext(this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, + this.receivePath, + referenceId, + isLinkOpened ? this.prefetchCount : null, + isLinkOpened && this.receiveLink != null ? this.receiveLink.getCredit() : null, + isLinkOpened && this.prefetchedMessages != null ? this.prefetchedMessages.size() : null); + + return errorContext; + } - ReceiveWorkItem pendingReceive; - while (!prefetchedMessages.isEmpty() && (pendingReceive = pendingReceives.poll()) != null) { + private static class ReceiveWorkItem extends WorkItem> { + private final int maxMessageCount; + + public ReceiveWorkItem(CompletableFuture> completableFuture, Duration timeout, final int maxMessageCount) { + super(completableFuture, timeout); + this.maxMessageCount = maxMessageCount; + } + } + + @Override + protected CompletableFuture onClose() { + if (!this.getIsClosed()) { + try { + this.activeClientTokenManager.cancel(); + scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); - if (pendingReceive.getWork() != null && !pendingReceive.getWork().isDone()) { - - Collection receivedMessages = receiveCore(pendingReceive.maxMessageCount); - pendingReceive.getWork().complete(receivedMessages); + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + @Override + public void onEvent() { + if (receiveLink != null && receiveLink.getLocalState() != EndpointState.CLOSED) { + receiveLink.close(); + } else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CLOSED) { + if (closeTimer != null) + closeTimer.cancel(false); + + linkClose.complete(null); + } } + }); + } catch (IOException ioException) { + this.linkClose.completeExceptionally(new ServiceBusException(false, "Scheduling close failed with error. See cause for more details.", ioException)); + } + } + + return this.linkClose; + } + + private final class ReceiveWork extends DispatchHandler { + + @Override + public void onEvent() { + + ReceiveWorkItem pendingReceive; + while (!prefetchedMessages.isEmpty() && (pendingReceive = pendingReceives.poll()) != null) { + + if (pendingReceive.getWork() != null && !pendingReceive.getWork().isDone()) { + + Collection receivedMessages = receiveCore(pendingReceive.maxMessageCount); + pendingReceive.getWork().complete(receivedMessages); } } } - - private final class CreateAndReceive extends DispatchHandler { - - @Override - public void onEvent() { - - receiveWork.onEvent(); - - if (!MessageReceiver.this.getIsClosingOrClosed() + } + + private final class CreateAndReceive extends DispatchHandler { + + @Override + public void onEvent() { + + receiveWork.onEvent(); + + if (!MessageReceiver.this.getIsClosingOrClosed() && (receiveLink.getLocalState() == EndpointState.CLOSED || receiveLink.getRemoteState() == EndpointState.CLOSED)) { - createReceiveLink(); - } + createReceiveLink(); } } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java index cc9cbb32a..a28778351 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java @@ -57,925 +57,781 @@ * Abstracts all amqp related details * translates event-driven reactor model into async send Api */ -public class MessageSender extends ClientEntity implements IAmqpSender, IErrorContextProvider -{ - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private static final String SEND_TIMED_OUT = "Send operation timed out"; - - private final MessagingFactory underlyingFactory; - private final String sendPath; - private final Duration operationTimeout; - private final RetryPolicy retryPolicy; - private final CompletableFuture linkClose; - private final Object pendingSendLock; - private final ConcurrentHashMap> pendingSendsData; - private final PriorityQueue pendingSends; - private final DispatchHandler sendWork; - private final ActiveClientTokenManager activeClientTokenManager; - private final String tokenAudience; - - private Sender sendLink; - private CompletableFuture linkFirstOpen; - private int linkCredit; - private TimeoutTracker openLinkTracker; - private Exception lastKnownLinkError; - private Instant lastKnownErrorReportedAt; - private boolean creatingLink; - private ScheduledFuture closeTimer; - private ScheduledFuture openTimer; - - public static CompletableFuture create( - final MessagingFactory factory, - final String sendLinkName, - final String senderPath) - { - final MessageSender msgSender = new MessageSender(factory, sendLinkName, senderPath); - msgSender.openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout()); - msgSender.initializeLinkOpen(msgSender.openLinkTracker); - - try - { - msgSender.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - msgSender.createSendLink(); - } - }); - } - catch (IOException ioException) - { - msgSender.linkFirstOpen.completeExceptionally(new ServiceBusException(false, "Failed to create Sender, see cause for more details.", ioException)); - } - - return msgSender.linkFirstOpen; - } - - private MessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) - { - super(sendLinkName, factory); - - this.sendPath = senderPath; - this.underlyingFactory = factory; - this.operationTimeout = factory.getOperationTimeout(); - - this.lastKnownLinkError = null; - this.lastKnownErrorReportedAt = Instant.EPOCH; - - this.retryPolicy = factory.getRetryPolicy(); - - this.pendingSendLock = new Object(); - this.pendingSendsData = new ConcurrentHashMap<>(); - this.pendingSends = new PriorityQueue<>(1000, new DeliveryTagComparator()); - this.linkCredit = 0; - - this.linkClose = new CompletableFuture<>(); - - this.sendWork = new DispatchHandler() - { - @Override - public void onEvent() - { - MessageSender.this.processSendWork(); - } - }; - - this.tokenAudience = String.format(ClientConstants.TOKEN_AUDIENCE_FORMAT, underlyingFactory.getHostName(), sendPath); - this.activeClientTokenManager = new ActiveClientTokenManager( - this, - new Runnable() { - @Override - public void run() { - try { - underlyingFactory.getCBSChannel().sendToken( - underlyingFactory.getReactorScheduler(), - underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), - tokenAudience, - new IOperationResult() { - @Override - public void onComplete(Void result) { - if (TRACE_LOGGER.isLoggable(Level.FINE)) { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, +public class MessageSender extends ClientEntity implements IAmqpSender, IErrorContextProvider { + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private static final String SEND_TIMED_OUT = "Send operation timed out"; + + private final MessagingFactory underlyingFactory; + private final String sendPath; + private final Duration operationTimeout; + private final RetryPolicy retryPolicy; + private final CompletableFuture linkClose; + private final Object pendingSendLock; + private final ConcurrentHashMap> pendingSendsData; + private final PriorityQueue pendingSends; + private final DispatchHandler sendWork; + private final ActiveClientTokenManager activeClientTokenManager; + private final String tokenAudience; + + private Sender sendLink; + private CompletableFuture linkFirstOpen; + private int linkCredit; + private TimeoutTracker openLinkTracker; + private Exception lastKnownLinkError; + private Instant lastKnownErrorReportedAt; + private boolean creatingLink; + private ScheduledFuture closeTimer; + private ScheduledFuture openTimer; + + public static CompletableFuture create( + final MessagingFactory factory, + final String sendLinkName, + final String senderPath) { + final MessageSender msgSender = new MessageSender(factory, sendLinkName, senderPath); + msgSender.openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout()); + msgSender.initializeLinkOpen(msgSender.openLinkTracker); + + try { + msgSender.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + @Override + public void onEvent() { + msgSender.createSendLink(); + } + }); + } catch (IOException ioException) { + msgSender.linkFirstOpen.completeExceptionally(new ServiceBusException(false, "Failed to create Sender, see cause for more details.", ioException)); + } + + return msgSender.linkFirstOpen; + } + + private MessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) { + super(sendLinkName, factory); + + this.sendPath = senderPath; + this.underlyingFactory = factory; + this.operationTimeout = factory.getOperationTimeout(); + + this.lastKnownLinkError = null; + this.lastKnownErrorReportedAt = Instant.EPOCH; + + this.retryPolicy = factory.getRetryPolicy(); + + this.pendingSendLock = new Object(); + this.pendingSendsData = new ConcurrentHashMap<>(); + this.pendingSends = new PriorityQueue<>(1000, new DeliveryTagComparator()); + this.linkCredit = 0; + + this.linkClose = new CompletableFuture<>(); + + this.sendWork = new DispatchHandler() { + @Override + public void onEvent() { + MessageSender.this.processSendWork(); + } + }; + + this.tokenAudience = String.format(ClientConstants.TOKEN_AUDIENCE_FORMAT, underlyingFactory.getHostName(), sendPath); + this.activeClientTokenManager = new ActiveClientTokenManager( + this, + new Runnable() { + @Override + public void run() { + try { + underlyingFactory.getCBSChannel().sendToken( + underlyingFactory.getReactorScheduler(), + underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), + tokenAudience, + new IOperationResult() { + @Override + public void onComplete(Void result) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "path[%s], linkName[%s] - token renewed", sendPath, sendLink.getName())); - } - } - @Override - public void onError(Exception error) { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) { - TRACE_LOGGER.log(Level.WARNING, + } + } + + @Override + public void onError(Exception error) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, String.format(Locale.US, - "path[%s], linkName[%s] - tokenRenewalFailure[%s]", sendPath, sendLink.getName(), error.getMessage())); - } - } - }); - } - catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, - "path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", sendPath, sendLink.getName(), exception.getMessage())); + "path[%s], linkName[%s] - tokenRenewalFailure[%s]", sendPath, sendLink.getName(), error.getMessage())); + } } + }); + } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, + "path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", sendPath, sendLink.getName(), exception.getMessage())); + } + } + } + }, + ClientConstants.TOKEN_REFRESH_INTERVAL); + } + + public String getSendPath() { + return this.sendPath; + } + + private CompletableFuture send(byte[] bytes, int arrayOffset, int messageFormat) { + return this.send(bytes, arrayOffset, messageFormat, null, null); + } + + private CompletableFuture sendCore( + final byte[] bytes, + final int arrayOffset, + final int messageFormat, + final CompletableFuture onSend, + final TimeoutTracker tracker, + final Exception lastKnownError, + final ScheduledFuture timeoutTask) { + this.throwIfClosed(this.lastKnownLinkError); + + final boolean isRetrySend = (onSend != null); + + final CompletableFuture onSendFuture = (onSend == null) ? new CompletableFuture<>() : onSend; + + final ReplayableWorkItem sendWaiterData = (tracker == null) ? + new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, this.operationTimeout) : + new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, tracker); + + final TimeoutTracker currentSendTracker = sendWaiterData.getTimeoutTracker(); + final String deliveryTag = UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) + "_" + currentSendTracker.elapsed().getSeconds(); + + if (lastKnownError != null) { + sendWaiterData.setLastKnownException(lastKnownError); + } + + if (timeoutTask != null) + timeoutTask.cancel(false); + + final ScheduledFuture timeoutTimerTask = Timer.schedule( + new SendTimeout(deliveryTag, sendWaiterData), + currentSendTracker.remaining(), TimerType.OneTimeRun); + + sendWaiterData.setTimeoutTask(timeoutTimerTask); + + synchronized (this.pendingSendLock) { + this.pendingSendsData.put(deliveryTag, sendWaiterData); + this.pendingSends.offer(new WeightedDeliveryTag(deliveryTag, isRetrySend ? 1 : 0)); + } + + try { + this.underlyingFactory.scheduleOnReactorThread(this.sendWork); + } catch (IOException ioException) { + onSendFuture.completeExceptionally( + new OperationCancelledException("Send failed while dispatching to Reactor, see cause for more details.", ioException)); + } + + return onSendFuture; + } + + private CompletableFuture send( + final byte[] bytes, + final int arrayOffset, + final int messageFormat, + final CompletableFuture onSend, + final TimeoutTracker tracker) { + return this.sendCore(bytes, arrayOffset, messageFormat, onSend, tracker, null, null); + } + + public CompletableFuture send(final Iterable messages) { + if (messages == null || IteratorUtil.sizeEquals(messages, 0)) { + throw new IllegalArgumentException("Sending Empty batch of messages is not allowed."); + } + + final Message firstMessage = messages.iterator().next(); + if (IteratorUtil.sizeEquals(messages, 1)) { + return this.send(firstMessage); + } + + // proton-j doesn't support multiple dataSections to be part of AmqpMessage + // here's the alternate approach provided by them: https://github.com/apache/qpid-proton/pull/54 + final Message batchMessage = Proton.message(); + batchMessage.setMessageAnnotations(firstMessage.getMessageAnnotations()); + + final byte[] bytes = new byte[ClientConstants.MAX_MESSAGE_LENGTH_BYTES]; + int encodedSize = batchMessage.encode(bytes, 0, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); + int byteArrayOffset = encodedSize; + + for (Message amqpMessage : messages) { + Message messageWrappedByData = Proton.message(); + + int payloadSize = AmqpUtil.getDataSerializedSize(amqpMessage); + int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); + + byte[] messageBytes = new byte[allocationSize]; + int messageSizeBytes = amqpMessage.encode(messageBytes, 0, allocationSize); + messageWrappedByData.setBody(new Data(new Binary(messageBytes, 0, messageSizeBytes))); + + try { + encodedSize = messageWrappedByData.encode(bytes, byteArrayOffset, ClientConstants.MAX_MESSAGE_LENGTH_BYTES - byteArrayOffset - 1); + } catch (BufferOverflowException exception) { + final CompletableFuture sendTask = new CompletableFuture<>(); + sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception)); + return sendTask; + } + + byteArrayOffset = byteArrayOffset + encodedSize; + } + + return this.send(bytes, byteArrayOffset, AmqpConstants.AMQP_BATCH_MESSAGE_FORMAT); + } + + public CompletableFuture send(Message msg) { + int payloadSize = AmqpUtil.getDataSerializedSize(msg); + int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); + + final byte[] bytes = new byte[allocationSize]; + int encodedSize = 0; + try { + encodedSize = msg.encode(bytes, 0, allocationSize); + } catch (BufferOverflowException exception) { + final CompletableFuture sendTask = new CompletableFuture(); + sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception)); + return sendTask; + } + + return this.send(bytes, encodedSize, DeliveryImpl.DEFAULT_MESSAGE_FORMAT); + } + + @Override + public void onOpenComplete(Exception completionException) { + this.creatingLink = false; + + if (completionException == null) { + if (this.getIsClosingOrClosed()) { + + this.sendLink.close(); + return; + } + + this.openLinkTracker = null; + + this.lastKnownLinkError = null; + this.retryPolicy.resetRetryCount(this.getClientId()); + + if (!this.linkFirstOpen.isDone()) { + this.linkFirstOpen.complete(this); + if (this.openTimer != null) + this.openTimer.cancel(false); + } else { + synchronized (this.pendingSendLock) { + if (!this.pendingSendsData.isEmpty()) { + List unacknowledgedSends = new LinkedList<>(); + unacknowledgedSends.addAll(this.pendingSendsData.keySet()); + + if (unacknowledgedSends.size() > 0) { + Iterator reverseReader = unacknowledgedSends.iterator(); + while (reverseReader.hasNext()) { + String unacknowledgedSend = reverseReader.next(); + if (this.pendingSendsData.get(unacknowledgedSend).isWaitingForAck()) { + this.pendingSends.offer(new WeightedDeliveryTag(unacknowledgedSend, 1)); + } + } + } + + unacknowledgedSends.clear(); + } + } + } + } else { + if (!this.linkFirstOpen.isDone()) { + this.setClosed(); + ExceptionUtil.completeExceptionally(this.linkFirstOpen, completionException, this); + if (this.openTimer != null) + this.openTimer.cancel(false); + } + } + } + + @Override + public void onClose(final ErrorCondition condition) { + final Exception completionException = (condition != null && condition.getCondition() != null) ? ExceptionUtil.toException(condition) : null; + this.onError(completionException); + } + + @Override + public void onError(final Exception completionException) { + this.linkCredit = 0; + this.underlyingFactory.deregisterForConnectionError(this.sendLink); + + if (this.getIsClosingOrClosed()) { + if (this.closeTimer != null && !this.closeTimer.isDone()) + this.closeTimer.cancel(false); + + synchronized (this.pendingSendLock) { + for (Map.Entry> pendingSend : this.pendingSendsData.entrySet()) { + ExceptionUtil.completeExceptionally(pendingSend.getValue().getWork(), + completionException == null + ? new OperationCancelledException("Send cancelled as the Sender instance is Closed before the sendOperation completed.") + : completionException, + this); + } + + this.pendingSendsData.clear(); + this.pendingSends.clear(); + } + + this.linkClose.complete(null); + + return; + } else { + this.lastKnownLinkError = completionException == null ? this.lastKnownLinkError : completionException; + this.lastKnownErrorReportedAt = Instant.now(); + + final Exception finalCompletionException = completionException == null + ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; + + this.onOpenComplete(finalCompletionException); + + final Map.Entry> pendingSendEntry = IteratorUtil.getFirst(this.pendingSendsData.entrySet()); + if (pendingSendEntry != null && pendingSendEntry.getValue() != null) { + final TimeoutTracker tracker = pendingSendEntry.getValue().getTimeoutTracker(); + if (tracker != null) { + final Duration nextRetryInterval = this.retryPolicy.getNextRetryInterval(this.getClientId(), finalCompletionException, tracker.remaining()); + boolean scheduledRecreate = true; + + if (nextRetryInterval != null) { + try { + this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler() { + @Override + public void onEvent() { + if (!MessageSender.this.getIsClosingOrClosed() + && (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED)) { + recreateSendLink(); } } - }, - ClientConstants.TOKEN_REFRESH_INTERVAL); - } - - public String getSendPath() - { - return this.sendPath; - } - - private CompletableFuture send(byte[] bytes, int arrayOffset, int messageFormat) - { - return this.send(bytes, arrayOffset, messageFormat, null, null); - } - - private CompletableFuture sendCore( - final byte[] bytes, - final int arrayOffset, - final int messageFormat, - final CompletableFuture onSend, - final TimeoutTracker tracker, - final Exception lastKnownError, - final ScheduledFuture timeoutTask) - { - this.throwIfClosed(this.lastKnownLinkError); - - final boolean isRetrySend = (onSend != null); - - final CompletableFuture onSendFuture = (onSend == null) ? new CompletableFuture<>() : onSend; - - final ReplayableWorkItem sendWaiterData = (tracker == null) ? - new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, this.operationTimeout) : - new ReplayableWorkItem<>(bytes, arrayOffset, messageFormat, onSendFuture, tracker); - - final TimeoutTracker currentSendTracker = sendWaiterData.getTimeoutTracker(); - final String deliveryTag = UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) + "_" + currentSendTracker.elapsed().getSeconds(); - - if (lastKnownError != null) - { - sendWaiterData.setLastKnownException(lastKnownError); - } - - if (timeoutTask != null) - timeoutTask.cancel(false); - - final ScheduledFuture timeoutTimerTask = Timer.schedule( - new SendTimeout(deliveryTag, sendWaiterData), - currentSendTracker.remaining(), TimerType.OneTimeRun); - - sendWaiterData.setTimeoutTask(timeoutTimerTask); - - synchronized (this.pendingSendLock) - { - this.pendingSendsData.put(deliveryTag, sendWaiterData); - this.pendingSends.offer(new WeightedDeliveryTag(deliveryTag, isRetrySend ? 1 : 0)); - } - - try - { - this.underlyingFactory.scheduleOnReactorThread(this.sendWork); - } - catch (IOException ioException) - { - onSendFuture.completeExceptionally( - new OperationCancelledException("Send failed while dispatching to Reactor, see cause for more details.", ioException)); - } - - return onSendFuture; - } - - private CompletableFuture send( - final byte[] bytes, - final int arrayOffset, - final int messageFormat, - final CompletableFuture onSend, - final TimeoutTracker tracker) - { - return this.sendCore(bytes, arrayOffset, messageFormat, onSend, tracker, null, null); - } - - public CompletableFuture send(final Iterable messages) - { - if (messages == null || IteratorUtil.sizeEquals(messages, 0)) - { - throw new IllegalArgumentException("Sending Empty batch of messages is not allowed."); - } - - final Message firstMessage = messages.iterator().next(); - if (IteratorUtil.sizeEquals(messages, 1)) - { - return this.send(firstMessage); - } - - // proton-j doesn't support multiple dataSections to be part of AmqpMessage - // here's the alternate approach provided by them: https://github.com/apache/qpid-proton/pull/54 - final Message batchMessage = Proton.message(); - batchMessage.setMessageAnnotations(firstMessage.getMessageAnnotations()); - - final byte[] bytes = new byte[ClientConstants.MAX_MESSAGE_LENGTH_BYTES]; - int encodedSize = batchMessage.encode(bytes, 0, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); - int byteArrayOffset = encodedSize; - - for(Message amqpMessage: messages) - { - Message messageWrappedByData = Proton.message(); - - int payloadSize = AmqpUtil.getDataSerializedSize(amqpMessage); - int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); - - byte[] messageBytes = new byte[allocationSize]; - int messageSizeBytes = amqpMessage.encode(messageBytes, 0, allocationSize); - messageWrappedByData.setBody(new Data(new Binary(messageBytes, 0, messageSizeBytes))); - - try - { - encodedSize = messageWrappedByData.encode(bytes, byteArrayOffset, ClientConstants.MAX_MESSAGE_LENGTH_BYTES - byteArrayOffset - 1); - } - catch(BufferOverflowException exception) - { - final CompletableFuture sendTask = new CompletableFuture<>(); - sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception)); - return sendTask; - } - - byteArrayOffset = byteArrayOffset + encodedSize; - } - - return this.send(bytes, byteArrayOffset, AmqpConstants.AMQP_BATCH_MESSAGE_FORMAT); - } - - public CompletableFuture send(Message msg) - { - int payloadSize = AmqpUtil.getDataSerializedSize(msg); - int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES); - - final byte[] bytes = new byte[allocationSize]; - int encodedSize = 0; - try - { - encodedSize = msg.encode(bytes, 0, allocationSize); - } - catch(BufferOverflowException exception) - { - final CompletableFuture sendTask = new CompletableFuture(); - sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception)); - return sendTask; - } - - return this.send(bytes, encodedSize, DeliveryImpl.DEFAULT_MESSAGE_FORMAT); - } - - @Override - public void onOpenComplete(Exception completionException) - { - this.creatingLink = false; - - if (completionException == null) - { - if (this.getIsClosingOrClosed()) { - - this.sendLink.close(); - return; + }); + } catch (IOException ignore) { + scheduledRecreate = false; } - - this.openLinkTracker = null; - - this.lastKnownLinkError = null; - this.retryPolicy.resetRetryCount(this.getClientId()); - - if (!this.linkFirstOpen.isDone()) - { - this.linkFirstOpen.complete(this); - if (this.openTimer != null) - this.openTimer.cancel(false); - } - else - { - synchronized (this.pendingSendLock) - { - if (!this.pendingSendsData.isEmpty()) - { - List unacknowledgedSends = new LinkedList<>(); - unacknowledgedSends.addAll(this.pendingSendsData.keySet()); - - if (unacknowledgedSends.size() > 0) - { - Iterator reverseReader = unacknowledgedSends.iterator(); - while (reverseReader.hasNext()) - { - String unacknowledgedSend = reverseReader.next(); - if (this.pendingSendsData.get(unacknowledgedSend).isWaitingForAck()) - { - this.pendingSends.offer(new WeightedDeliveryTag(unacknowledgedSend, 1)); - } - } - } - - unacknowledgedSends.clear(); - } - } - } - } - else - { - if (!this.linkFirstOpen.isDone()) - { - this.setClosed(); - ExceptionUtil.completeExceptionally(this.linkFirstOpen, completionException, this); - if (this.openTimer != null) - this.openTimer.cancel(false); - } - } - } - - @Override - public void onClose(final ErrorCondition condition) - { - final Exception completionException = (condition != null && condition.getCondition() != null) ? ExceptionUtil.toException(condition) : null; - this.onError(completionException); - } - - @Override - public void onError(final Exception completionException) - { - this.linkCredit = 0; - this.underlyingFactory.deregisterForConnectionError(this.sendLink); - - if (this.getIsClosingOrClosed()) - { - if (this.closeTimer != null && !this.closeTimer.isDone()) - this.closeTimer.cancel(false); - - synchronized (this.pendingSendLock) - { - for (Map.Entry> pendingSend: this.pendingSendsData.entrySet()) - { - ExceptionUtil.completeExceptionally(pendingSend.getValue().getWork(), - completionException == null - ? new OperationCancelledException("Send cancelled as the Sender instance is Closed before the sendOperation completed.") - : completionException, - this); - } - - this.pendingSendsData.clear(); - this.pendingSends.clear(); - } - - this.linkClose.complete(null); - - return; - } - else - { - this.lastKnownLinkError = completionException == null ? this.lastKnownLinkError : completionException; - this.lastKnownErrorReportedAt = Instant.now(); - - final Exception finalCompletionException = completionException == null - ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; - - this.onOpenComplete(finalCompletionException); - - final Map.Entry> pendingSendEntry = IteratorUtil.getFirst(this.pendingSendsData.entrySet()); - if (pendingSendEntry != null && pendingSendEntry.getValue() != null) - { - final TimeoutTracker tracker = pendingSendEntry.getValue().getTimeoutTracker(); - if (tracker != null) - { - final Duration nextRetryInterval = this.retryPolicy.getNextRetryInterval(this.getClientId(), finalCompletionException, tracker.remaining()); - boolean scheduledRecreate = true; - - if (nextRetryInterval != null) - { - try - { - this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler() - { - @Override - public void onEvent() - { - if (!MessageSender.this.getIsClosingOrClosed() - && (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED)) - { - recreateSendLink(); - } - } - }); - } - catch (IOException ignore) - { - scheduledRecreate = false; - } - } - - if (nextRetryInterval == null || !scheduledRecreate) - { - synchronized (this.pendingSendLock) - { - for (Map.Entry> pendingSend: this.pendingSendsData.entrySet()) - { - this.cleanupFailedSend(pendingSend.getValue(), finalCompletionException); - } - - this.pendingSendsData.clear(); - this.pendingSends.clear(); - } - } - } - } - } - } - - @Override - public void onSendComplete(final Delivery delivery) - { - final DeliveryState outcome = delivery.getRemoteState(); - final String deliveryTag = new String(delivery.getTag()); - - if (TRACE_LOGGER.isLoggable(Level.FINEST)) - TRACE_LOGGER.log(Level.FINEST, - String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s]", MessageSender.this.sendPath, this.sendLink.getName(), deliveryTag)); - - final ReplayableWorkItem pendingSendWorkItem = this.pendingSendsData.remove(deliveryTag); - - if (pendingSendWorkItem != null) - { - if (outcome instanceof Accepted) - { - this.lastKnownLinkError = null; - this.retryPolicy.resetRetryCount(this.getClientId()); - - pendingSendWorkItem.getTimeoutTask().cancel(false); - pendingSendWorkItem.getWork().complete(null); - } - else if (outcome instanceof Rejected) - { - final Rejected rejected = (Rejected) outcome; - final ErrorCondition error = rejected.getError(); - - final Exception exception = ExceptionUtil.toException(error); - - if (ExceptionUtil.isGeneralSendError(error.getCondition())) - { - this.lastKnownLinkError = exception; - this.lastKnownErrorReportedAt = Instant.now(); - } - - final Duration retryInterval = this.retryPolicy.getNextRetryInterval( - this.getClientId(), exception, pendingSendWorkItem.getTimeoutTracker().remaining()); - if (retryInterval == null) - { - this.cleanupFailedSend(pendingSendWorkItem, exception); - } - else - { - pendingSendWorkItem.setLastKnownException(exception); - try - { - this.underlyingFactory.scheduleOnReactorThread((int) retryInterval.toMillis(), - new DispatchHandler() - { - @Override - public void onEvent() - { - MessageSender.this.sendCore( - pendingSendWorkItem.getMessage(), - pendingSendWorkItem.getEncodedMessageSize(), - pendingSendWorkItem.getMessageFormat(), - pendingSendWorkItem.getWork(), - pendingSendWorkItem.getTimeoutTracker(), - pendingSendWorkItem.getLastKnownException(), - pendingSendWorkItem.getTimeoutTask()); - } - }); - } - catch (IOException ioException) - { - exception.initCause(ioException); - this.cleanupFailedSend( - pendingSendWorkItem, - new ServiceBusException(false, "Send operation failed while scheduling a retry on Reactor, see cause for more details.", ioException)); - } - } - } - else if (outcome instanceof Released) - { - this.cleanupFailedSend(pendingSendWorkItem, new OperationCancelledException(outcome.toString())); - } - else - { - this.cleanupFailedSend(pendingSendWorkItem, new ServiceBusException(false, outcome.toString())); - } - } - else - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch (or send timedout)", this.sendPath, this.sendLink.getName(), deliveryTag)); - } - } - - private void cleanupFailedSend(final ReplayableWorkItem failedSend, final Exception exception) - { - if (failedSend.getTimeoutTask() != null) - failedSend.getTimeoutTask().cancel(false); - - ExceptionUtil.completeExceptionally(failedSend.getWork(), exception, this); - } - - private void createSendLink() - { - if (this.creatingLink) - return; - - this.creatingLink = true; + } - final Consumer onSessionOpen = new Consumer() - { - @Override - public void accept(Session session) - { - if (MessageSender.this.getIsClosingOrClosed()) { - - session.close(); - return; + if (nextRetryInterval == null || !scheduledRecreate) { + synchronized (this.pendingSendLock) { + for (Map.Entry> pendingSend : this.pendingSendsData.entrySet()) { + this.cleanupFailedSend(pendingSend.getValue(), finalCompletionException); + } + + this.pendingSendsData.clear(); + this.pendingSends.clear(); } - - final Sender sender = session.sender(TrackingUtil.getLinkName(session)); - final Target target = new Target(); - target.setAddress(sendPath); - sender.setTarget(target); + } + } + } + } + } - final Source source = new Source(); - sender.setSource(source); + @Override + public void onSendComplete(final Delivery delivery) { + final DeliveryState outcome = delivery.getRemoteState(); + final String deliveryTag = new String(delivery.getTag()); - sender.setSenderSettleMode(SenderSettleMode.UNSETTLED); + if (TRACE_LOGGER.isLoggable(Level.FINEST)) + TRACE_LOGGER.log(Level.FINEST, + String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s]", MessageSender.this.sendPath, this.sendLink.getName(), deliveryTag)); - final SendLinkHandler handler = new SendLinkHandler(MessageSender.this); - BaseHandler.setHandler(sender, handler); + final ReplayableWorkItem pendingSendWorkItem = this.pendingSendsData.remove(deliveryTag); - MessageSender.this.underlyingFactory.registerForConnectionError(sender); - sender.open(); + if (pendingSendWorkItem != null) { + if (outcome instanceof Accepted) { + this.lastKnownLinkError = null; + this.retryPolicy.resetRetryCount(this.getClientId()); - MessageSender.this.sendLink = sender; - } - }; - - final BiConsumer onSessionOpenError = new BiConsumer() - { - @Override - public void accept(ErrorCondition t, Exception u) - { - if (t != null) - MessageSender.this.onClose(t); - else if (u != null) - MessageSender.this.onError(u); + pendingSendWorkItem.getTimeoutTask().cancel(false); + pendingSendWorkItem.getWork().complete(null); + } else if (outcome instanceof Rejected) { + final Rejected rejected = (Rejected) outcome; + final ErrorCondition error = rejected.getError(); + + final Exception exception = ExceptionUtil.toException(error); + + if (ExceptionUtil.isGeneralSendError(error.getCondition())) { + this.lastKnownLinkError = exception; + this.lastKnownErrorReportedAt = Instant.now(); + } + + final Duration retryInterval = this.retryPolicy.getNextRetryInterval( + this.getClientId(), exception, pendingSendWorkItem.getTimeoutTracker().remaining()); + if (retryInterval == null) { + this.cleanupFailedSend(pendingSendWorkItem, exception); + } else { + pendingSendWorkItem.setLastKnownException(exception); + try { + this.underlyingFactory.scheduleOnReactorThread((int) retryInterval.toMillis(), + new DispatchHandler() { + @Override + public void onEvent() { + MessageSender.this.sendCore( + pendingSendWorkItem.getMessage(), + pendingSendWorkItem.getEncodedMessageSize(), + pendingSendWorkItem.getMessageFormat(), + pendingSendWorkItem.getWork(), + pendingSendWorkItem.getTimeoutTracker(), + pendingSendWorkItem.getLastKnownException(), + pendingSendWorkItem.getTimeoutTask()); + } + }); + } catch (IOException ioException) { + exception.initCause(ioException); + this.cleanupFailedSend( + pendingSendWorkItem, + new ServiceBusException(false, "Send operation failed while scheduling a retry on Reactor, see cause for more details.", ioException)); } - }; - - try - { - this.underlyingFactory.getCBSChannel().sendToken( - this.underlyingFactory.getReactorScheduler(), - this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), - tokenAudience, - new IOperationResult() { - @Override - public void onComplete(Void result) { - if (MessageSender.this.getIsClosingOrClosed()) - return; - - underlyingFactory.getSession( + } + } else if (outcome instanceof Released) { + this.cleanupFailedSend(pendingSendWorkItem, new OperationCancelledException(outcome.toString())); + } else { + this.cleanupFailedSend(pendingSendWorkItem, new ServiceBusException(false, outcome.toString())); + } + } else { + if (TRACE_LOGGER.isLoggable(Level.FINE)) + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch (or send timedout)", this.sendPath, this.sendLink.getName(), deliveryTag)); + } + } + + private void cleanupFailedSend(final ReplayableWorkItem failedSend, final Exception exception) { + if (failedSend.getTimeoutTask() != null) + failedSend.getTimeoutTask().cancel(false); + + ExceptionUtil.completeExceptionally(failedSend.getWork(), exception, this); + } + + private void createSendLink() { + if (this.creatingLink) + return; + + this.creatingLink = true; + + final Consumer onSessionOpen = new Consumer() { + @Override + public void accept(Session session) { + if (MessageSender.this.getIsClosingOrClosed()) { + + session.close(); + return; + } + + final Sender sender = session.sender(TrackingUtil.getLinkName(session)); + final Target target = new Target(); + target.setAddress(sendPath); + sender.setTarget(target); + + final Source source = new Source(); + sender.setSource(source); + + sender.setSenderSettleMode(SenderSettleMode.UNSETTLED); + + final SendLinkHandler handler = new SendLinkHandler(MessageSender.this); + BaseHandler.setHandler(sender, handler); + + MessageSender.this.underlyingFactory.registerForConnectionError(sender); + sender.open(); + + MessageSender.this.sendLink = sender; + } + }; + + final BiConsumer onSessionOpenError = new BiConsumer() { + @Override + public void accept(ErrorCondition t, Exception u) { + if (t != null) + MessageSender.this.onClose(t); + else if (u != null) + MessageSender.this.onError(u); + } + }; + + try { + this.underlyingFactory.getCBSChannel().sendToken( + this.underlyingFactory.getReactorScheduler(), + this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_REFRESH_INTERVAL), + tokenAudience, + new IOperationResult() { + @Override + public void onComplete(Void result) { + if (MessageSender.this.getIsClosingOrClosed()) + return; + + underlyingFactory.getSession( sendPath, onSessionOpen, onSessionOpenError); + } + + @Override + public void onError(Exception error) { + MessageSender.this.onError(error); + } + }); + } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { + MessageSender.this.onError(exception); + } + } + + // TODO: consolidate common-code written for timeouts in Sender/Receiver + private void initializeLinkOpen(TimeoutTracker timeout) { + this.linkFirstOpen = new CompletableFuture<>(); + + // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory + this.openTimer = Timer.schedule( + new Runnable() { + public void run() { + if (!MessageSender.this.linkFirstOpen.isDone()) { + Exception operationTimedout = new TimeoutException( + String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", MessageSender.this.sendLink.getName(), MessageSender.this.getSendPath(), ZonedDateTime.now().toString()), + MessageSender.this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? MessageSender.this.lastKnownLinkError : null); + + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, "path[%s], linkName[%s], open call timedout", MessageSender.this.sendPath, MessageSender.this.sendLink.getName()), + operationTimedout); } - @Override - public void onError(Exception error) { - MessageSender.this.onError(error); - } - }); - } - catch(IOException|NoSuchAlgorithmException|InvalidKeyException|RuntimeException exception) - { - MessageSender.this.onError(exception); + + ExceptionUtil.completeExceptionally(MessageSender.this.linkFirstOpen, operationTimedout, MessageSender.this); + } + } } - } - - // TODO: consolidate common-code written for timeouts in Sender/Receiver - private void initializeLinkOpen(TimeoutTracker timeout) - { - this.linkFirstOpen = new CompletableFuture<>(); - - // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - this.openTimer = Timer.schedule( - new Runnable() - { - public void run() - { - if (!MessageSender.this.linkFirstOpen.isDone()) - { - Exception operationTimedout = new TimeoutException( - String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", MessageSender.this.sendLink.getName(), MessageSender.this.getSendPath(), ZonedDateTime.now().toString()), - MessageSender.this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? MessageSender.this.lastKnownLinkError : null); - - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "path[%s], linkName[%s], open call timedout", MessageSender.this.sendPath, MessageSender.this.sendLink.getName()), - operationTimedout); - } - - ExceptionUtil.completeExceptionally(MessageSender.this.linkFirstOpen, operationTimedout, MessageSender.this); - } - } - } - , timeout.remaining() - , TimerType.OneTimeRun); - } - - @Override - public ErrorContext getContext() - { - final boolean isLinkOpened = this.linkFirstOpen != null && this.linkFirstOpen.isDone(); - final String referenceId = this.sendLink != null && this.sendLink.getRemoteProperties() != null && this.sendLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) - ? this.sendLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() - : ((this.sendLink != null) ? this.sendLink.getName() : null); - - SenderContext errorContext = new SenderContext( - this.underlyingFactory!=null ? this.underlyingFactory.getHostName() : null, - this.sendPath, - referenceId, - isLinkOpened && this.sendLink != null ? this.sendLink.getCredit() : null); - return errorContext; - } - - @Override - public void onFlow(final int creditIssued) - { - this.lastKnownLinkError = null; - - if (creditIssued <= 0) - return; - - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - int numberOfSendsWaitingforCredit = this.pendingSends.size(); - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "path[%s], linkName[%s], remoteLinkCredit[%s], pendingSendsWaitingForCredit[%s], pendingSendsWaitingDelivery[%s]", - this.sendPath, this.sendLink.getName(), creditIssued, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit)); - } - - this.linkCredit = this.linkCredit + creditIssued; - this.sendWork.onEvent(); - } - - private void recreateSendLink() - { - this.createSendLink(); - this.retryPolicy.incrementRetryCount(this.getClientId()); - } - - // actual send on the SenderLink should happen only in this method & should run on Reactor Thread - private void processSendWork() - { - if (this.sendLink.getLocalState() == EndpointState.CLOSED || this.sendLink.getRemoteState() == EndpointState.CLOSED) - { + , timeout.remaining() + , TimerType.OneTimeRun); + } + + @Override + public ErrorContext getContext() { + final boolean isLinkOpened = this.linkFirstOpen != null && this.linkFirstOpen.isDone(); + final String referenceId = this.sendLink != null && this.sendLink.getRemoteProperties() != null && this.sendLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) + ? this.sendLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() + : ((this.sendLink != null) ? this.sendLink.getName() : null); + + SenderContext errorContext = new SenderContext( + this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, + this.sendPath, + referenceId, + isLinkOpened && this.sendLink != null ? this.sendLink.getCredit() : null); + return errorContext; + } + + @Override + public void onFlow(final int creditIssued) { + this.lastKnownLinkError = null; + + if (creditIssued <= 0) + return; + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + int numberOfSendsWaitingforCredit = this.pendingSends.size(); + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "path[%s], linkName[%s], remoteLinkCredit[%s], pendingSendsWaitingForCredit[%s], pendingSendsWaitingDelivery[%s]", + this.sendPath, this.sendLink.getName(), creditIssued, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit)); + } + + this.linkCredit = this.linkCredit + creditIssued; + this.sendWork.onEvent(); + } + + private void recreateSendLink() { + this.createSendLink(); + this.retryPolicy.incrementRetryCount(this.getClientId()); + } + + // actual send on the SenderLink should happen only in this method & should run on Reactor Thread + private void processSendWork() { + if (this.sendLink.getLocalState() == EndpointState.CLOSED || this.sendLink.getRemoteState() == EndpointState.CLOSED) { if (!this.getIsClosingOrClosed()) - this.recreateSendLink(); + this.recreateSendLink(); return; - } - - while (this.sendLink.getLocalState() == EndpointState.ACTIVE && this.sendLink.getRemoteState() == EndpointState.ACTIVE - && this.linkCredit > 0) - { - final WeightedDeliveryTag weightedDelivery; - final ReplayableWorkItem sendData; - final String deliveryTag; - synchronized (this.pendingSendLock) - { - weightedDelivery = this.pendingSends.poll(); - if (weightedDelivery != null) { - deliveryTag = weightedDelivery.getDeliveryTag(); - sendData = this.pendingSendsData.get(deliveryTag); + } + + while (this.sendLink.getLocalState() == EndpointState.ACTIVE && this.sendLink.getRemoteState() == EndpointState.ACTIVE + && this.linkCredit > 0) { + final WeightedDeliveryTag weightedDelivery; + final ReplayableWorkItem sendData; + final String deliveryTag; + synchronized (this.pendingSendLock) { + weightedDelivery = this.pendingSends.poll(); + if (weightedDelivery != null) { + deliveryTag = weightedDelivery.getDeliveryTag(); + sendData = this.pendingSendsData.get(deliveryTag); + } else { + sendData = null; + deliveryTag = null; + } + } + + if (sendData != null) { + if (sendData.getWork() != null && sendData.getWork().isDone()) { + // CoreSend could enque Sends into PendingSends Queue and can fail the SendCompletableFuture + // (when It fails to schedule the ProcessSendWork on reactor Thread) + this.pendingSendsData.remove(deliveryTag); + continue; + } + + Delivery delivery = null; + boolean linkAdvance = false; + int sentMsgSize = 0; + Exception sendException = null; + + try { + delivery = this.sendLink.delivery(deliveryTag.getBytes()); + delivery.setMessageFormat(sendData.getMessageFormat()); + + sentMsgSize = this.sendLink.send(sendData.getMessage(), 0, sendData.getEncodedMessageSize()); + assert sentMsgSize == sendData.getEncodedMessageSize() : "Contract of the ProtonJ library for Sender.Send API changed"; + + linkAdvance = this.sendLink.advance(); + } catch (Exception exception) { + sendException = exception; + } + + if (linkAdvance) { + this.linkCredit--; + sendData.setWaitingForAck(); + } else { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s], sentMessageSize[%s], payloadActualSize[%s] - sendlink advance failed", + this.sendPath, this.sendLink.getName(), deliveryTag, sentMsgSize, sendData.getEncodedMessageSize())); } - else { - sendData = null; - deliveryTag = null; + + if (delivery != null) { + delivery.free(); } - } - - if (sendData != null) - { - if (sendData.getWork() != null && sendData.getWork().isDone()) - { - // CoreSend could enque Sends into PendingSends Queue and can fail the SendCompletableFuture - // (when It fails to schedule the ProcessSendWork on reactor Thread) - this.pendingSendsData.remove(deliveryTag); - continue; - } - - Delivery delivery = null; - boolean linkAdvance = false; - int sentMsgSize = 0; - Exception sendException = null; - - try - { - delivery = this.sendLink.delivery(deliveryTag.getBytes()); - delivery.setMessageFormat(sendData.getMessageFormat()); - - sentMsgSize = this.sendLink.send(sendData.getMessage(), 0, sendData.getEncodedMessageSize()); - assert sentMsgSize == sendData.getEncodedMessageSize() : "Contract of the ProtonJ library for Sender.Send API changed"; - - linkAdvance = this.sendLink.advance(); - } - catch(Exception exception) - { - sendException = exception; - } - - if (linkAdvance) - { - this.linkCredit--; - sendData.setWaitingForAck(); - } - else - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s], sentMessageSize[%s], payloadActualSize[%s] - sendlink advance failed", - this.sendPath, this.sendLink.getName(), deliveryTag, sentMsgSize, sendData.getEncodedMessageSize())); - } - - if (delivery != null) - { - delivery.free(); - } - + sendData.getWork().completeExceptionally(sendException != null - ? new OperationCancelledException("Send operation failed. Please see cause for more details", sendException) - : new OperationCancelledException( - String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag))); - } - } - else - { - if (deliveryTag != null) - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.", - this.sendPath, this.sendLink.getName(), deliveryTag)); - } - } - - break; - } - } - } - - private void throwSenderTimeout(CompletableFuture pendingSendWork, Exception lastKnownException) - { - Exception cause = lastKnownException; - if (lastKnownException == null && this.lastKnownLinkError != null) - { - boolean isServerBusy = ((this.lastKnownLinkError instanceof ServerBusyException) - && (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)))); - cause = isServerBusy || (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusMillis(this.operationTimeout.toMillis()))) - ? this.lastKnownLinkError - : null; - } - - boolean isClientSideTimeout = (cause == null || !(cause instanceof ServiceBusException)); - ServiceBusException exception = isClientSideTimeout - ? new TimeoutException(String.format(Locale.US, "%s %s %s.", MessageSender.SEND_TIMED_OUT, " at ", ZonedDateTime.now(), cause)) - : (ServiceBusException) cause; - - ExceptionUtil.completeExceptionally(pendingSendWork, exception, this); - } - - private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) - { - // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory - this.closeTimer = Timer.schedule( - new Runnable() - { - public void run() - { - if (!linkClose.isDone()) - { - Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageSender.this.sendLink.getName(), ZonedDateTime.now())); - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "message recever(linkName: %s, path: %s) %s call timedout", MessageSender.this.sendLink.getName(), MessageSender.this.sendPath, "Close"), - operationTimedout); - } - - ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageSender.this); - MessageSender.this.onError((Exception) null); - } - } - } - , timeout.remaining() - , TimerType.OneTimeRun); - } - - @Override - protected CompletableFuture onClose() - { - if (!this.getIsClosed()) - { - try - { - this.activeClientTokenManager.cancel(); - scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - if (sendLink != null && sendLink.getLocalState() != EndpointState.CLOSED) - { - sendLink.close(); - } - else if (sendLink == null || sendLink.getRemoteState() == EndpointState.CLOSED) - { - if (closeTimer != null) - closeTimer.cancel(false); - - linkClose.complete(null); - } - } - }); - + ? new OperationCancelledException("Send operation failed. Please see cause for more details", sendException) + : new OperationCancelledException( + String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag))); } - catch (IOException ioException) - { - this.linkClose.completeExceptionally(new ServiceBusException(false, "Scheduling close failed. See cause for more details.", ioException)); + } else { + if (deliveryTag != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.", + this.sendPath, this.sendLink.getName(), deliveryTag)); + } } + + break; } - - return this.linkClose; - } - - private static class WeightedDeliveryTag - { - private final String deliveryTag; - private final int priority; - - WeightedDeliveryTag(final String deliveryTag, final int priority) - { - this.deliveryTag = deliveryTag; - this.priority = priority; - } - - public String getDeliveryTag() - { - return this.deliveryTag; - } - - public int getPriority() - { - return this.priority; - } - } - - private static class DeliveryTagComparator implements Comparator - { - @Override - public int compare(WeightedDeliveryTag deliveryTag0, WeightedDeliveryTag deliveryTag1) - { - return deliveryTag1.getPriority() - deliveryTag0.getPriority(); - } - } - - private class SendTimeout implements Runnable - { - private final String deliveryTag; - private final ReplayableWorkItem sendWaiterData; - - public SendTimeout( - final String deliveryTag, - final ReplayableWorkItem sendWaiterData) { - this.sendWaiterData = sendWaiterData; - this.deliveryTag = deliveryTag; - } - - @Override - public void run() - { - if (!sendWaiterData.getWork().isDone()) - { - MessageSender.this.pendingSendsData.remove(deliveryTag); - MessageSender.this.throwSenderTimeout(sendWaiterData.getWork(), sendWaiterData.getLastKnownException()); + } + } + + private void throwSenderTimeout(CompletableFuture pendingSendWork, Exception lastKnownException) { + Exception cause = lastKnownException; + if (lastKnownException == null && this.lastKnownLinkError != null) { + boolean isServerBusy = ((this.lastKnownLinkError instanceof ServerBusyException) + && (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)))); + cause = isServerBusy || (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusMillis(this.operationTimeout.toMillis()))) + ? this.lastKnownLinkError + : null; + } + + boolean isClientSideTimeout = (cause == null || !(cause instanceof ServiceBusException)); + ServiceBusException exception = isClientSideTimeout + ? new TimeoutException(String.format(Locale.US, "%s %s %s.", MessageSender.SEND_TIMED_OUT, " at ", ZonedDateTime.now(), cause)) + : (ServiceBusException) cause; + + ExceptionUtil.completeExceptionally(pendingSendWork, exception, this); + } + + private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { + // timer to signal a timeout if exceeds the operationTimeout on MessagingFactory + this.closeTimer = Timer.schedule( + new Runnable() { + public void run() { + if (!linkClose.isDone()) { + Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageSender.this.sendLink.getName(), ZonedDateTime.now())); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + String.format(Locale.US, "message recever(linkName: %s, path: %s) %s call timedout", MessageSender.this.sendLink.getName(), MessageSender.this.sendPath, "Close"), + operationTimedout); + } + + ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageSender.this); + MessageSender.this.onError((Exception) null); + } + } } + , timeout.remaining() + , TimerType.OneTimeRun); + } + + @Override + protected CompletableFuture onClose() { + if (!this.getIsClosed()) { + try { + this.activeClientTokenManager.cancel(); + scheduleLinkCloseTimeout(TimeoutTracker.create(operationTimeout)); + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + @Override + public void onEvent() { + if (sendLink != null && sendLink.getLocalState() != EndpointState.CLOSED) { + sendLink.close(); + } else if (sendLink == null || sendLink.getRemoteState() == EndpointState.CLOSED) { + if (closeTimer != null) + closeTimer.cancel(false); + + linkClose.complete(null); + } + } + }); + + } catch (IOException ioException) { + this.linkClose.completeExceptionally(new ServiceBusException(false, "Scheduling close failed. See cause for more details.", ioException)); + } + } + + return this.linkClose; + } + + private static class WeightedDeliveryTag { + private final String deliveryTag; + private final int priority; + + WeightedDeliveryTag(final String deliveryTag, final int priority) { + this.deliveryTag = deliveryTag; + this.priority = priority; + } + + public String getDeliveryTag() { + return this.deliveryTag; + } + + public int getPriority() { + return this.priority; + } + } + + private static class DeliveryTagComparator implements Comparator { + @Override + public int compare(WeightedDeliveryTag deliveryTag0, WeightedDeliveryTag deliveryTag1) { + return deliveryTag1.getPriority() - deliveryTag0.getPriority(); + } + } + + private class SendTimeout implements Runnable { + private final String deliveryTag; + private final ReplayableWorkItem sendWaiterData; + + public SendTimeout( + final String deliveryTag, + final ReplayableWorkItem sendWaiterData) { + this.sendWaiterData = sendWaiterData; + this.deliveryTag = deliveryTag; + } + + @Override + public void run() { + if (!sendWaiterData.getWork().isDone()) { + MessageSender.this.pendingSendsData.remove(deliveryTag); + MessageSender.this.throwSenderTimeout(sendWaiterData.getWork(), sendWaiterData.getLastKnownException()); } } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java index 1e696a290..a4d1d7b43 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java @@ -41,452 +41,372 @@ * Abstracts all amqp related details and exposes AmqpConnection object * Manages connection life-cycle */ -public class MessagingFactory extends ClientEntity implements IAmqpConnection, ISessionProvider -{ - public static final Duration DefaultOperationTimeout = Duration.ofSeconds(60); - - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private final String hostName; - private final CompletableFuture closeTask; - private final ConnectionHandler connectionHandler; - private final LinkedList registeredLinks; - private final Object reactorLock; - private final Object cbsChannelCreateLock; - private final SharedAccessSignatureTokenProvider tokenProvider; - - private Reactor reactor; - private ReactorDispatcher reactorScheduler; - private Connection connection; - private CBSChannel cbsChannel; - - private Duration operationTimeout; - private RetryPolicy retryPolicy; - private CompletableFuture open; - private CompletableFuture openConnection; - - /** - * @param reactor parameter reactor is purely for testing purposes and the SDK code should always set it to null - */ - MessagingFactory(final ConnectionStringBuilder builder, final RetryPolicy retryPolicy) - { - super("MessagingFactory".concat(StringUtil.getRandomString()), null); - - Timer.register(this.getClientId()); - this.hostName = builder.getEndpoint().getHost(); - - this.operationTimeout = builder.getOperationTimeout(); - this.retryPolicy = retryPolicy; - this.registeredLinks = new LinkedList<>(); - this.reactorLock = new Object(); - this.connectionHandler = new ConnectionHandler(this); - this.openConnection = new CompletableFuture<>(); - this.cbsChannelCreateLock = new Object(); - this.tokenProvider = builder.getSharedAccessSignature() == null - ? new SharedAccessSignatureTokenProvider(builder.getSasKeyName(), builder.getSasKey()) - : new SharedAccessSignatureTokenProvider(builder.getSharedAccessSignature()); - - this.closeTask = new CompletableFuture<>(); - this.closeTask.thenAccept(new Consumer() - { - @Override - public void accept(Void arg0) - { - Timer.unregister(getClientId()); - } - }); - } - - String getHostName() - { - return this.hostName; - } - - private Reactor getReactor() - { - synchronized (this.reactorLock) - { - return this.reactor; - } - } - - public ReactorDispatcher getReactorScheduler() - { - synchronized (this.reactorLock) - { - return this.reactorScheduler; - } - } - - public SharedAccessSignatureTokenProvider getTokenProvider() - { - return this.tokenProvider; +public class MessagingFactory extends ClientEntity implements IAmqpConnection, ISessionProvider { + public static final Duration DefaultOperationTimeout = Duration.ofSeconds(60); + + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private final String hostName; + private final CompletableFuture closeTask; + private final ConnectionHandler connectionHandler; + private final LinkedList registeredLinks; + private final Object reactorLock; + private final Object cbsChannelCreateLock; + private final SharedAccessSignatureTokenProvider tokenProvider; + + private Reactor reactor; + private ReactorDispatcher reactorScheduler; + private Connection connection; + private CBSChannel cbsChannel; + + private Duration operationTimeout; + private RetryPolicy retryPolicy; + private CompletableFuture open; + private CompletableFuture openConnection; + + /** + * @param reactor parameter reactor is purely for testing purposes and the SDK code should always set it to null + */ + MessagingFactory(final ConnectionStringBuilder builder, final RetryPolicy retryPolicy) { + super("MessagingFactory".concat(StringUtil.getRandomString()), null); + + Timer.register(this.getClientId()); + this.hostName = builder.getEndpoint().getHost(); + + this.operationTimeout = builder.getOperationTimeout(); + this.retryPolicy = retryPolicy; + this.registeredLinks = new LinkedList<>(); + this.reactorLock = new Object(); + this.connectionHandler = new ConnectionHandler(this); + this.openConnection = new CompletableFuture<>(); + this.cbsChannelCreateLock = new Object(); + this.tokenProvider = builder.getSharedAccessSignature() == null + ? new SharedAccessSignatureTokenProvider(builder.getSasKeyName(), builder.getSasKey()) + : new SharedAccessSignatureTokenProvider(builder.getSharedAccessSignature()); + + this.closeTask = new CompletableFuture<>(); + this.closeTask.thenAccept(new Consumer() { + @Override + public void accept(Void arg0) { + Timer.unregister(getClientId()); + } + }); + } + + String getHostName() { + return this.hostName; + } + + private Reactor getReactor() { + synchronized (this.reactorLock) { + return this.reactor; + } + } + + public ReactorDispatcher getReactorScheduler() { + synchronized (this.reactorLock) { + return this.reactorScheduler; + } + } + + public SharedAccessSignatureTokenProvider getTokenProvider() { + return this.tokenProvider; + } + + private void createConnection(ConnectionStringBuilder builder) throws IOException { + this.open = new CompletableFuture<>(); + this.startReactor(new ReactorHandler() { + @Override + public void onReactorInit(Event e) { + super.onReactorInit(e); + + final Reactor r = e.getReactor(); + connection = r.connectionToHost(hostName, ClientConstants.AMQPS_PORT, connectionHandler); + } + }); + } + + private void startReactor(final ReactorHandler reactorHandler) throws IOException { + final Reactor newReactor = ProtonUtil.reactor(reactorHandler); + synchronized (this.reactorLock) { + this.reactor = newReactor; + this.reactorScheduler = new ReactorDispatcher(newReactor); + reactorHandler.unsafeSetReactorDispatcher(this.reactorScheduler); + } + + final Thread reactorThread = new Thread(new RunReactor(newReactor)); + reactorThread.start(); + } + + public CBSChannel getCBSChannel() { + synchronized (this.cbsChannelCreateLock) { + if (this.cbsChannel == null) { + this.cbsChannel = new CBSChannel(this, this, "cbs-link"); + } + } + + return this.cbsChannel; + } + + @Override + public Session getSession(final String path, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) { + if (this.getIsClosingOrClosed()) { + + onRemoteSessionOpenError.accept(null, new OperationCancelledException("underlying messagingFactory instance is closed")); + } + + if (this.connection == null || this.connection.getLocalState() == EndpointState.CLOSED || this.connection.getRemoteState() == EndpointState.CLOSED) { + this.connection = this.getReactor().connectionToHost(this.hostName, ClientConstants.AMQPS_PORT, this.connectionHandler); } - private void createConnection(ConnectionStringBuilder builder) throws IOException - { - this.open = new CompletableFuture<>(); - this.startReactor(new ReactorHandler() - { - @Override - public void onReactorInit(Event e) - { - super.onReactorInit(e); - - final Reactor r = e.getReactor(); - connection = r.connectionToHost(hostName, ClientConstants.AMQPS_PORT, connectionHandler); - } - }); - } - - private void startReactor(final ReactorHandler reactorHandler) throws IOException - { - final Reactor newReactor = ProtonUtil.reactor(reactorHandler); - synchronized (this.reactorLock) - { - this.reactor = newReactor; - this.reactorScheduler = new ReactorDispatcher(newReactor); - reactorHandler.unsafeSetReactorDispatcher(this.reactorScheduler); - } - - final Thread reactorThread = new Thread(new RunReactor(newReactor)); - reactorThread.start(); - } - - public CBSChannel getCBSChannel() - { - synchronized (this.cbsChannelCreateLock) - { - if (this.cbsChannel == null) - { - this.cbsChannel = new CBSChannel(this, this, "cbs-link"); + final Session session = this.connection.session(); + BaseHandler.setHandler(session, new SessionHandler(path, onRemoteSessionOpen, onRemoteSessionOpenError)); + session.open(); + + return session; + } + + public Duration getOperationTimeout() { + return this.operationTimeout; + } + + public RetryPolicy getRetryPolicy() { + return this.retryPolicy; + } + + public static CompletableFuture createFromConnectionString(final String connectionString) throws IOException { + return createFromConnectionString(connectionString, RetryPolicy.getDefault()); + } + + public static CompletableFuture createFromConnectionString(final String connectionString, final RetryPolicy retryPolicy) throws IOException { + final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString); + final MessagingFactory messagingFactory = new MessagingFactory(builder, (retryPolicy != null) ? retryPolicy : RetryPolicy.getDefault()); + + messagingFactory.createConnection(builder); + return messagingFactory.open; + } + + @Override + public void onOpenComplete(Exception exception) { + if (exception == null) { + this.open.complete(this); + this.openConnection.complete(this.connection); + if (this.getIsClosingOrClosed()) + this.connection.close(); + } else { + this.open.completeExceptionally(exception); + this.openConnection.completeExceptionally(exception); + } + } + + @Override + public void onConnectionError(ErrorCondition error) { + if (!this.open.isDone()) { + this.onOpenComplete(ExceptionUtil.toException(error)); + } else { + final Connection currentConnection = this.connection; + for (Link link : this.registeredLinks) { + if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) { + link.close(); } } - - return this.cbsChannel; + + this.openConnection = new CompletableFuture<>(); + + if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) { + currentConnection.close(); + } + + // Clone of the registeredLinks is needed here + // onClose of link will lead to un-register - which will result into iteratorCollectionModified error + final List registeredLinksCopy = new LinkedList<>(this.registeredLinks); + for (Link link : registeredLinksCopy) { + final Handler handler = BaseHandler.getHandler(link); + if (handler != null && handler instanceof BaseLinkHandler) { + final BaseLinkHandler linkHandler = (BaseLinkHandler) handler; + linkHandler.processOnClose(link, error); + } + } + } + + if (this.getIsClosingOrClosed() && !this.closeTask.isDone()) { + this.getReactor().stop(); } + } - @Override - public Session getSession(final String path, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) - { + private void onReactorError(Exception cause) { + if (!this.open.isDone()) { + this.onOpenComplete(cause); + } else { + final Connection currentConnection = this.connection; + + try { if (this.getIsClosingOrClosed()) { - - onRemoteSessionOpenError.accept(null, new OperationCancelledException("underlying messagingFactory instance is closed")); + return; + } else { + this.startReactor(new ReactorHandler()); } - - if (this.connection == null || this.connection.getLocalState() == EndpointState.CLOSED || this.connection.getRemoteState() == EndpointState.CLOSED) - { - this.connection = this.getReactor().connectionToHost(this.hostName, ClientConstants.AMQPS_PORT, this.connectionHandler); - } - - final Session session = this.connection.session(); - BaseHandler.setHandler(session, new SessionHandler(path, onRemoteSessionOpen, onRemoteSessionOpenError)); - session.open(); - - return session; - } + } catch (IOException e) { + TRACE_LOGGER.log(Level.SEVERE, ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error")); + + this.onReactorError(cause); + } - public Duration getOperationTimeout() - { - return this.operationTimeout; - } - - public RetryPolicy getRetryPolicy() - { - return this.retryPolicy; - } - - public static CompletableFuture createFromConnectionString(final String connectionString) throws IOException - { - return createFromConnectionString(connectionString, RetryPolicy.getDefault()); - } - - public static CompletableFuture createFromConnectionString(final String connectionString, final RetryPolicy retryPolicy) throws IOException - { - final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString); - final MessagingFactory messagingFactory = new MessagingFactory(builder, (retryPolicy != null) ? retryPolicy : RetryPolicy.getDefault()); - - messagingFactory.createConnection(builder); - return messagingFactory.open; - } - - @Override - public void onOpenComplete(Exception exception) - { - if (exception == null) - { - this.open.complete(this); - this.openConnection.complete(this.connection); - if (this.getIsClosingOrClosed()) - this.connection.close(); - } - else - { - this.open.completeExceptionally(exception); - this.openConnection.completeExceptionally(exception); - } - } - - @Override - public void onConnectionError(ErrorCondition error) - { - if (!this.open.isDone()) - { - this.onOpenComplete(ExceptionUtil.toException(error)); - } - else - { - final Connection currentConnection = this.connection; - for (Link link: this.registeredLinks) - { - if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) - { - link.close(); - } - } - - this.openConnection = new CompletableFuture<>(); - - if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) - { - currentConnection.close(); - } - - // Clone of the registeredLinks is needed here - // onClose of link will lead to un-register - which will result into iteratorCollectionModified error - final List registeredLinksCopy = new LinkedList<>(this.registeredLinks); - for (Link link: registeredLinksCopy) - { - final Handler handler = BaseHandler.getHandler(link); - if (handler != null && handler instanceof BaseLinkHandler) - { - final BaseLinkHandler linkHandler = (BaseLinkHandler) handler; - linkHandler.processOnClose(link, error); - } - } - } - - if (this.getIsClosingOrClosed() && !this.closeTask.isDone()) - { - this.getReactor().stop(); - } - } - - private void onReactorError(Exception cause) - { - if (!this.open.isDone()) - { - this.onOpenComplete(cause); - } - else - { - final Connection currentConnection = this.connection; - - try - { - if (this.getIsClosingOrClosed()) - { - return; - } - else - { - this.startReactor(new ReactorHandler()); - } - } - catch (IOException e) - { - TRACE_LOGGER.log(Level.SEVERE, ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error")); - - this.onReactorError(cause); - } - - if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) - { - currentConnection.close(); - } - - for (Link link: this.registeredLinks) - { - if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) - { - link.close(); - } - - final Handler handler = BaseHandler.getHandler(link); - if (handler != null && handler instanceof BaseLinkHandler) - { - final BaseLinkHandler linkHandler = (BaseLinkHandler) handler; - linkHandler.processOnClose(link, cause); - } - } - } - } - - @Override - protected CompletableFuture onClose() - { - if (!this.getIsClosed()) - { - try - { - this.scheduleOnReactorThread(new CloseWork()); + if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) { + currentConnection.close(); + } + + for (Link link : this.registeredLinks) { + if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) { + link.close(); } - catch (IOException ioException) - { - this.closeTask.completeExceptionally(new ServiceBusException(false, "Failed to Close MessagingFactory, see cause for more details.", ioException)); + + final Handler handler = BaseHandler.getHandler(link); + if (handler != null && handler instanceof BaseLinkHandler) { + final BaseLinkHandler linkHandler = (BaseLinkHandler) handler; + linkHandler.processOnClose(link, cause); } - } + } + } + } + + @Override + protected CompletableFuture onClose() { + if (!this.getIsClosed()) { + try { + this.scheduleOnReactorThread(new CloseWork()); + } catch (IOException ioException) { + this.closeTask.completeExceptionally(new ServiceBusException(false, "Failed to Close MessagingFactory, see cause for more details.", ioException)); + } + } - return this.closeTask; - } - - private class CloseWork extends DispatchHandler - { - @Override - public void onEvent() - { - final ReactorDispatcher dispatcher = getReactorScheduler(); - synchronized (cbsChannelCreateLock) { - - if (cbsChannel != null) { - - cbsChannel.close( - dispatcher, - new IOperationResult() { - - private void closeConnection() { - - if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { - - if (connection.getLocalState() != EndpointState.CLOSED) { - connection.close(); - } + return this.closeTask; + } + + private class CloseWork extends DispatchHandler { + @Override + public void onEvent() { + final ReactorDispatcher dispatcher = getReactorScheduler(); + synchronized (cbsChannelCreateLock) { + + if (cbsChannel != null) { + + cbsChannel.close( + dispatcher, + new IOperationResult() { + + private void closeConnection() { + + if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { + + if (connection.getLocalState() != EndpointState.CLOSED) { + connection.close(); } } - - @Override - public void onComplete(Void result) { - this.closeConnection(); - } - @Override - public void onError(Exception error) { - this.closeConnection(); - } - }); - } - - else { - - if (connection != null && connection.getRemoteState() != EndpointState.CLOSED && connection.getLocalState() != EndpointState.CLOSED) - connection.close(); - } + } + + @Override + public void onComplete(Void result) { + this.closeConnection(); + } + + @Override + public void onError(Exception error) { + this.closeConnection(); + } + }); + } else { + + if (connection != null && connection.getRemoteState() != EndpointState.CLOSED && connection.getLocalState() != EndpointState.CLOSED) + connection.close(); } - - if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) - { - Timer.schedule(new Runnable() - { - @Override - public void run() - { - if (!closeTask.isDone()) - { - closeTask.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); - } - } - }, - operationTimeout, TimerType.OneTimeRun); + } + + if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { + Timer.schedule(new Runnable() { + @Override + public void run() { + if (!closeTask.isDone()) { + closeTask.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); + } + } + }, + operationTimeout, TimerType.OneTimeRun); + } + } + } + + private class RunReactor implements Runnable { + final private Reactor rctr; + + public RunReactor(final Reactor reactor) { + this.rctr = reactor; + } + + public void run() { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "starting reactor instance."); + } + + try { + this.rctr.setTimeout(3141); + this.rctr.start(); + while (!Thread.interrupted() && this.rctr.process()) { + } + this.rctr.stop(); + } catch (HandlerException handlerException) { + Throwable cause = handlerException.getCause(); + if (cause == null) { + cause = handlerException; + } + + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, + ExceptionUtil.toStackTraceString(handlerException, "UnHandled exception while processing events in reactor:")); + } + + String message = !StringUtil.isNullOrEmpty(cause.getMessage()) ? + cause.getMessage() : + !StringUtil.isNullOrEmpty(handlerException.getMessage()) ? + handlerException.getMessage() : + "Reactor encountered unrecoverable error"; + + ServiceBusException sbException = new ServiceBusException( + true, + String.format(Locale.US, "%s, %s", message, ExceptionUtil.getTrackingIDAndTimeToLog()), + cause); + + if (cause instanceof UnresolvedAddressException) { + sbException = new CommunicationException( + String.format(Locale.US, "%s. This is usually caused by incorrect hostname or network configuration. Please check to see if namespace information is correct. %s", message, ExceptionUtil.getTrackingIDAndTimeToLog()), + cause); + } + + MessagingFactory.this.onReactorError(sbException); + } finally { + this.rctr.free(); + + if (getIsClosingOrClosed() && !closeTask.isDone()) { + closeTask.complete(null); } } } + } + + @Override + public void registerForConnectionError(Link link) { + this.registeredLinks.add(link); + } + + @Override + public void deregisterForConnectionError(Link link) { + this.registeredLinks.remove(link); + } + + public void scheduleOnReactorThread(final DispatchHandler handler) throws IOException { + this.getReactorScheduler().invoke(handler); + } - private class RunReactor implements Runnable - { - final private Reactor rctr; - - public RunReactor(final Reactor reactor) - { - this.rctr = reactor; - } - - public void run() - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "starting reactor instance."); - } - - try - { - this.rctr.setTimeout(3141); - this.rctr.start(); - while(!Thread.interrupted() && this.rctr.process()) {} - this.rctr.stop(); - } - catch (HandlerException handlerException) - { - Throwable cause = handlerException.getCause(); - if (cause == null) - { - cause = handlerException; - } - - if(TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, - ExceptionUtil.toStackTraceString(handlerException, "UnHandled exception while processing events in reactor:")); - } - - String message = !StringUtil.isNullOrEmpty(cause.getMessage()) ? - cause.getMessage(): - !StringUtil.isNullOrEmpty(handlerException.getMessage()) ? - handlerException.getMessage() : - "Reactor encountered unrecoverable error"; - - ServiceBusException sbException = new ServiceBusException( - true, - String.format(Locale.US, "%s, %s", message, ExceptionUtil.getTrackingIDAndTimeToLog()), - cause); - - if (cause instanceof UnresolvedAddressException) - { - sbException = new CommunicationException( - String.format(Locale.US, "%s. This is usually caused by incorrect hostname or network configuration. Please check to see if namespace information is correct. %s", message, ExceptionUtil.getTrackingIDAndTimeToLog()), - cause); - } - - MessagingFactory.this.onReactorError(sbException); - } - finally - { - this.rctr.free(); - - if (getIsClosingOrClosed() && !closeTask.isDone()) - { - closeTask.complete(null); - } - } - } - } - - @Override - public void registerForConnectionError(Link link) - { - this.registeredLinks.add(link); - } - - @Override - public void deregisterForConnectionError(Link link) - { - this.registeredLinks.remove(link); - } - - public void scheduleOnReactorThread(final DispatchHandler handler) throws IOException - { - this.getReactorScheduler().invoke(handler); - } - - public void scheduleOnReactorThread(final int delay, final DispatchHandler handler) throws IOException - { - this.getReactorScheduler().invoke(delay, handler); - } + public void scheduleOnReactorThread(final int delay, final DispatchHandler handler) throws IOException { + this.getReactorScheduler().invoke(delay, handler); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/OperationCancelledException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/OperationCancelledException.java index 735cedea0..3ee44aa61 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/OperationCancelledException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/OperationCancelledException.java @@ -7,27 +7,22 @@ /** * This exception is thrown when the underlying Amqp layer encounter an abnormal link abort or disconnect of connection in an unexpected fashion. */ -public class OperationCancelledException extends ServiceBusException -{ - private static final long serialVersionUID = 1L; +public class OperationCancelledException extends ServiceBusException { + private static final long serialVersionUID = 1L; - OperationCancelledException() - { - super(false); - } + OperationCancelledException() { + super(false); + } - public OperationCancelledException(final String message) - { - super(false, message); - } + public OperationCancelledException(final String message) { + super(false, message); + } - OperationCancelledException(final Throwable cause) - { - super(false, cause); - } + OperationCancelledException(final Throwable cause) { + super(false, cause); + } - OperationCancelledException(final String message, final Throwable cause) - { - super(false, message, cause); - } + OperationCancelledException(final String message, final Throwable cause) { + super(false, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PassByRef.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PassByRef.java index ef39d1f68..00aa7b7d7 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PassByRef.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PassByRef.java @@ -5,13 +5,13 @@ package com.microsoft.azure.servicebus; public final class PassByRef { - + T t; - + public T get() { return this.t; } - + public void set(final T t) { this.t = t; } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PayloadSizeExceededException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PayloadSizeExceededException.java index 4a0cbaeb2..dfafc5f3d 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PayloadSizeExceededException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/PayloadSizeExceededException.java @@ -5,32 +5,28 @@ package com.microsoft.azure.servicebus; /** - * this exception is thrown when user attempts to send a event data or brokered message that has exceeded the - * allowed payload size as defined by the service. Note that in a batch send scenario the limit can include possible + * this exception is thrown when user attempts to send a event data or brokered message that has exceeded the + * allowed payload size as defined by the service. Note that in a batch send scenario the limit can include possible * batch overhead. + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class PayloadSizeExceededException extends ServiceBusException -{ - private static final long serialVersionUID = 3627182744252750014L; +public class PayloadSizeExceededException extends ServiceBusException { + private static final long serialVersionUID = 3627182744252750014L; - PayloadSizeExceededException() - { - super(false); - } + PayloadSizeExceededException() { + super(false); + } - PayloadSizeExceededException(final String message) - { - super(false, message); - } + PayloadSizeExceededException(final String message) { + super(false, message); + } - PayloadSizeExceededException(final Throwable cause) - { - super(false, cause); - } + PayloadSizeExceededException(final Throwable cause) { + super(false, cause); + } - PayloadSizeExceededException(final String message, final Throwable cause) - { - super(false, message, cause); - } + PayloadSizeExceededException(final String message, final Throwable cause) { + super(false, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/QuotaExceededException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/QuotaExceededException.java index d1f648707..2f794e2ff 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/QuotaExceededException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/QuotaExceededException.java @@ -4,14 +4,14 @@ */ package com.microsoft.azure.servicebus; -public class QuotaExceededException extends ServiceBusException { - +public class QuotaExceededException extends ServiceBusException { + public QuotaExceededException(String message) { super(false, message); } - + public QuotaExceededException(Throwable cause) { super(false, cause); } - + } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverContext.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverContext.java index 1c0789e78..cd14ebc22 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverContext.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverContext.java @@ -6,80 +6,70 @@ import java.util.Locale; -public class ReceiverContext extends ErrorContext -{ - final static boolean EPOCH_RECEIVER_TYPE = true; - final static boolean NON_EPOCH_RECEIVER_TYPE = !ReceiverContext.EPOCH_RECEIVER_TYPE; +public class ReceiverContext extends ErrorContext { + final static boolean EPOCH_RECEIVER_TYPE = true; + final static boolean NON_EPOCH_RECEIVER_TYPE = !ReceiverContext.EPOCH_RECEIVER_TYPE; - final String receivePath; - final String referenceId; - final Integer prefetchCount; - final Integer currentLinkCredit; - final Integer prefetchQueueLength; + final String receivePath; + final String referenceId; + final Integer prefetchCount; + final Integer currentLinkCredit; + final Integer prefetchQueueLength; - ReceiverContext( - final String namespaceName, - final String receivePath, - final String referenceId, - final Integer prefetchCount, - final Integer currentLinkCredit, - final Integer prefetchQueueLength) - { - super(namespaceName); - this.receivePath = receivePath; - this.referenceId = referenceId; - this.prefetchCount = prefetchCount; - this.currentLinkCredit = currentLinkCredit; - this.prefetchQueueLength = prefetchQueueLength; - } + ReceiverContext( + final String namespaceName, + final String receivePath, + final String referenceId, + final Integer prefetchCount, + final Integer currentLinkCredit, + final Integer prefetchQueueLength) { + super(namespaceName); + this.receivePath = receivePath; + this.referenceId = referenceId; + this.prefetchCount = prefetchCount; + this.currentLinkCredit = currentLinkCredit; + this.prefetchQueueLength = prefetchQueueLength; + } - @Override - public String toString() - { - final String superString = super.toString(); - StringBuilder toString = new StringBuilder(); + @Override + public String toString() { + final String superString = super.toString(); + StringBuilder toString = new StringBuilder(); - if (!StringUtil.isNullOrEmpty(superString)) - { - toString.append(superString); - toString.append(", "); - } + if (!StringUtil.isNullOrEmpty(superString)) { + toString.append(superString); + toString.append(", "); + } - if (this.receivePath != null) - { - toString.append(String.format(Locale.US, "PATH: %s", this.receivePath)); - toString.append(", "); - } + if (this.receivePath != null) { + toString.append(String.format(Locale.US, "PATH: %s", this.receivePath)); + toString.append(", "); + } - if (this.referenceId != null) - { - toString.append(String.format(Locale.US, "REFERENCE_ID: %s", this.referenceId)); - toString.append(", "); - } + if (this.referenceId != null) { + toString.append(String.format(Locale.US, "REFERENCE_ID: %s", this.referenceId)); + toString.append(", "); + } - if (this.prefetchCount != null) - { - toString.append(String.format(Locale.US, "PREFETCH_COUNT: %s", this.prefetchCount)); - toString.append(", "); - } + if (this.prefetchCount != null) { + toString.append(String.format(Locale.US, "PREFETCH_COUNT: %s", this.prefetchCount)); + toString.append(", "); + } - if (this.currentLinkCredit != null) - { - toString.append(String.format(Locale.US, "LINK_CREDIT: %s", this.currentLinkCredit)); - toString.append(", "); - } + if (this.currentLinkCredit != null) { + toString.append(String.format(Locale.US, "LINK_CREDIT: %s", this.currentLinkCredit)); + toString.append(", "); + } - if (this.prefetchQueueLength != null) - { - toString.append(String.format(Locale.US, "PREFETCH_Q_LEN: %s", this.prefetchQueueLength)); - toString.append(", "); - } + if (this.prefetchQueueLength != null) { + toString.append(String.format(Locale.US, "PREFETCH_Q_LEN: %s", this.prefetchQueueLength)); + toString.append(", "); + } - if (toString.length() > 2) - { - toString.setLength(toString.length() - 2); - } + if (toString.length() > 2) { + toString.setLength(toString.length() - 2); + } - return toString.toString(); - } + return toString.toString(); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverDisconnectedException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverDisconnectedException.java index a1bf2ad63..2d756417a 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverDisconnectedException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReceiverDisconnectedException.java @@ -11,31 +11,27 @@ *
  • user attempts to connect a non-epoch receiver to a event hub partition, when there is an epoch receiver connected to the partition. *
  • you are using an epoch receiver for a given partition but another epoch receiver with a higher epoch value connects to the same partition. * - * User should make sure either all code are using non-epoch receivers, or ensure that there is only one epoch receiver processing a given partition - * at any given point in time. + * User should make sure either all code are using non-epoch receivers, or ensure that there is only one epoch receiver processing a given partition + * at any given point in time. + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class ReceiverDisconnectedException extends ServiceBusException -{ - private static final long serialVersionUID = 3385140843418138213L; +public class ReceiverDisconnectedException extends ServiceBusException { + private static final long serialVersionUID = 3385140843418138213L; - ReceiverDisconnectedException() - { - super(false); - } + ReceiverDisconnectedException() { + super(false); + } - ReceiverDisconnectedException(final String message) - { - super(false, message); - } + ReceiverDisconnectedException(final String message) { + super(false, message); + } - ReceiverDisconnectedException(final Throwable cause) - { - super(false, cause); - } + ReceiverDisconnectedException(final Throwable cause) { + super(false, cause); + } - ReceiverDisconnectedException(final String message, final Throwable cause) - { - super(false, message, cause); - } + ReceiverDisconnectedException(final String message, final Throwable cause) { + super(false, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReplayableWorkItem.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReplayableWorkItem.java index 34face21e..2c807bc0e 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReplayableWorkItem.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ReplayableWorkItem.java @@ -8,77 +8,64 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; -public class ReplayableWorkItem extends WorkItem -{ - private byte[] amqpMessage; - private int messageFormat; - private int encodedMessageSize; - private boolean waitingForAck; - - private Exception lastKnownException; - private ScheduledFuture timeoutTask; - - public ReplayableWorkItem(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat, final CompletableFuture completableFuture, final Duration timeout) - { - super(completableFuture, timeout); - this.initialize(amqpMessage, encodedMessageSize, messageFormat); - } +public class ReplayableWorkItem extends WorkItem { + private byte[] amqpMessage; + private int messageFormat; + private int encodedMessageSize; + private boolean waitingForAck; - public ReplayableWorkItem(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat, final CompletableFuture completableFuture, final TimeoutTracker timeout) - { - super(completableFuture, timeout); - this.initialize(amqpMessage, encodedMessageSize, messageFormat); - } + private Exception lastKnownException; + private ScheduledFuture timeoutTask; - private void initialize(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat) - { - this.amqpMessage = amqpMessage; - this.messageFormat = messageFormat; - this.encodedMessageSize = encodedMessageSize; - } + public ReplayableWorkItem(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat, final CompletableFuture completableFuture, final Duration timeout) { + super(completableFuture, timeout); + this.initialize(amqpMessage, encodedMessageSize, messageFormat); + } - public byte[] getMessage() - { - return this.amqpMessage; - } + public ReplayableWorkItem(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat, final CompletableFuture completableFuture, final TimeoutTracker timeout) { + super(completableFuture, timeout); + this.initialize(amqpMessage, encodedMessageSize, messageFormat); + } - public int getEncodedMessageSize() - { - return this.encodedMessageSize; - } + private void initialize(final byte[] amqpMessage, final int encodedMessageSize, final int messageFormat) { + this.amqpMessage = amqpMessage; + this.messageFormat = messageFormat; + this.encodedMessageSize = encodedMessageSize; + } - public int getMessageFormat() - { - return this.messageFormat; - } + public byte[] getMessage() { + return this.amqpMessage; + } - public Exception getLastKnownException() - { - return this.lastKnownException; - } + public int getEncodedMessageSize() { + return this.encodedMessageSize; + } - public void setLastKnownException(Exception exception) - { - this.lastKnownException = exception; - } - - public ScheduledFuture getTimeoutTask() - { - return this.timeoutTask; - } - - public void setTimeoutTask(final ScheduledFuture timeoutTask) - { - this.timeoutTask = timeoutTask; - } - - public void setWaitingForAck() - { - this.waitingForAck = true; - } - - public boolean isWaitingForAck() - { - return this.waitingForAck; - } + public int getMessageFormat() { + return this.messageFormat; + } + + public Exception getLastKnownException() { + return this.lastKnownException; + } + + public void setLastKnownException(Exception exception) { + this.lastKnownException = exception; + } + + public ScheduledFuture getTimeoutTask() { + return this.timeoutTask; + } + + public void setTimeoutTask(final ScheduledFuture timeoutTask) { + this.timeoutTask = timeoutTask; + } + + public void setWaitingForAck() { + this.waitingForAck = true; + } + + public boolean isWaitingForAck() { + return this.waitingForAck; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryExponential.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryExponential.java index 3eeea0be3..b08ae4e21 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryExponential.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryExponential.java @@ -7,64 +7,56 @@ import java.time.Duration; /** - * RetryPolicy implementation where the delay between retries will grow in an exponential manner. - * RetryPolicy can be set on the client operations using {@link ConnectionStringBuilder}. - * RetryIntervals will be computed using a retryFactor which is a function of deltaBackOff (MaximumBackoff - MinimumBackoff) and MaximumRetryCount + * RetryPolicy implementation where the delay between retries will grow in an exponential manner. + * RetryPolicy can be set on the client operations using {@link ConnectionStringBuilder}. + * RetryIntervals will be computed using a retryFactor which is a function of deltaBackOff (MaximumBackoff - MinimumBackoff) and MaximumRetryCount */ -public final class RetryExponential extends RetryPolicy -{ - private final Duration minimumBackoff; - private final Duration maximumBackoff; - private final int maximumRetryCount; - private final double retryFactor; - - public RetryExponential(final Duration minimumBackoff, final Duration maximumBackoff, final int maximumRetryCount, final String name) - { - super(name); - - this.minimumBackoff = minimumBackoff; - this.maximumBackoff = maximumBackoff; - this.maximumRetryCount = maximumRetryCount; - this.retryFactor = this.computeRetryFactor(); - } - - @Override - protected Duration onGetNextRetryInterval(final String clientId, final Exception lastException, final Duration remainingTime, final int baseWaitTimeSecs) - { - int currentRetryCount = this.getRetryCount(clientId); - - if (currentRetryCount >= this.maximumRetryCount) - { - return null; - } - - if (!RetryPolicy.isRetryableException(lastException)) - { - return null; - } - - double nextRetryInterval = Math.pow(this.retryFactor, (double)currentRetryCount); - long nextRetryIntervalSeconds = (long) nextRetryInterval ; - long nextRetryIntervalNano = (long)((nextRetryInterval - (double)nextRetryIntervalSeconds) * 1000000000); - if (remainingTime.getSeconds() < Math.max(nextRetryInterval, ClientConstants.TIMER_TOLERANCE.getSeconds())) - { - return null; - } - - Duration retryAfter = this.minimumBackoff.plus(Duration.ofSeconds(nextRetryIntervalSeconds, nextRetryIntervalNano)); - retryAfter = retryAfter.plus(Duration.ofSeconds(baseWaitTimeSecs)); - - return retryAfter; - } - - private double computeRetryFactor() - { - long deltaBackoff = this.maximumBackoff.minus(this.minimumBackoff).getSeconds(); - if (deltaBackoff <= 0 || this.maximumRetryCount <= 0) - { - return 0; - } - - return (Math.log(deltaBackoff) / Math.log(this.maximumRetryCount)); - } +public final class RetryExponential extends RetryPolicy { + private final Duration minimumBackoff; + private final Duration maximumBackoff; + private final int maximumRetryCount; + private final double retryFactor; + + public RetryExponential(final Duration minimumBackoff, final Duration maximumBackoff, final int maximumRetryCount, final String name) { + super(name); + + this.minimumBackoff = minimumBackoff; + this.maximumBackoff = maximumBackoff; + this.maximumRetryCount = maximumRetryCount; + this.retryFactor = this.computeRetryFactor(); + } + + @Override + protected Duration onGetNextRetryInterval(final String clientId, final Exception lastException, final Duration remainingTime, final int baseWaitTimeSecs) { + int currentRetryCount = this.getRetryCount(clientId); + + if (currentRetryCount >= this.maximumRetryCount) { + return null; + } + + if (!RetryPolicy.isRetryableException(lastException)) { + return null; + } + + double nextRetryInterval = Math.pow(this.retryFactor, (double) currentRetryCount); + long nextRetryIntervalSeconds = (long) nextRetryInterval; + long nextRetryIntervalNano = (long) ((nextRetryInterval - (double) nextRetryIntervalSeconds) * 1000000000); + if (remainingTime.getSeconds() < Math.max(nextRetryInterval, ClientConstants.TIMER_TOLERANCE.getSeconds())) { + return null; + } + + Duration retryAfter = this.minimumBackoff.plus(Duration.ofSeconds(nextRetryIntervalSeconds, nextRetryIntervalNano)); + retryAfter = retryAfter.plus(Duration.ofSeconds(baseWaitTimeSecs)); + + return retryAfter; + } + + private double computeRetryFactor() { + long deltaBackoff = this.maximumBackoff.minus(this.minimumBackoff).getSeconds(); + if (deltaBackoff <= 0 || this.maximumRetryCount <= 0) { + return 0; + } + + return (Math.log(deltaBackoff) / Math.log(this.maximumRetryCount)); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryPolicy.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryPolicy.java index 03c56c1d6..0298809a9 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryPolicy.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/RetryPolicy.java @@ -8,99 +8,84 @@ import java.util.concurrent.ConcurrentHashMap; // TODO: SIMPLIFY retryPolicy - ConcurrentHashMap is not needed -public abstract class RetryPolicy -{ - private static final RetryPolicy NO_RETRY = new RetryExponential(Duration.ofSeconds(0), Duration.ofSeconds(0), 0, ClientConstants.NO_RETRY); - - private final String name; - private ConcurrentHashMap retryCounts; - private Object serverBusySync; - - protected RetryPolicy(final String name) - { - this.name = name; - this.retryCounts = new ConcurrentHashMap(); - this.serverBusySync = new Object(); - } - - public void incrementRetryCount(String clientId) - { - Integer retryCount = this.retryCounts.get(clientId); - this.retryCounts.put(clientId, retryCount == null ? 1 : retryCount + 1); - } - - public void resetRetryCount(String clientId) - { - Integer currentRetryCount = this.retryCounts.get(clientId); - if (currentRetryCount != null && currentRetryCount.intValue() != 0) - { - this.retryCounts.put(clientId, 0); - } - } - - public static boolean isRetryableException(Exception exception) - { - if (exception == null) - { - throw new IllegalArgumentException("exception cannot be null"); - } - - if (exception instanceof ServiceBusException) - { - return ((ServiceBusException) exception).getIsTransient(); - } - - return false; - } - - public static RetryPolicy getDefault() - { - return new RetryExponential( - ClientConstants.DEFAULT_RERTRY_MIN_BACKOFF, - ClientConstants.DEFAULT_RERTRY_MAX_BACKOFF, - ClientConstants.DEFAULT_MAX_RETRY_COUNT, - ClientConstants.DEFAULT_RETRY); - } - - public static RetryPolicy getNoRetry() - { - return RetryPolicy.NO_RETRY; - } - - protected int getRetryCount(String clientId) - { - Integer retryCount = this.retryCounts.get(clientId); - return retryCount == null ? 0 : retryCount; - } - - /** - * Gets the Interval after which nextRetry should be done. - * - * @param clientId clientId - * @param lastException lastException - * @param remainingTime remainingTime to retry - * @return returns 'null' Duration when not Allowed - */ - public Duration getNextRetryInterval(String clientId, Exception lastException, Duration remainingTime) - { - int baseWaitTime = 0; - synchronized (this.serverBusySync) - { - if (lastException != null && - (lastException instanceof ServerBusyException || (lastException.getCause() != null && lastException.getCause() instanceof ServerBusyException))) - { - baseWaitTime += ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS; - } - } - - return this.onGetNextRetryInterval(clientId, lastException, remainingTime, baseWaitTime); - } - - protected abstract Duration onGetNextRetryInterval(String clientId, Exception lastException, Duration remainingTime, int baseWaitTime); - - @Override - public String toString() - { - return this.name; - } +public abstract class RetryPolicy { + private static final RetryPolicy NO_RETRY = new RetryExponential(Duration.ofSeconds(0), Duration.ofSeconds(0), 0, ClientConstants.NO_RETRY); + + private final String name; + private ConcurrentHashMap retryCounts; + private Object serverBusySync; + + protected RetryPolicy(final String name) { + this.name = name; + this.retryCounts = new ConcurrentHashMap(); + this.serverBusySync = new Object(); + } + + public void incrementRetryCount(String clientId) { + Integer retryCount = this.retryCounts.get(clientId); + this.retryCounts.put(clientId, retryCount == null ? 1 : retryCount + 1); + } + + public void resetRetryCount(String clientId) { + Integer currentRetryCount = this.retryCounts.get(clientId); + if (currentRetryCount != null && currentRetryCount.intValue() != 0) { + this.retryCounts.put(clientId, 0); + } + } + + public static boolean isRetryableException(Exception exception) { + if (exception == null) { + throw new IllegalArgumentException("exception cannot be null"); + } + + if (exception instanceof ServiceBusException) { + return ((ServiceBusException) exception).getIsTransient(); + } + + return false; + } + + public static RetryPolicy getDefault() { + return new RetryExponential( + ClientConstants.DEFAULT_RERTRY_MIN_BACKOFF, + ClientConstants.DEFAULT_RERTRY_MAX_BACKOFF, + ClientConstants.DEFAULT_MAX_RETRY_COUNT, + ClientConstants.DEFAULT_RETRY); + } + + public static RetryPolicy getNoRetry() { + return RetryPolicy.NO_RETRY; + } + + protected int getRetryCount(String clientId) { + Integer retryCount = this.retryCounts.get(clientId); + return retryCount == null ? 0 : retryCount; + } + + /** + * Gets the Interval after which nextRetry should be done. + * + * @param clientId clientId + * @param lastException lastException + * @param remainingTime remainingTime to retry + * @return returns 'null' Duration when not Allowed + */ + public Duration getNextRetryInterval(String clientId, Exception lastException, Duration remainingTime) { + int baseWaitTime = 0; + synchronized (this.serverBusySync) { + if (lastException != null && + (lastException instanceof ServerBusyException || (lastException.getCause() != null && lastException.getCause() instanceof ServerBusyException))) { + baseWaitTime += ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS; + } + } + + return this.onGetNextRetryInterval(clientId, lastException, remainingTime, baseWaitTime); + } + + protected abstract Duration onGetNextRetryInterval(String clientId, Exception lastException, Duration remainingTime, int baseWaitTime); + + @Override + public String toString() { + return this.name; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SenderContext.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SenderContext.java index fb66d0604..36150eaa0 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SenderContext.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SenderContext.java @@ -6,60 +6,52 @@ import java.util.Locale; -public class SenderContext extends ErrorContext -{ - final String sendPath; - final String referenceId; - final Integer currentLinkCredit; - - SenderContext( - final String namespaceName, - final String sendPath, - final String referenceId, - final Integer currentLinkCredit) - { - super(namespaceName); - - this.sendPath = sendPath; - this.referenceId = referenceId; - this.currentLinkCredit = currentLinkCredit; - } - - @Override - public String toString() - { - final String superString = super.toString(); - StringBuilder toString = new StringBuilder(); - - if (!StringUtil.isNullOrEmpty(superString)) - { - toString.append(superString); - toString.append(", "); - } - - if (this.sendPath != null) - { - toString.append(String.format(Locale.US, "PATH: %s", this.sendPath)); - toString.append(", "); - } - - if (this.referenceId != null) - { - toString.append(String.format(Locale.US, "REFERENCE_ID: %s", this.referenceId)); - toString.append(", "); - } - - if (this.currentLinkCredit != null) - { - toString.append(String.format(Locale.US, "LINK_CREDIT: %s", this.currentLinkCredit)); - toString.append(", "); - } - - if (toString.length() > 2) - { - toString.setLength(toString.length() - 2); - } - - return toString.toString(); - } +public class SenderContext extends ErrorContext { + final String sendPath; + final String referenceId; + final Integer currentLinkCredit; + + SenderContext( + final String namespaceName, + final String sendPath, + final String referenceId, + final Integer currentLinkCredit) { + super(namespaceName); + + this.sendPath = sendPath; + this.referenceId = referenceId; + this.currentLinkCredit = currentLinkCredit; + } + + @Override + public String toString() { + final String superString = super.toString(); + StringBuilder toString = new StringBuilder(); + + if (!StringUtil.isNullOrEmpty(superString)) { + toString.append(superString); + toString.append(", "); + } + + if (this.sendPath != null) { + toString.append(String.format(Locale.US, "PATH: %s", this.sendPath)); + toString.append(", "); + } + + if (this.referenceId != null) { + toString.append(String.format(Locale.US, "REFERENCE_ID: %s", this.referenceId)); + toString.append(", "); + } + + if (this.currentLinkCredit != null) { + toString.append(String.format(Locale.US, "LINK_CREDIT: %s", this.currentLinkCredit)); + toString.append(", "); + } + + if (toString.length() > 2) { + toString.setLength(toString.length() - 2); + } + + return toString.toString(); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServerBusyException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServerBusyException.java index ee1d47995..d8810c103 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServerBusyException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ServerBusyException.java @@ -7,32 +7,28 @@ /** * Server busy exception is thrown when the current entity's activity has put excessive load onto the service. * When encountered this exception user should wait at least 4 seconds before any retry/runtime operations for the said entity again. + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class ServerBusyException extends ServiceBusException -{ - private static final long serialVersionUID = 1L; +public class ServerBusyException extends ServiceBusException { + private static final long serialVersionUID = 1L; - /** - * Default constructor for the exception - */ - public ServerBusyException() - { - super(true); - } + /** + * Default constructor for the exception + */ + public ServerBusyException() { + super(true); + } - ServerBusyException(final String message) - { - super(true, message); - } + ServerBusyException(final String message) { + super(true, message); + } - ServerBusyException(final Throwable cause) - { - super(true, cause); - } + ServerBusyException(final Throwable cause) { + super(true, cause); + } - ServerBusyException(final String message, final Throwable cause) - { - super(true, message, cause); - } + ServerBusyException(final String message, final Throwable cause) { + super(true, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SharedAccessSignatureTokenProvider.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SharedAccessSignatureTokenProvider.java index 1852da9bd..a1d82e22d 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SharedAccessSignatureTokenProvider.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/SharedAccessSignatureTokenProvider.java @@ -17,79 +17,70 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -public class SharedAccessSignatureTokenProvider -{ - final String keyName; - final String sharedAccessKey; - final String sharedAccessSignature; - - SharedAccessSignatureTokenProvider( - final String keyName, - final String sharedAccessKey) - { - this.keyName = keyName; - this.sharedAccessKey = sharedAccessKey; - this.sharedAccessSignature = null; - } +public class SharedAccessSignatureTokenProvider { + final String keyName; + final String sharedAccessKey; + final String sharedAccessSignature; + + SharedAccessSignatureTokenProvider( + final String keyName, + final String sharedAccessKey) { + this.keyName = keyName; + this.sharedAccessKey = sharedAccessKey; + this.sharedAccessSignature = null; + } + + public SharedAccessSignatureTokenProvider(final String sharedAccessSignature) { + this.keyName = null; + this.sharedAccessKey = null; + this.sharedAccessSignature = sharedAccessSignature; + } + + public String getToken(final String resource, final Duration tokenTimeToLive) throws IOException, InvalidKeyException, NoSuchAlgorithmException { + return this.sharedAccessSignature == null + ? generateSharedAccessSignature(this.keyName, this.sharedAccessKey, resource, tokenTimeToLive) + : this.sharedAccessSignature; + } - public SharedAccessSignatureTokenProvider(final String sharedAccessSignature) - { - this.keyName = null; - this.sharedAccessKey = null; - this.sharedAccessSignature = sharedAccessSignature; - } - - public String getToken(final String resource, final Duration tokenTimeToLive) throws IOException, InvalidKeyException, NoSuchAlgorithmException - { - return this.sharedAccessSignature == null - ? generateSharedAccessSignature(this.keyName, this.sharedAccessKey, resource, tokenTimeToLive) - : this.sharedAccessSignature; + public static String generateSharedAccessSignature( + final String keyName, + final String sharedAccessKey, + final String resource, + final Duration tokenTimeToLive) + throws IOException, NoSuchAlgorithmException, InvalidKeyException { + if (StringUtil.isNullOrWhiteSpace(keyName)) { + throw new IllegalArgumentException("keyName cannot be empty"); } - - public static String generateSharedAccessSignature( - final String keyName, - final String sharedAccessKey, - final String resource, - final Duration tokenTimeToLive) - throws IOException, NoSuchAlgorithmException, InvalidKeyException - { - if (StringUtil.isNullOrWhiteSpace(keyName)) - { - throw new IllegalArgumentException("keyName cannot be empty"); - } - if (StringUtil.isNullOrWhiteSpace(sharedAccessKey)) - { - throw new IllegalArgumentException("sharedAccessKey cannot be empty"); - } + if (StringUtil.isNullOrWhiteSpace(sharedAccessKey)) { + throw new IllegalArgumentException("sharedAccessKey cannot be empty"); + } - if (StringUtil.isNullOrWhiteSpace(resource)) - { - throw new IllegalArgumentException("resource cannot be empty"); - } + if (StringUtil.isNullOrWhiteSpace(resource)) { + throw new IllegalArgumentException("resource cannot be empty"); + } - if (tokenTimeToLive.isZero() || tokenTimeToLive.isNegative()) - { - throw new IllegalArgumentException("tokenTimeToLive has to positive and in the order-of seconds"); - } + if (tokenTimeToLive.isZero() || tokenTimeToLive.isNegative()) { + throw new IllegalArgumentException("tokenTimeToLive has to positive and in the order-of seconds"); + } - final String utf8Encoding = StandardCharsets.UTF_8.name(); - String expiresOn = Long.toString(Instant.now().getEpochSecond() + tokenTimeToLive.getSeconds()); - String audienceUri = URLEncoder.encode(resource, utf8Encoding); - String secretToSign = audienceUri + "\n" + expiresOn; + final String utf8Encoding = StandardCharsets.UTF_8.name(); + String expiresOn = Long.toString(Instant.now().getEpochSecond() + tokenTimeToLive.getSeconds()); + String audienceUri = URLEncoder.encode(resource, utf8Encoding); + String secretToSign = audienceUri + "\n" + expiresOn; - final String hashAlgorithm = "HMACSHA256"; - Mac hmac = Mac.getInstance(hashAlgorithm); - byte[] sasKeyBytes = sharedAccessKey.getBytes(utf8Encoding); - SecretKeySpec finalKey = new SecretKeySpec(sasKeyBytes, hashAlgorithm); - hmac.init(finalKey); - byte[] signatureBytes = hmac.doFinal(secretToSign.getBytes(utf8Encoding)); - String signature = Base64.getEncoder().encodeToString(signatureBytes); + final String hashAlgorithm = "HMACSHA256"; + Mac hmac = Mac.getInstance(hashAlgorithm); + byte[] sasKeyBytes = sharedAccessKey.getBytes(utf8Encoding); + SecretKeySpec finalKey = new SecretKeySpec(sasKeyBytes, hashAlgorithm); + hmac.init(finalKey); + byte[] signatureBytes = hmac.doFinal(secretToSign.getBytes(utf8Encoding)); + String signature = Base64.getEncoder().encodeToString(signatureBytes); - return String.format(Locale.US, "SharedAccessSignature sr=%s&sig=%s&se=%s&skn=%s", - audienceUri, - URLEncoder.encode(signature, utf8Encoding), - URLEncoder.encode(expiresOn, utf8Encoding), - URLEncoder.encode(keyName, utf8Encoding)); - } + return String.format(Locale.US, "SharedAccessSignature sr=%s&sig=%s&se=%s&skn=%s", + audienceUri, + URLEncoder.encode(signature, utf8Encoding), + URLEncoder.encode(expiresOn, utf8Encoding), + URLEncoder.encode(keyName, utf8Encoding)); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/StringUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/StringUtil.java index 2b13cf54a..17c18b40b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/StringUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/StringUtil.java @@ -6,33 +6,27 @@ import java.util.UUID; -public final class StringUtil -{ - public final static String EMPTY = ""; +public final class StringUtil { + public final static String EMPTY = ""; - public static boolean isNullOrEmpty(String string) - { - return (string == null || string.isEmpty()); - } + public static boolean isNullOrEmpty(String string) { + return (string == null || string.isEmpty()); + } - public static boolean isNullOrWhiteSpace(String string) - { - if (string == null) - return true; + public static boolean isNullOrWhiteSpace(String string) { + if (string == null) + return true; - for (int index=0; index < string.length(); index++) - { - if (!Character.isWhitespace(string.charAt(index))) - { - return false; - } - } + for (int index = 0; index < string.length(); index++) { + if (!Character.isWhitespace(string.charAt(index))) { + return false; + } + } - return true; - } + return true; + } - public static String getRandomString() - { - return UUID.randomUUID().toString().substring(0, 6); - } + public static String getRandomString() { + return UUID.randomUUID().toString().substring(0, 6); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutException.java index 2b8d648b9..770930d19 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutException.java @@ -7,32 +7,28 @@ /** * This exception is thrown when the operation has exceeded the predetermined time limit. * User should check connectivity is healthy between client process and service. + * * @see http://go.microsoft.com/fwlink/?LinkId=761101 */ -public class TimeoutException extends ServiceBusException -{ - private static final long serialVersionUID = -3505469991851121512L; +public class TimeoutException extends ServiceBusException { + private static final long serialVersionUID = -3505469991851121512L; - /** - * Default constructor for exception type. - */ - public TimeoutException() - { - super(true); - } + /** + * Default constructor for exception type. + */ + public TimeoutException() { + super(true); + } - public TimeoutException(final String message) - { - super(true, message); - } + public TimeoutException(final String message) { + super(true, message); + } - public TimeoutException(final Throwable cause) - { - super(true, cause); - } + public TimeoutException(final Throwable cause) { + super(true, cause); + } - public TimeoutException(final String message, final Throwable cause) - { - super(true, message, cause); - } + public TimeoutException(final String message, final Throwable cause) { + super(true, message, cause); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutTracker.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutTracker.java index e8983929f..ef502c640 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutTracker.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimeoutTracker.java @@ -6,51 +6,43 @@ import java.time.*; -public class TimeoutTracker -{ - private final Duration originalTimeout; - private boolean isTimerStarted; - private Instant startTime; - - /** - * @param timeout original operationTimeout - * @param startTrackingTimeout whether/not to start the timeout tracking - right now. if not started now, timer tracking will start upon the first call to {@link TimeoutTracker#elapsed()}/{@link TimeoutTracker#remaining()} - */ - public TimeoutTracker(Duration timeout, boolean startTrackingTimeout) - { - if (timeout.compareTo(Duration.ZERO) < 0) - { - throw new IllegalArgumentException("timeout should be non-negative"); - } - - this.originalTimeout = timeout; - - if (startTrackingTimeout) - { - this.startTime = Instant.now(); - } - - this.isTimerStarted = startTrackingTimeout; - } - - public static TimeoutTracker create(Duration timeout) - { - return new TimeoutTracker(timeout, true); - } - - public Duration remaining() - { - return this.originalTimeout.minus(this.elapsed()); - } - - public Duration elapsed() - { - if (!this.isTimerStarted) - { - this.startTime = Instant.now(); - this.isTimerStarted = true; - } - - return Duration.between(this.startTime, Instant.now()); - } +public class TimeoutTracker { + private final Duration originalTimeout; + private boolean isTimerStarted; + private Instant startTime; + + /** + * @param timeout original operationTimeout + * @param startTrackingTimeout whether/not to start the timeout tracking - right now. if not started now, timer tracking will start upon the first call to {@link TimeoutTracker#elapsed()}/{@link TimeoutTracker#remaining()} + */ + public TimeoutTracker(Duration timeout, boolean startTrackingTimeout) { + if (timeout.compareTo(Duration.ZERO) < 0) { + throw new IllegalArgumentException("timeout should be non-negative"); + } + + this.originalTimeout = timeout; + + if (startTrackingTimeout) { + this.startTime = Instant.now(); + } + + this.isTimerStarted = startTrackingTimeout; + } + + public static TimeoutTracker create(Duration timeout) { + return new TimeoutTracker(timeout, true); + } + + public Duration remaining() { + return this.originalTimeout.minus(this.elapsed()); + } + + public Duration elapsed() { + if (!this.isTimerStarted) { + this.startTime = Instant.now(); + this.isTimerStarted = true; + } + + return Duration.between(this.startTime, Instant.now()); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/Timer.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/Timer.java index 2c2ecf375..411d1b186 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/Timer.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/Timer.java @@ -16,69 +16,57 @@ /** * An abstraction for a Scheduler functionality - which can later be replaced by a light-weight Thread */ -final class Timer -{ - private static ScheduledThreadPoolExecutor executor = null; +final class Timer { + private static ScheduledThreadPoolExecutor executor = null; - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private static final HashSet references = new HashSet(); - private static final Object syncReferences = new Object(); + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private static final HashSet references = new HashSet(); + private static final Object syncReferences = new Object(); - private Timer() - { - } + private Timer() { + } - /** - * @param runFrequency implemented only for TimeUnit granularity - Seconds - */ - public static ScheduledFuture schedule(Runnable runnable, Duration runFrequency, TimerType timerType) - { - switch (timerType) - { - case OneTimeRun: - return executor.schedule(runnable, runFrequency.toMillis(), TimeUnit.MILLISECONDS); + /** + * @param runFrequency implemented only for TimeUnit granularity - Seconds + */ + public static ScheduledFuture schedule(Runnable runnable, Duration runFrequency, TimerType timerType) { + switch (timerType) { + case OneTimeRun: + return executor.schedule(runnable, runFrequency.toMillis(), TimeUnit.MILLISECONDS); - case RepeatRun: - return executor.scheduleWithFixedDelay(runnable, runFrequency.toMillis(), runFrequency.toMillis(), TimeUnit.MILLISECONDS); + case RepeatRun: + return executor.scheduleWithFixedDelay(runnable, runFrequency.toMillis(), runFrequency.toMillis(), TimeUnit.MILLISECONDS); - default: - throw new UnsupportedOperationException("Unsupported timer pattern."); - } - } + default: + throw new UnsupportedOperationException("Unsupported timer pattern."); + } + } - static void register(final String clientId) - { - synchronized (syncReferences) - { - if (references.size() == 0 && (executor == null || executor.isShutdown())) - { - final int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors(), 4); - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "Starting ScheduledThreadPoolExecutor with coreThreadPoolSize: %s", corePoolSize)); - } + static void register(final String clientId) { + synchronized (syncReferences) { + if (references.size() == 0 && (executor == null || executor.isShutdown())) { + final int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors(), 4); + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "Starting ScheduledThreadPoolExecutor with coreThreadPoolSize: %s", corePoolSize)); + } - executor = new ScheduledThreadPoolExecutor(corePoolSize); - } + executor = new ScheduledThreadPoolExecutor(corePoolSize); + } - references.add(clientId); - } - } + references.add(clientId); + } + } - static void unregister(final String clientId) - { - synchronized (syncReferences) - { - if (references.remove(clientId) && references.size() == 0 && executor != null) - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "Shuting down ScheduledThreadPoolExecutor."); - } + static void unregister(final String clientId) { + synchronized (syncReferences) { + if (references.remove(clientId) && references.size() == 0 && executor != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "Shuting down ScheduledThreadPoolExecutor."); + } - executor.shutdownNow(); - } - } - } + executor.shutdownNow(); + } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimerType.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimerType.java index 7e67e0ce5..0c6794ee8 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimerType.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TimerType.java @@ -4,8 +4,7 @@ */ package com.microsoft.azure.servicebus; -public enum TimerType -{ - OneTimeRun, - RepeatRun +public enum TimerType { + OneTimeRun, + RepeatRun } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TrackingUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TrackingUtil.java index 14939124a..1a7a0297b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TrackingUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/TrackingUtil.java @@ -5,38 +5,35 @@ package com.microsoft.azure.servicebus; import java.time.Instant; + import org.apache.qpid.proton.engine.Session; -public final class TrackingUtil -{ - public static final String TRACKING_ID_TOKEN_SEPARATOR = "_"; +public final class TrackingUtil { + public static final String TRACKING_ID_TOKEN_SEPARATOR = "_"; - private TrackingUtil() - { - } + private TrackingUtil() { + } - /** - * parses ServiceBus role identifiers from trackingId - * @return null if no roleIdentifier found - */ - static String parseRoleIdentifier(final String trackingId) - { - if (StringUtil.isNullOrWhiteSpace(trackingId) || !trackingId.contains(TRACKING_ID_TOKEN_SEPARATOR)) - { - return null; - } + /** + * parses ServiceBus role identifiers from trackingId + * + * @return null if no roleIdentifier found + */ + static String parseRoleIdentifier(final String trackingId) { + if (StringUtil.isNullOrWhiteSpace(trackingId) || !trackingId.contains(TRACKING_ID_TOKEN_SEPARATOR)) { + return null; + } - return trackingId.substring(trackingId.indexOf(TRACKING_ID_TOKEN_SEPARATOR)); - } + return trackingId.substring(trackingId.indexOf(TRACKING_ID_TOKEN_SEPARATOR)); + } - public static String getLinkName(final Session session) - { - // returned linkName lookslike: ea9cac_8b_G27_1479943074829 - final String linkNamePrefix = StringUtil.getRandomString(); - final String linkNameWithServiceRoleTracker = session.getConnection() != null && !StringUtil.isNullOrEmpty(session.getConnection().getRemoteContainer()) ? - linkNamePrefix.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(session.getConnection().getRemoteContainer() - .substring(Math.max(session.getConnection().getRemoteContainer().length() - 7, 0), session.getConnection().getRemoteContainer().length())) : - linkNamePrefix; - return linkNameWithServiceRoleTracker.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(String.valueOf(Instant.now().toEpochMilli())); - } + public static String getLinkName(final Session session) { + // returned linkName lookslike: ea9cac_8b_G27_1479943074829 + final String linkNamePrefix = StringUtil.getRandomString(); + final String linkNameWithServiceRoleTracker = session.getConnection() != null && !StringUtil.isNullOrEmpty(session.getConnection().getRemoteContainer()) ? + linkNamePrefix.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(session.getConnection().getRemoteContainer() + .substring(Math.max(session.getConnection().getRemoteContainer().length() - 7, 0), session.getConnection().getRemoteContainer().length())) : + linkNamePrefix; + return linkNameWithServiceRoleTracker.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(String.valueOf(Instant.now().toEpochMilli())); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/WorkItem.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/WorkItem.java index 998b5eb3f..bcf830c57 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/WorkItem.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/WorkItem.java @@ -7,29 +7,24 @@ import java.time.*; import java.util.concurrent.*; -public class WorkItem -{ - private final TimeoutTracker tracker; - private final CompletableFuture work; +public class WorkItem { + private final TimeoutTracker tracker; + private final CompletableFuture work; - public WorkItem(final CompletableFuture completableFuture, final Duration timeout) - { - this(completableFuture, TimeoutTracker.create(timeout)); - } + public WorkItem(final CompletableFuture completableFuture, final Duration timeout) { + this(completableFuture, TimeoutTracker.create(timeout)); + } - public WorkItem(final CompletableFuture completableFuture, final TimeoutTracker tracker) - { - this.work = completableFuture; - this.tracker = tracker; - } + public WorkItem(final CompletableFuture completableFuture, final TimeoutTracker tracker) { + this.work = completableFuture; + this.tracker = tracker; + } - public TimeoutTracker getTimeoutTracker() - { - return this.tracker; - } + public TimeoutTracker getTimeoutTracker() { + return this.tracker; + } - public CompletableFuture getWork() - { - return this.work; - } + public CompletableFuture getWork() { + return this.work; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java index 971c06e3f..3471b8238 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java @@ -11,65 +11,66 @@ import org.apache.qpid.proton.amqp.Symbol; public final class AmqpConstants { - - private AmqpConstants() { } - @SuppressWarnings("serial") - public static final Set RESERVED_PROPERTY_NAMES = Collections.unmodifiableSet(new HashSet() {{ - add(AMQP_PROPERTY_MESSAGE_ID); - add(AMQP_PROPERTY_USER_ID); - add(AMQP_PROPERTY_TO); - add(AMQP_PROPERTY_SUBJECT); - add(AMQP_PROPERTY_REPLY_TO); - add(AMQP_PROPERTY_CORRELATION_ID); - add(AMQP_PROPERTY_CONTENT_TYPE); - add(AMQP_PROPERTY_CONTENT_ENCODING); - add(AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME); - add(AMQP_PROPERTY_CREATION_TIME); - add(AMQP_PROPERTY_GROUP_ID); - add(AMQP_PROPERTY_GROUP_SEQUENCE); - add(AMQP_PROPERTY_REPLY_TO_GROUP_ID); - }}); - - public static final String APACHE = "apache.org"; - public static final String VENDOR = "com.microsoft"; + private AmqpConstants() { + } - public static final String AMQP_ANNOTATION_FORMAT = "amqp.annotation.%s >%s '%s'"; - public static final String OFFSET_ANNOTATION_NAME = "x-opt-offset"; - public static final String ENQUEUED_TIME_UTC_ANNOTATION_NAME = "x-opt-enqueued-time"; - public static final String PARTITION_KEY_ANNOTATION_NAME = "x-opt-partition-key"; - public static final String SEQUENCE_NUMBER_ANNOTATION_NAME = "x-opt-sequence-number"; - public static final String PUBLISHER_ANNOTATION_NAME = "x-opt-publisher"; + @SuppressWarnings("serial") + public static final Set RESERVED_PROPERTY_NAMES = Collections.unmodifiableSet(new HashSet() {{ + add(AMQP_PROPERTY_MESSAGE_ID); + add(AMQP_PROPERTY_USER_ID); + add(AMQP_PROPERTY_TO); + add(AMQP_PROPERTY_SUBJECT); + add(AMQP_PROPERTY_REPLY_TO); + add(AMQP_PROPERTY_CORRELATION_ID); + add(AMQP_PROPERTY_CONTENT_TYPE); + add(AMQP_PROPERTY_CONTENT_ENCODING); + add(AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME); + add(AMQP_PROPERTY_CREATION_TIME); + add(AMQP_PROPERTY_GROUP_ID); + add(AMQP_PROPERTY_GROUP_SEQUENCE); + add(AMQP_PROPERTY_REPLY_TO_GROUP_ID); + }}); - public static final Symbol PARTITION_KEY = Symbol.getSymbol(PARTITION_KEY_ANNOTATION_NAME); - public static final Symbol OFFSET = Symbol.getSymbol(OFFSET_ANNOTATION_NAME); - public static final Symbol SEQUENCE_NUMBER = Symbol.getSymbol(SEQUENCE_NUMBER_ANNOTATION_NAME); - public static final Symbol ENQUEUED_TIME_UTC = Symbol.getSymbol(ENQUEUED_TIME_UTC_ANNOTATION_NAME); + public static final String APACHE = "apache.org"; + public static final String VENDOR = "com.microsoft"; - public static final Symbol STRING_FILTER = Symbol.valueOf(APACHE + ":selector-filter:string"); - public static final Symbol EPOCH = Symbol.valueOf(VENDOR + ":epoch"); - - public static final Symbol PRODUCT = Symbol.valueOf("product"); - public static final Symbol VERSION = Symbol.valueOf("version"); - public static final Symbol PLATFORM = Symbol.valueOf("platform"); + public static final String AMQP_ANNOTATION_FORMAT = "amqp.annotation.%s >%s '%s'"; + public static final String OFFSET_ANNOTATION_NAME = "x-opt-offset"; + public static final String ENQUEUED_TIME_UTC_ANNOTATION_NAME = "x-opt-enqueued-time"; + public static final String PARTITION_KEY_ANNOTATION_NAME = "x-opt-partition-key"; + public static final String SEQUENCE_NUMBER_ANNOTATION_NAME = "x-opt-sequence-number"; + public static final String PUBLISHER_ANNOTATION_NAME = "x-opt-publisher"; - public static final int AMQP_BATCH_MESSAGE_FORMAT = 0x80013700; // 2147563264L; + public static final Symbol PARTITION_KEY = Symbol.getSymbol(PARTITION_KEY_ANNOTATION_NAME); + public static final Symbol OFFSET = Symbol.getSymbol(OFFSET_ANNOTATION_NAME); + public static final Symbol SEQUENCE_NUMBER = Symbol.getSymbol(SEQUENCE_NUMBER_ANNOTATION_NAME); + public static final Symbol ENQUEUED_TIME_UTC = Symbol.getSymbol(ENQUEUED_TIME_UTC_ANNOTATION_NAME); - public static final int MAX_FRAME_SIZE = 65536; - - public static final String AMQP_PROPERTY_MESSAGE_ID = "message-id"; - public static final String AMQP_PROPERTY_USER_ID = "user-id"; - public static final String AMQP_PROPERTY_TO = "to"; - public static final String AMQP_PROPERTY_SUBJECT = "subject"; - public static final String AMQP_PROPERTY_REPLY_TO = "reply-to"; - public static final String AMQP_PROPERTY_CORRELATION_ID = "correlation-id"; - public static final String AMQP_PROPERTY_CONTENT_TYPE = "content-type"; - public static final String AMQP_PROPERTY_CONTENT_ENCODING = "content-encoding"; - public static final String AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME = "absolute-expiry-time"; - public static final String AMQP_PROPERTY_CREATION_TIME = "creation-time"; - public static final String AMQP_PROPERTY_GROUP_ID = "group-id"; - public static final String AMQP_PROPERTY_GROUP_SEQUENCE = "group-sequence"; - public static final String AMQP_PROPERTY_REPLY_TO_GROUP_ID = "reply-to-group-id"; - - public static final Symbol ENABLE_RECEIVER_RUNTIME_METRIC_NAME = Symbol.valueOf(VENDOR + ":enable-receiver-runtime-metric"); + public static final Symbol STRING_FILTER = Symbol.valueOf(APACHE + ":selector-filter:string"); + public static final Symbol EPOCH = Symbol.valueOf(VENDOR + ":epoch"); + + public static final Symbol PRODUCT = Symbol.valueOf("product"); + public static final Symbol VERSION = Symbol.valueOf("version"); + public static final Symbol PLATFORM = Symbol.valueOf("platform"); + + public static final int AMQP_BATCH_MESSAGE_FORMAT = 0x80013700; // 2147563264L; + + public static final int MAX_FRAME_SIZE = 65536; + + public static final String AMQP_PROPERTY_MESSAGE_ID = "message-id"; + public static final String AMQP_PROPERTY_USER_ID = "user-id"; + public static final String AMQP_PROPERTY_TO = "to"; + public static final String AMQP_PROPERTY_SUBJECT = "subject"; + public static final String AMQP_PROPERTY_REPLY_TO = "reply-to"; + public static final String AMQP_PROPERTY_CORRELATION_ID = "correlation-id"; + public static final String AMQP_PROPERTY_CONTENT_TYPE = "content-type"; + public static final String AMQP_PROPERTY_CONTENT_ENCODING = "content-encoding"; + public static final String AMQP_PROPERTY_ABSOLUTE_EXPRITY_TIME = "absolute-expiry-time"; + public static final String AMQP_PROPERTY_CREATION_TIME = "creation-time"; + public static final String AMQP_PROPERTY_GROUP_ID = "group-id"; + public static final String AMQP_PROPERTY_GROUP_SEQUENCE = "group-sequence"; + public static final String AMQP_PROPERTY_REPLY_TO_GROUP_ID = "reply-to-group-id"; + + public static final Symbol ENABLE_RECEIVER_RUNTIME_METRIC_NAME = Symbol.valueOf(VENDOR + ":enable-receiver-runtime-metric"); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpErrorCode.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpErrorCode.java index 6d422c365..4286d5eda 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpErrorCode.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpErrorCode.java @@ -6,22 +6,21 @@ import org.apache.qpid.proton.amqp.Symbol; -public final class AmqpErrorCode -{ +public final class AmqpErrorCode { - public static final Symbol NotFound = Symbol.getSymbol("amqp:not-found"); - public static final Symbol UnauthorizedAccess = Symbol.getSymbol("amqp:unauthorized-access"); - public static final Symbol ResourceLimitExceeded = Symbol.getSymbol("amqp:resource-limit-exceeded"); - public static final Symbol NotAllowed = Symbol.getSymbol("amqp:not-allowed"); - public static final Symbol InternalError = Symbol.getSymbol("amqp:internal-error"); - public static final Symbol IllegalState = Symbol.getSymbol("amqp:illegal-state"); - public static final Symbol NotImplemented = Symbol.getSymbol("amqp:not-implemented"); + public static final Symbol NotFound = Symbol.getSymbol("amqp:not-found"); + public static final Symbol UnauthorizedAccess = Symbol.getSymbol("amqp:unauthorized-access"); + public static final Symbol ResourceLimitExceeded = Symbol.getSymbol("amqp:resource-limit-exceeded"); + public static final Symbol NotAllowed = Symbol.getSymbol("amqp:not-allowed"); + public static final Symbol InternalError = Symbol.getSymbol("amqp:internal-error"); + public static final Symbol IllegalState = Symbol.getSymbol("amqp:illegal-state"); + public static final Symbol NotImplemented = Symbol.getSymbol("amqp:not-implemented"); - // link errors - public static final Symbol Stolen = Symbol.getSymbol("amqp:link:stolen"); - public static final Symbol PayloadSizeExceeded = Symbol.getSymbol("amqp:link:message-size-exceeded"); - public static final Symbol AmqpLinkDetachForced = Symbol.getSymbol("amqp:link:detach-forced"); + // link errors + public static final Symbol Stolen = Symbol.getSymbol("amqp:link:stolen"); + public static final Symbol PayloadSizeExceeded = Symbol.getSymbol("amqp:link:message-size-exceeded"); + public static final Symbol AmqpLinkDetachForced = Symbol.getSymbol("amqp:link:detach-forced"); - // connection errors - public static final Symbol ConnectionForced = Symbol.getSymbol("amqp:connection:forced"); + // connection errors + public static final Symbol ConnectionForced = Symbol.getSymbol("amqp:connection:forced"); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpException.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpException.java index 695bf08a8..1a73efc47 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpException.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpException.java @@ -7,21 +7,18 @@ import org.apache.qpid.proton.amqp.transport.*; /** - * All AmqpExceptions - which EventHub client handles internally. + * All AmqpExceptions - which EventHub client handles internally. */ -public class AmqpException extends Exception -{ - private static final long serialVersionUID = -750417419234273714L; - private ErrorCondition errorCondition; +public class AmqpException extends Exception { + private static final long serialVersionUID = -750417419234273714L; + private ErrorCondition errorCondition; - public AmqpException(ErrorCondition errorCondition) - { - super(errorCondition.getDescription()); - this.errorCondition = errorCondition; - } + public AmqpException(ErrorCondition errorCondition) { + super(errorCondition.getDescription()); + this.errorCondition = errorCondition; + } - public ErrorCondition getError() - { - return this.errorCondition; - } + public ErrorCondition getError() { + return this.errorCondition; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpResponseCode.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpResponseCode.java index 88808ab9d..9263afeb5 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpResponseCode.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpResponseCode.java @@ -7,36 +7,33 @@ import java.util.HashMap; import java.util.Map; -public enum AmqpResponseCode -{ - ACCEPTED (0xca), - OK (200), - BAD_REQUEST (400), - NOT_FOUND (0x194), - FORBIDDEN (0x193), - INTERNAL_SERVER_ERROR (500), - UNAUTHORIZED (0x191); - +public enum AmqpResponseCode { + ACCEPTED(0xca), + OK(200), + BAD_REQUEST(400), + NOT_FOUND(0x194), + FORBIDDEN(0x193), + INTERNAL_SERVER_ERROR(500), + UNAUTHORIZED(0x191); + private final int value; - + private static Map valueMap = new HashMap<>(); - + static { - for (AmqpResponseCode code: AmqpResponseCode.values()) { + for (AmqpResponseCode code : AmqpResponseCode.values()) { valueMap.put(code.value, code); } } - - private AmqpResponseCode(final int value) - { + + private AmqpResponseCode(final int value) { this.value = value; } - - public int getValue() - { + + public int getValue() { return this.value; } - + public static AmqpResponseCode valueOf(final int value) { return valueMap.get(value); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpUtil.java index 50a0a48f4..072cf9511 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpUtil.java @@ -15,113 +15,113 @@ import org.apache.qpid.proton.message.Message; public class AmqpUtil { - + private AmqpUtil() { } private static int getPayloadSize(Message msg) { - if (msg == null || msg.getBody() == null) { - return 0; - } - - if (msg.getBody() instanceof Data) { - final Data payloadSection = (Data) msg.getBody(); - if (payloadSection == null) { - return 0; - } - - final Binary payloadBytes = payloadSection.getValue(); - if (payloadBytes == null) { - return 0; - } - - return payloadBytes.getLength(); - } - - if (msg.getBody() instanceof AmqpValue) { - final AmqpValue amqpValue = (AmqpValue) msg.getBody(); - if (amqpValue == null) { - return 0; - } - - return amqpValue.getValue().toString().length() * 2; - } - + if (msg == null || msg.getBody() == null) { + return 0; + } + + if (msg.getBody() instanceof Data) { + final Data payloadSection = (Data) msg.getBody(); + if (payloadSection == null) { + return 0; + } + + final Binary payloadBytes = payloadSection.getValue(); + if (payloadBytes == null) { return 0; - } - + } + + return payloadBytes.getLength(); + } + + if (msg.getBody() instanceof AmqpValue) { + final AmqpValue amqpValue = (AmqpValue) msg.getBody(); + if (amqpValue == null) { + return 0; + } + + return amqpValue.getValue().toString().length() * 2; + } + + return 0; + } + public static int getDataSerializedSize(Message amqpMessage) { - - if (amqpMessage == null) { - return 0; - } - - int payloadSize = getPayloadSize(amqpMessage); - - // EventData - accepts only PartitionKey - which is a String & stuffed into MessageAnnotation - final MessageAnnotations messageAnnotations = amqpMessage.getMessageAnnotations(); - final ApplicationProperties applicationProperties = amqpMessage.getApplicationProperties(); - - int annotationsSize = 0; - int applicationPropertiesSize = 0; - - if (messageAnnotations != null) { - for(Symbol value: messageAnnotations.getValue().keySet()) { - annotationsSize += sizeof(value); - } - - for(Object value: messageAnnotations.getValue().values()) { - annotationsSize += sizeof(value); - } - } - - if (applicationProperties != null) { - for(Object value: applicationProperties.getValue().keySet()) { - applicationPropertiesSize += sizeof(value); - } - - for(Object value: applicationProperties.getValue().values()) { - applicationPropertiesSize += sizeof(value); - } - } - - return annotationsSize + applicationPropertiesSize + payloadSize; - } + + if (amqpMessage == null) { + return 0; + } + + int payloadSize = getPayloadSize(amqpMessage); + + // EventData - accepts only PartitionKey - which is a String & stuffed into MessageAnnotation + final MessageAnnotations messageAnnotations = amqpMessage.getMessageAnnotations(); + final ApplicationProperties applicationProperties = amqpMessage.getApplicationProperties(); + + int annotationsSize = 0; + int applicationPropertiesSize = 0; + + if (messageAnnotations != null) { + for (Symbol value : messageAnnotations.getValue().keySet()) { + annotationsSize += sizeof(value); + } + + for (Object value : messageAnnotations.getValue().values()) { + annotationsSize += sizeof(value); + } + } + + if (applicationProperties != null) { + for (Object value : applicationProperties.getValue().keySet()) { + applicationPropertiesSize += sizeof(value); + } + + for (Object value : applicationProperties.getValue().values()) { + applicationPropertiesSize += sizeof(value); + } + } + + return annotationsSize + applicationPropertiesSize + payloadSize; + } private static int sizeof(Object obj) { - if (obj instanceof String) { - return obj.toString().length() << 1; - } - - if (obj instanceof Symbol) { - return ((Symbol) obj).length() << 1; - } - - if (obj instanceof Integer) { - return Integer.BYTES; - } - - if (obj instanceof Long) { - return Long.BYTES; - } - - if (obj instanceof Short) { - return Short.BYTES; - } - - if (obj instanceof Character) { - return Character.BYTES; - } - - if (obj instanceof Float) { - return Float.BYTES; - } - - if (obj instanceof Double) { - return Double.BYTES; - } - - throw new IllegalArgumentException(String.format(Locale.US, "Encoding Type: %s is not supported", obj.getClass())); - } + if (obj instanceof String) { + return obj.toString().length() << 1; + } + + if (obj instanceof Symbol) { + return ((Symbol) obj).length() << 1; + } + + if (obj instanceof Integer) { + return Integer.BYTES; + } + + if (obj instanceof Long) { + return Long.BYTES; + } + + if (obj instanceof Short) { + return Short.BYTES; + } + + if (obj instanceof Character) { + return Character.BYTES; + } + + if (obj instanceof Float) { + return Float.BYTES; + } + + if (obj instanceof Double) { + return Double.BYTES; + } + + throw new IllegalArgumentException(String.format(Locale.US, "Encoding Type: %s is not supported", obj.getClass())); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java index 761ee6f7a..1eec32ff0 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java @@ -12,91 +12,75 @@ import com.microsoft.azure.servicebus.ClientConstants; -public class BaseLinkHandler extends BaseHandler -{ - protected static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - private final IAmqpLink underlyingEntity; - - public BaseLinkHandler(final IAmqpLink amqpLink) - { - this.underlyingEntity = amqpLink; - } - - @Override - public void onLinkLocalClose(Event event) - { - Link link = event.getLink(); - if (link != null) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format("linkName[%s]", link.getName())); - } - } - - closeSession(link); - } - - @Override - public void onLinkRemoteClose(Event event) - { - final Link link = event.getLink(); - - if (link.getLocalState() != EndpointState.CLOSED) - { - link.close(); - } - - if (link != null) - { - ErrorCondition condition = link.getRemoteCondition(); - this.processOnClose(link, condition); - } - - closeSession(link); - } - - @Override - public void onLinkRemoteDetach(Event event) - { - final Link link = event.getLink(); - - if (link.getLocalState() != EndpointState.CLOSED) - { - link.close(); - } - - if (link != null) - { - this.processOnClose(link, link.getRemoteCondition()); - } - - closeSession(link); - } - - public void processOnClose(Link link, ErrorCondition condition) - { - if (condition != null) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "linkName[" + link.getName() + - (condition != null ? "], ErrorCondition[" + condition.getCondition() + ", " + condition.getDescription() + "]" : "], condition[null]")); - } - } - - this.underlyingEntity.onClose(condition); - } - - public void processOnClose(Link link, Exception exception) - { - this.underlyingEntity.onError(exception); - } - - private void closeSession(Link link) - { - if (link.getSession() != null && link.getSession().getLocalState() != EndpointState.CLOSED) - link.getSession().close(); - } +public class BaseLinkHandler extends BaseHandler { + protected static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + + private final IAmqpLink underlyingEntity; + + public BaseLinkHandler(final IAmqpLink amqpLink) { + this.underlyingEntity = amqpLink; + } + + @Override + public void onLinkLocalClose(Event event) { + Link link = event.getLink(); + if (link != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format("linkName[%s]", link.getName())); + } + } + + closeSession(link); + } + + @Override + public void onLinkRemoteClose(Event event) { + final Link link = event.getLink(); + + if (link.getLocalState() != EndpointState.CLOSED) { + link.close(); + } + + if (link != null) { + ErrorCondition condition = link.getRemoteCondition(); + this.processOnClose(link, condition); + } + + closeSession(link); + } + + @Override + public void onLinkRemoteDetach(Event event) { + final Link link = event.getLink(); + + if (link.getLocalState() != EndpointState.CLOSED) { + link.close(); + } + + if (link != null) { + this.processOnClose(link, link.getRemoteCondition()); + } + + closeSession(link); + } + + public void processOnClose(Link link, ErrorCondition condition) { + if (condition != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "linkName[" + link.getName() + + (condition != null ? "], ErrorCondition[" + condition.getCondition() + ", " + condition.getDescription() + "]" : "], condition[null]")); + } + } + + this.underlyingEntity.onClose(condition); + } + + public void processOnClose(Link link, Exception exception) { + this.underlyingEntity.onError(exception); + } + + private void closeSession(Link link) { + if (link.getSession() != null && link.getSession().getLocalState() != EndpointState.CLOSED) + link.getSession().close(); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java index 1025b056c..91784aa79 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java @@ -26,120 +26,102 @@ // ServiceBus <-> ProtonReactor interaction handles all // amqp_connection/transport related events from reactor -public final class ConnectionHandler extends BaseHandler -{ - - private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - private final IAmqpConnection messagingFactory; - - public ConnectionHandler(final IAmqpConnection messagingFactory) - { - add(new Handshaker()); - this.messagingFactory = messagingFactory; - } - - @Override - public void onConnectionInit(Event event) - { - final Connection connection = event.getConnection(); - final String hostName = event.getReactor().getConnectionAddress(connection); - - connection.setHostname(hostName); - connection.setContainer(StringUtil.getRandomString()); - - final Map connectionProperties = new HashMap(); - connectionProperties.put(AmqpConstants.PRODUCT, ClientConstants.PRODUCT_NAME); - connectionProperties.put(AmqpConstants.VERSION, ClientConstants.CURRENT_JAVACLIENT_VERSION); - connectionProperties.put(AmqpConstants.PLATFORM, ClientConstants.PLATFORM_INFO); - connection.setProperties(connectionProperties); - - connection.open(); - } - - @Override - public void onConnectionBound(Event event) - { - Transport transport = event.getTransport(); - - SslDomain domain = makeDomain(SslDomain.Mode.CLIENT); - transport.ssl(domain); - - Sasl sasl = transport.sasl(); - sasl.setMechanisms("ANONYMOUS"); - } - - @Override - public void onConnectionUnbound(Event event) - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "Connection.onConnectionUnbound: hostname[" + event.getConnection().getHostname() + "]"); - } - } - - @Override - public void onTransportError(Event event) - { - ErrorCondition condition = event.getTransport().getCondition(); - if (condition != null) - { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, "Connection.onTransportError: hostname[" + event.getConnection().getHostname() + "], error[" + condition.getDescription() + "]"); - } - } - else - { - if (TRACE_LOGGER.isLoggable(Level.WARNING)) - { - TRACE_LOGGER.log(Level.WARNING, "Connection.onTransportError: hostname[" + event.getConnection().getHostname() + "], error[no description returned]"); - } - } - - this.messagingFactory.onConnectionError(condition); - } - - @Override - public void onConnectionRemoteOpen(Event event) - { - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "Connection.onConnectionRemoteOpen: hostname[" + event.getConnection().getHostname() + ", " + event.getConnection().getRemoteContainer() +"]"); - } - - this.messagingFactory.onOpenComplete(null); - } - - @Override - public void onConnectionRemoteClose(Event event) - { - final Connection connection = event.getConnection(); - final ErrorCondition error = connection.getRemoteCondition(); - - if (TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, "hostname[" + connection.getHostname() + - (error != null - ? "], errorCondition[" + error.getCondition() + ", " + error.getDescription() + "]" - : "]")); - } - - if (connection.getRemoteState() != EndpointState.CLOSED) - { - connection.close(); - } - - this.messagingFactory.onConnectionError(error); - } - - private static SslDomain makeDomain(SslDomain.Mode mode) - { - SslDomain domain = Proton.sslDomain(); - domain.init(mode); - - // TODO: VERIFY_PEER_NAME support - domain.setPeerAuthentication(SslDomain.VerifyMode.ANONYMOUS_PEER); - return domain; - } +public final class ConnectionHandler extends BaseHandler { + + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + + private final IAmqpConnection messagingFactory; + + public ConnectionHandler(final IAmqpConnection messagingFactory) { + add(new Handshaker()); + this.messagingFactory = messagingFactory; + } + + @Override + public void onConnectionInit(Event event) { + final Connection connection = event.getConnection(); + final String hostName = event.getReactor().getConnectionAddress(connection); + + connection.setHostname(hostName); + connection.setContainer(StringUtil.getRandomString()); + + final Map connectionProperties = new HashMap(); + connectionProperties.put(AmqpConstants.PRODUCT, ClientConstants.PRODUCT_NAME); + connectionProperties.put(AmqpConstants.VERSION, ClientConstants.CURRENT_JAVACLIENT_VERSION); + connectionProperties.put(AmqpConstants.PLATFORM, ClientConstants.PLATFORM_INFO); + connection.setProperties(connectionProperties); + + connection.open(); + } + + @Override + public void onConnectionBound(Event event) { + Transport transport = event.getTransport(); + + SslDomain domain = makeDomain(SslDomain.Mode.CLIENT); + transport.ssl(domain); + + Sasl sasl = transport.sasl(); + sasl.setMechanisms("ANONYMOUS"); + } + + @Override + public void onConnectionUnbound(Event event) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "Connection.onConnectionUnbound: hostname[" + event.getConnection().getHostname() + "]"); + } + } + + @Override + public void onTransportError(Event event) { + ErrorCondition condition = event.getTransport().getCondition(); + if (condition != null) { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, "Connection.onTransportError: hostname[" + event.getConnection().getHostname() + "], error[" + condition.getDescription() + "]"); + } + } else { + if (TRACE_LOGGER.isLoggable(Level.WARNING)) { + TRACE_LOGGER.log(Level.WARNING, "Connection.onTransportError: hostname[" + event.getConnection().getHostname() + "], error[no description returned]"); + } + } + + this.messagingFactory.onConnectionError(condition); + } + + @Override + public void onConnectionRemoteOpen(Event event) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "Connection.onConnectionRemoteOpen: hostname[" + event.getConnection().getHostname() + ", " + event.getConnection().getRemoteContainer() + "]"); + } + + this.messagingFactory.onOpenComplete(null); + } + + @Override + public void onConnectionRemoteClose(Event event) { + final Connection connection = event.getConnection(); + final ErrorCondition error = connection.getRemoteCondition(); + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, "hostname[" + connection.getHostname() + + (error != null + ? "], errorCondition[" + error.getCondition() + ", " + error.getDescription() + "]" + : "]")); + } + + if (connection.getRemoteState() != EndpointState.CLOSED) { + connection.close(); + } + + this.messagingFactory.onConnectionError(error); + } + + private static SslDomain makeDomain(SslDomain.Mode mode) { + SslDomain domain = Proton.sslDomain(); + domain.init(mode); + + // TODO: VERIFY_PEER_NAME support + domain.setPeerAuthentication(SslDomain.VerifyMode.ANONYMOUS_PEER); + return domain; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/CustomIOHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/CustomIOHandler.java index e8037884d..6598681ed 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/CustomIOHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/CustomIOHandler.java @@ -7,21 +7,18 @@ import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.reactor.impl.IOHandler; -public class CustomIOHandler extends IOHandler -{ - @Override - public void onConnectionLocalOpen(Event event) - { - Connection connection = event.getConnection(); - if (connection.getRemoteState() != EndpointState.UNINITIALIZED) - { - return; - } +public class CustomIOHandler extends IOHandler { + @Override + public void onConnectionLocalOpen(Event event) { + Connection connection = event.getConnection(); + if (connection.getRemoteState() != EndpointState.UNINITIALIZED) { + return; + } - Transport transport = Proton.transport(); - transport.setMaxFrameSize(AmqpConstants.MAX_FRAME_SIZE); - transport.sasl(); - transport.setEmitFlowEventOnSend(false); - transport.bind(connection); - } + Transport transport = Proton.transport(); + transport.setMaxFrameSize(AmqpConstants.MAX_FRAME_SIZE); + transport.sasl(); + transport.setEmitFlowEventOnSend(false); + transport.bind(connection); + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/DispatchHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/DispatchHandler.java index 56f372579..5d2311ced 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/DispatchHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/DispatchHandler.java @@ -3,12 +3,11 @@ import org.apache.qpid.proton.engine.BaseHandler; import org.apache.qpid.proton.engine.Event; -public abstract class DispatchHandler extends BaseHandler -{ - @Override public void onTimerTask(Event e) - { - this.onEvent(); - } - - public abstract void onEvent(); +public abstract class DispatchHandler extends BaseHandler { + @Override + public void onTimerTask(Event e) { + this.onEvent(); + } + + public abstract void onEvent(); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java index a931739f4..24d036efa 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java @@ -7,13 +7,12 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.engine.Link; -public interface IAmqpConnection -{ - void onOpenComplete(Exception exception); +public interface IAmqpConnection { + void onOpenComplete(Exception exception); - void onConnectionError(ErrorCondition error); + void onConnectionError(ErrorCondition error); - void registerForConnectionError(Link link); + void registerForConnectionError(Link link); - void deregisterForConnectionError(Link link); + void deregisterForConnectionError(Link link); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpLink.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpLink.java index e959d38e0..cf4b76ba3 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpLink.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpLink.java @@ -6,14 +6,13 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition; -public interface IAmqpLink -{ - /** - * @param completionException completionException=null if open is successful - */ - void onOpenComplete(Exception completionException); +public interface IAmqpLink { + /** + * @param completionException completionException=null if open is successful + */ + void onOpenComplete(Exception completionException); - void onError(Exception exception); + void onError(Exception exception); - void onClose(ErrorCondition condition); + void onClose(ErrorCondition condition); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpReceiver.java index 0e6bae519..f06d53a1b 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpReceiver.java @@ -6,7 +6,6 @@ import org.apache.qpid.proton.engine.Delivery; -public interface IAmqpReceiver extends IAmqpLink -{ - void onReceiveComplete(Delivery delivery); +public interface IAmqpReceiver extends IAmqpLink { + void onReceiveComplete(Delivery delivery); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpSender.java index 737f0ac30..13e4c7ccd 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpSender.java @@ -6,9 +6,8 @@ import org.apache.qpid.proton.engine.Delivery; -public interface IAmqpSender extends IAmqpLink -{ - void onFlow(final int creditIssued); +public interface IAmqpSender extends IAmqpLink { + void onFlow(final int creditIssued); - void onSendComplete(final Delivery delivery); + void onSendComplete(final Delivery delivery); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperation.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperation.java index fff9fde20..8576b861c 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperation.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperation.java @@ -5,6 +5,6 @@ package com.microsoft.azure.servicebus.amqp; public interface IOperation { - + public void run(IOperationResult operationCallback); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperationResult.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperationResult.java index fcb7ddf83..fda0076df 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperationResult.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/IOperationResult.java @@ -6,8 +6,8 @@ public interface IOperationResult { - + void onComplete(T result); - - void onError(E error); + + void onError(E error); } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ProtonUtil.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ProtonUtil.java index 40183d23c..3e117f89a 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ProtonUtil.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ProtonUtil.java @@ -5,18 +5,19 @@ package com.microsoft.azure.servicebus.amqp; import java.io.IOException; + import org.apache.qpid.proton.Proton; import org.apache.qpid.proton.reactor.Reactor; public final class ProtonUtil { - - private ProtonUtil() { - } - public static Reactor reactor(ReactorHandler reactorHandler) throws IOException { + private ProtonUtil() { + } + + public static Reactor reactor(ReactorHandler reactorHandler) throws IOException { - final Reactor reactor = Proton.reactor(reactorHandler); - reactor.setGlobalHandler(new CustomIOHandler()); - return reactor; - } + final Reactor reactor = Proton.reactor(reactorHandler); + reactor.setGlobalHandler(new CustomIOHandler()); + return reactor; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java index 3667fe709..540772690 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java @@ -24,137 +24,108 @@ * So, the following utility class is used to generate an Event to hook into {@link Reactor}'s event delegation pattern. * It uses a {@link Pipe} as the IO on which Reactor Listens to. * Cardinality: multiple {@link ReactorDispatcher}'s could be attached to 1 {@link Reactor}. - * Each {@link ReactorDispatcher} should be initialized Synchronously - as it calls API in {@link Reactor} which is not thread-safe. + * Each {@link ReactorDispatcher} should be initialized Synchronously - as it calls API in {@link Reactor} which is not thread-safe. */ -public final class ReactorDispatcher -{ - private final Reactor reactor; - private final Pipe ioSignal; - private final ConcurrentLinkedQueue workQueue; - private final ScheduleHandler workScheduler; - - public ReactorDispatcher(final Reactor reactor) throws IOException - { - this.reactor = reactor; - this.ioSignal = Pipe.open(); - this.workQueue = new ConcurrentLinkedQueue<>(); - this.workScheduler = new ScheduleHandler(); - - initializeSelectable(); - } - - private void initializeSelectable() - { - Selectable schedulerSelectable = this.reactor.selectable(); - - schedulerSelectable.setChannel(this.ioSignal.source()); - schedulerSelectable.onReadable(this.workScheduler); - schedulerSelectable.onFree(new CloseHandler()); - - schedulerSelectable.setReading(true); - this.reactor.update(schedulerSelectable); - } - - public void invoke(final DispatchHandler timerCallback) throws IOException - { - this.workQueue.offer(timerCallback); - this.signalWorkQueue(); - } - - public void invoke(final int delay, final DispatchHandler timerCallback) throws IOException - { - this.workQueue.offer(new DelayHandler(this.reactor, delay, timerCallback)); - this.signalWorkQueue(); - } - - private void signalWorkQueue() throws IOException - { - try - { - while (this.ioSignal.sink().write(ByteBuffer.allocate(1)) == 0) {} - } - catch(ClosedChannelException ignorePipeClosedDuringReactorShutdown) - { - } - } - - private final class DelayHandler extends BaseHandler - { - final int delay; - final BaseHandler timerCallback; - final Reactor reactor; - - public DelayHandler(final Reactor reactor, final int delay, final DispatchHandler timerCallback) - { - this.delay = delay; - this.timerCallback = timerCallback; - this.reactor = reactor; - } - - @Override - public void onTimerTask(Event e) - { - this.reactor.schedule(this.delay, this.timerCallback); - } - } - - private final class ScheduleHandler implements Callback - { - @Override - public void run(Selectable selectable) - { - try - { - ioSignal.source().read(ByteBuffer.allocate(1024)); - } - catch(ClosedChannelException ignorePipeClosedDuringReactorShutdown) - { - } - catch(IOException ioException) - { - throw new RuntimeException(ioException); - } - - - BaseHandler topWork; - while ((topWork = workQueue.poll()) != null) - { - topWork.onTimerTask(null); - } - } - } - - private final class CloseHandler implements Callback - { - @Override public void run(Selectable selectable) - { - try - { - selectable.getChannel().close(); - } - catch (IOException ignore) - { - } - - try - { - if (ioSignal.sink().isOpen()) - ioSignal.sink().close(); - } - catch (IOException ignore) - { - } - - workScheduler.run(null); - - try - { - if (ioSignal.source().isOpen()) - ioSignal.source().close(); - } - catch (IOException ignore) - { - } - } - } +public final class ReactorDispatcher { + private final Reactor reactor; + private final Pipe ioSignal; + private final ConcurrentLinkedQueue workQueue; + private final ScheduleHandler workScheduler; + + public ReactorDispatcher(final Reactor reactor) throws IOException { + this.reactor = reactor; + this.ioSignal = Pipe.open(); + this.workQueue = new ConcurrentLinkedQueue<>(); + this.workScheduler = new ScheduleHandler(); + + initializeSelectable(); + } + + private void initializeSelectable() { + Selectable schedulerSelectable = this.reactor.selectable(); + + schedulerSelectable.setChannel(this.ioSignal.source()); + schedulerSelectable.onReadable(this.workScheduler); + schedulerSelectable.onFree(new CloseHandler()); + + schedulerSelectable.setReading(true); + this.reactor.update(schedulerSelectable); + } + + public void invoke(final DispatchHandler timerCallback) throws IOException { + this.workQueue.offer(timerCallback); + this.signalWorkQueue(); + } + + public void invoke(final int delay, final DispatchHandler timerCallback) throws IOException { + this.workQueue.offer(new DelayHandler(this.reactor, delay, timerCallback)); + this.signalWorkQueue(); + } + + private void signalWorkQueue() throws IOException { + try { + while (this.ioSignal.sink().write(ByteBuffer.allocate(1)) == 0) { + } + } catch (ClosedChannelException ignorePipeClosedDuringReactorShutdown) { + } + } + + private final class DelayHandler extends BaseHandler { + final int delay; + final BaseHandler timerCallback; + final Reactor reactor; + + public DelayHandler(final Reactor reactor, final int delay, final DispatchHandler timerCallback) { + this.delay = delay; + this.timerCallback = timerCallback; + this.reactor = reactor; + } + + @Override + public void onTimerTask(Event e) { + this.reactor.schedule(this.delay, this.timerCallback); + } + } + + private final class ScheduleHandler implements Callback { + @Override + public void run(Selectable selectable) { + try { + ioSignal.source().read(ByteBuffer.allocate(1024)); + } catch (ClosedChannelException ignorePipeClosedDuringReactorShutdown) { + } catch (IOException ioException) { + throw new RuntimeException(ioException); + } + + + BaseHandler topWork; + while ((topWork = workQueue.poll()) != null) { + topWork.onTimerTask(null); + } + } + } + + private final class CloseHandler implements Callback { + @Override + public void run(Selectable selectable) { + try { + selectable.getChannel().close(); + } catch (IOException ignore) { + } + + try { + if (ioSignal.sink().isOpen()) + ioSignal.sink().close(); + } catch (IOException ignore) { + } + + workScheduler.run(null); + + try { + if (ioSignal.source().isOpen()) + ioSignal.source().close(); + } catch (IOException ignore) { + } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorHandler.java index f018a163a..798dc0c15 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorHandler.java @@ -14,24 +14,24 @@ import com.microsoft.azure.servicebus.ClientConstants; public class ReactorHandler extends BaseHandler { - + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - + private ReactorDispatcher reactorDispatcher; - + public ReactorDispatcher getReactorDispatcher() { return this.reactorDispatcher; } - + // set needs to happen before starting reactorThread public void unsafeSetReactorDispatcher(final ReactorDispatcher reactorDispatcher) { this.reactorDispatcher = reactorDispatcher; } - + @Override public void onReactorInit(Event e) { - - if(TRACE_LOGGER.isLoggable(Level.FINE)) { + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { TRACE_LOGGER.log(Level.FINE, "reactor.onReactorInit"); } @@ -39,9 +39,10 @@ public void onReactorInit(Event e) { reactor.setTimeout(ClientConstants.REACTOR_IO_POLL_TIMEOUT); } - @Override public void onReactorFinal(Event e) { - - if(TRACE_LOGGER.isLoggable(Level.FINE)) { + @Override + public void onReactorFinal(Event e) { + + if (TRACE_LOGGER.isLoggable(Level.FINE)) { TRACE_LOGGER.log(Level.FINE, "reactor.onReactorFinal"); } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReceiveLinkHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReceiveLinkHandler.java index 67ca037d6..5d96d90ad 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReceiveLinkHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/ReceiveLinkHandler.java @@ -14,96 +14,78 @@ // ServiceBus <-> ProtonReactor interaction // handles all recvLink - reactor events -public final class ReceiveLinkHandler extends BaseLinkHandler -{ - private final IAmqpReceiver amqpReceiver; - private final Object firstResponse; - private boolean isFirstResponse; +public final class ReceiveLinkHandler extends BaseLinkHandler { + private final IAmqpReceiver amqpReceiver; + private final Object firstResponse; + private boolean isFirstResponse; - public ReceiveLinkHandler(final IAmqpReceiver receiver) - { - super(receiver); + public ReceiveLinkHandler(final IAmqpReceiver receiver) { + super(receiver); - this.amqpReceiver = receiver; - this.firstResponse = new Object(); - this.isFirstResponse = true; - } + this.amqpReceiver = receiver; + this.firstResponse = new Object(); + this.isFirstResponse = true; + } - @Override - public void onLinkLocalOpen(Event evt) - { - Link link = evt.getLink(); - if (link instanceof Receiver) - { - Receiver receiver = (Receiver) link; + @Override + public void onLinkLocalOpen(Event evt) { + Link link = evt.getLink(); + if (link instanceof Receiver) { + Receiver receiver = (Receiver) link; - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format("linkName[%s], localSource[%s]", receiver.getName(), receiver.getSource())); - } - } - } + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format("linkName[%s], localSource[%s]", receiver.getName(), receiver.getSource())); + } + } + } - @Override - public void onLinkRemoteOpen(Event event) - { - Link link = event.getLink(); - if (link != null && link instanceof Receiver) - { - Receiver receiver = (Receiver) link; - if (link.getRemoteSource() != null) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "linkName[%s], remoteSource[%s]", receiver.getName(), link.getRemoteSource())); - } + @Override + public void onLinkRemoteOpen(Event event) { + Link link = event.getLink(); + if (link != null && link instanceof Receiver) { + Receiver receiver = (Receiver) link; + if (link.getRemoteSource() != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "linkName[%s], remoteSource[%s]", receiver.getName(), link.getRemoteSource())); + } - synchronized (this.firstResponse) - { - this.isFirstResponse = false; - this.amqpReceiver.onOpenComplete(null); - } - } - else - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", receiver.getName())); - } - } - } - } + synchronized (this.firstResponse) { + this.isFirstResponse = false; + this.amqpReceiver.onOpenComplete(null); + } + } else { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", receiver.getName())); + } + } + } + } - @Override - public void onDelivery(Event event) - { - synchronized (this.firstResponse) - { - if (this.isFirstResponse) - { - this.isFirstResponse = false; - this.amqpReceiver.onOpenComplete(null); - } - } + @Override + public void onDelivery(Event event) { + synchronized (this.firstResponse) { + if (this.isFirstResponse) { + this.isFirstResponse = false; + this.amqpReceiver.onOpenComplete(null); + } + } - Delivery delivery = event.getDelivery(); - Receiver receiveLink = (Receiver) delivery.getLink(); + Delivery delivery = event.getDelivery(); + Receiver receiveLink = (Receiver) delivery.getLink(); - // If a message spans across deliveries (for ex: 200k message will be 4 frames (deliveries) 64k 64k 64k 8k), - // all until "last-1" deliveries will be partial - // reactor will raise onDelivery event for all of these - we only need the last one - if (!delivery.isPartial()) - { - this.amqpReceiver.onReceiveComplete(delivery); - } + // If a message spans across deliveries (for ex: 200k message will be 4 frames (deliveries) 64k 64k 64k 8k), + // all until "last-1" deliveries will be partial + // reactor will raise onDelivery event for all of these - we only need the last one + if (!delivery.isPartial()) { + this.amqpReceiver.onReceiveComplete(delivery); + } - if(TRACE_LOGGER.isLoggable(Level.FINEST) && receiveLink != null) - { - TRACE_LOGGER.log(Level.FINEST, - String.format(Locale.US, "linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], remoteCondition[%s], delivery.isPartial[%s]", - receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isPartial())); - } - } + if (TRACE_LOGGER.isLoggable(Level.FINEST) && receiveLink != null) { + TRACE_LOGGER.log(Level.FINEST, + String.format(Locale.US, "linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], remoteCondition[%s], delivery.isPartial[%s]", + receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isPartial())); + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/RequestResponseChannel.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/RequestResponseChannel.java index c709fd39d..c5c38f720 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/RequestResponseChannel.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/RequestResponseChannel.java @@ -28,7 +28,7 @@ import com.microsoft.azure.servicebus.StringUtil; public class RequestResponseChannel implements IIOObject { - + private final Sender sendLink; private final Receiver receiveLink; private final String replyTo; @@ -36,22 +36,22 @@ public class RequestResponseChannel implements IIOObject { private final AtomicLong requestId; private final AtomicInteger openRefCount; private final AtomicInteger closeRefCount; - + private IOperationResult onOpen; private IOperationResult onClose; // handles closeLink due to failures private IOperationResult onGraceFullClose; // handles intentional close - + public RequestResponseChannel( final String linkName, final String path, final Session session) { - + this.replyTo = path.replace("$", "") + "-client-reply-to"; this.openRefCount = new AtomicInteger(2); this.closeRefCount = new AtomicInteger(2); this.inflightRequests = new HashMap<>(); this.requestId = new AtomicLong(0); - + this.sendLink = session.sender(linkName + ":sender"); final Target target = new Target(); target.setAddress(path); @@ -59,7 +59,7 @@ public RequestResponseChannel( sendLink.setSource(new Source()); this.sendLink.setSenderSettleMode(SenderSettleMode.SETTLED); BaseHandler.setHandler(this.sendLink, new SendLinkHandler(new RequestHandler())); - + this.receiveLink = session.receiver(linkName + ":receiver"); final Source source = new Source(); source.setAddress(path); @@ -74,13 +74,13 @@ public RequestResponseChannel( // open should be called only once - we use FaultTolerantObject for that public void open(final IOperationResult onOpen, final IOperationResult onClose) { - + this.onOpen = onOpen; this.onClose = onClose; this.sendLink.open(); - this.receiveLink.open(); + this.receiveLink.open(); } - + // close should be called exactly once - we use FaultTolerantObject for that public void close(final IOperationResult onGraceFullClose) { @@ -88,15 +88,15 @@ public void close(final IOperationResult onGraceFullClose) { this.sendLink.close(); this.receiveLink.close(); } - + public Sender getSendLink() { return this.sendLink; } - + public Receiver getReceiveLink() { return this.receiveLink; } - + public void request( final ReactorDispatcher dispatcher, final Message message, @@ -107,28 +107,28 @@ public void request( if (message.getMessageId() != null) throw new IllegalArgumentException("message.getMessageId() should be null"); - + if (message.getReplyTo() != null) throw new IllegalArgumentException("message.getReplyTo() should be null"); - + message.setMessageId("request" + UnsignedLong.valueOf(this.requestId.incrementAndGet()).toString()); message.setReplyTo(this.replyTo); - + this.inflightRequests.put(message.getMessageId(), onResponse); - + try { dispatcher.invoke(new DispatchHandler() { @Override public void onEvent() { - + final Delivery delivery = sendLink.delivery(UUID.randomUUID().toString().replace("-", StringUtil.EMPTY).getBytes()); final int payloadSize = AmqpUtil.getDataSerializedSize(message) + 512; // need buffer for headers - + delivery.setContext(onResponse); - + final byte[] bytes = new byte[payloadSize]; final int encodedSize = message.encode(bytes, 0, payloadSize); - + receiveLink.flow(1); sendLink.send(bytes, 0, encodedSize); sendLink.advance(); @@ -138,9 +138,9 @@ public void onEvent() { onResponse.onError(ioException); } } - + private void onLinkOpenComplete(final Exception exception) { - + if (openRefCount.decrementAndGet() <= 0 && onOpen != null) if (exception == null && this.sendLink.getRemoteState() == EndpointState.ACTIVE && this.receiveLink.getRemoteState() == EndpointState.ACTIVE) onOpen.onComplete(null); @@ -155,16 +155,15 @@ private void onLinkOpenComplete(final Exception exception) { } } } - + private void onLinkCloseComplete(final Exception exception) { - + if (closeRefCount.decrementAndGet() <= 0) if (exception == null) { onClose.onComplete(null); if (onGraceFullClose != null) onGraceFullClose.onComplete(null); - } - else { + } else { onClose.onError(exception); if (onGraceFullClose != null) onGraceFullClose.onError(exception); @@ -173,21 +172,21 @@ private void onLinkCloseComplete(final Exception exception) { @Override public IOObjectState getState() { - + if (sendLink.getLocalState() == EndpointState.UNINITIALIZED || receiveLink.getLocalState() == EndpointState.UNINITIALIZED || sendLink.getRemoteState() == EndpointState.UNINITIALIZED || receiveLink.getRemoteState() == EndpointState.UNINITIALIZED) return IOObjectState.OPENING; - + if (sendLink.getRemoteState() == EndpointState.ACTIVE && receiveLink.getRemoteState() == EndpointState.ACTIVE && sendLink.getLocalState() == EndpointState.ACTIVE && receiveLink.getRemoteState() == EndpointState.ACTIVE) return IOObjectState.OPENED; - + if (sendLink.getRemoteState() == EndpointState.CLOSED && receiveLink.getRemoteState() == EndpointState.CLOSED) return IOObjectState.CLOSED; - + return IOObjectState.CLOSING; // only left cases are if some are active and some are closed } - + private class RequestHandler implements IAmqpSender { @Override @@ -200,32 +199,32 @@ public void onSendComplete(Delivery delivery) { @Override public void onOpenComplete(Exception completionException) { - + onLinkOpenComplete(completionException); } @Override public void onError(Exception exception) { - + onLinkCloseComplete(exception); } @Override public void onClose(ErrorCondition condition) { - - if (condition == null|| condition.getCondition() == null) + + if (condition == null || condition.getCondition() == null) onLinkCloseComplete(null); - else + else onError(new AmqpException(condition)); - } - + } + } - + private class ResponseHandler implements IAmqpReceiver { @Override public void onReceiveComplete(Delivery delivery) { - + final Message response = Proton.message(); final int msgSize = delivery.pending(); final byte[] buffer = new byte[msgSize]; @@ -234,37 +233,37 @@ public void onReceiveComplete(Delivery delivery) { response.decode(buffer, 0, read); delivery.settle(); - + final IOperationResult responseCallback = inflightRequests.remove(response.getCorrelationId()); if (responseCallback != null) - responseCallback.onComplete(response); + responseCallback.onComplete(response); } @Override public void onOpenComplete(Exception completionException) { - + onLinkOpenComplete(completionException); } @Override public void onError(Exception exception) { - - for (IOperationResult responseCallback: inflightRequests.values()) + + for (IOperationResult responseCallback : inflightRequests.values()) responseCallback.onError(exception); - + inflightRequests.clear(); - + if (onClose != null) onLinkCloseComplete(exception); } @Override public void onClose(ErrorCondition condition) { - + if (condition == null || condition.getCondition() == null) onLinkCloseComplete(null); else onError(new AmqpException(condition)); - } - } + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SendLinkHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SendLinkHandler.java index 536172b9c..f210dac0f 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SendLinkHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SendLinkHandler.java @@ -12,97 +12,79 @@ import org.apache.qpid.proton.engine.Link; import org.apache.qpid.proton.engine.Sender; -public class SendLinkHandler extends BaseLinkHandler -{ - private final IAmqpSender msgSender; - private final Object firstFlow; - private boolean isFirstFlow; +public class SendLinkHandler extends BaseLinkHandler { + private final IAmqpSender msgSender; + private final Object firstFlow; + private boolean isFirstFlow; - public SendLinkHandler(final IAmqpSender sender) - { - super(sender); + public SendLinkHandler(final IAmqpSender sender) { + super(sender); - this.msgSender = sender; - this.firstFlow = new Object(); - this.isFirstFlow = true; - } + this.msgSender = sender; + this.firstFlow = new Object(); + this.isFirstFlow = true; + } - @Override - public void onLinkRemoteOpen(Event event) - { - Link link = event.getLink(); - if (link != null && link instanceof Sender) - { - Sender sender = (Sender) link; - if (link.getRemoteTarget() != null) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "linkName[%s], remoteTarget[%s]", sender.getName(), link.getRemoteTarget())); - } + @Override + public void onLinkRemoteOpen(Event event) { + Link link = event.getLink(); + if (link != null && link instanceof Sender) { + Sender sender = (Sender) link; + if (link.getRemoteTarget() != null) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "linkName[%s], remoteTarget[%s]", sender.getName(), link.getRemoteTarget())); + } - synchronized (this.firstFlow) - { - this.isFirstFlow = false; - this.msgSender.onOpenComplete(null); - } - } - else - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, - String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", sender.getName())); - } - } - } - } + synchronized (this.firstFlow) { + this.isFirstFlow = false; + this.msgSender.onOpenComplete(null); + } + } else { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, + String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", sender.getName())); + } + } + } + } - @Override - public void onDelivery(Event event) - { - Delivery delivery = event.getDelivery(); - - while (delivery != null) - { - Sender sender = (Sender) delivery.getLink(); - - if(TRACE_LOGGER.isLoggable(Level.FINEST)) - { - TRACE_LOGGER.log(Level.FINEST, - "linkName[" + sender.getName() + - "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getRemoteCredit()+ "], deliveryState[" + delivery.getRemoteState() + - "], delivery.isBuffered[" + delivery.isBuffered() +"], delivery.id[" + new String(delivery.getTag()) + "]"); - } - - msgSender.onSendComplete(delivery); - delivery.settle(); - - delivery = sender.current(); - } - } + @Override + public void onDelivery(Event event) { + Delivery delivery = event.getDelivery(); - @Override - public void onLinkFlow(Event event) - { - if (this.isFirstFlow) - { - synchronized (this.firstFlow) - { - if (this.isFirstFlow) - { - this.msgSender.onOpenComplete(null); - this.isFirstFlow = false; - } - } - } + while (delivery != null) { + Sender sender = (Sender) delivery.getLink(); - Sender sender = event.getSender(); - this.msgSender.onFlow(sender.getRemoteCredit()); + if (TRACE_LOGGER.isLoggable(Level.FINEST)) { + TRACE_LOGGER.log(Level.FINEST, + "linkName[" + sender.getName() + + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getRemoteCredit() + "], deliveryState[" + delivery.getRemoteState() + + "], delivery.isBuffered[" + delivery.isBuffered() + "], delivery.id[" + new String(delivery.getTag()) + "]"); + } - if(TRACE_LOGGER.isLoggable(Level.FINEST)) - { - TRACE_LOGGER.log(Level.FINEST, "linkName[" + sender.getName() + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getCredit()+ "]"); - } - } + msgSender.onSendComplete(delivery); + delivery.settle(); + + delivery = sender.current(); + } + } + + @Override + public void onLinkFlow(Event event) { + if (this.isFirstFlow) { + synchronized (this.firstFlow) { + if (this.isFirstFlow) { + this.msgSender.onOpenComplete(null); + this.isFirstFlow = false; + } + } + } + + Sender sender = event.getSender(); + this.msgSender.onFlow(sender.getRemoteCredit()); + + if (TRACE_LOGGER.isLoggable(Level.FINEST)) { + TRACE_LOGGER.log(Level.FINEST, "linkName[" + sender.getName() + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getCredit() + "]"); + } + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java index 879862252..f164951fc 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/amqp/SessionHandler.java @@ -25,160 +25,147 @@ import com.microsoft.azure.servicebus.ClientConstants; import com.microsoft.azure.servicebus.ServiceBusException; -public class SessionHandler extends BaseHandler -{ - protected static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - private final String entityName; - private final Consumer onRemoteSessionOpen; - private final BiConsumer onRemoteSessionOpenError; - - private boolean sessionCreated = false; - private boolean sessionOpenErrorDispatched = false; - - public SessionHandler(final String entityName, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) - { - this.entityName = entityName; - this.onRemoteSessionOpenError = onRemoteSessionOpenError; - this.onRemoteSessionOpen = onRemoteSessionOpen; - } - - @Override - public void onSessionLocalOpen(Event e) - { - if (this.onRemoteSessionOpenError != null) { - - ReactorHandler reactorHandler = null; - final Reactor reactor = e.getReactor(); - final Iterator reactorEventHandlers = reactor.getHandler().children(); - while (reactorEventHandlers.hasNext()) { - final Handler currentHandler = reactorEventHandlers.next(); - if (currentHandler instanceof ReactorHandler) { - reactorHandler = (ReactorHandler) currentHandler; - break; - } +public class SessionHandler extends BaseHandler { + protected static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + + private final String entityName; + private final Consumer onRemoteSessionOpen; + private final BiConsumer onRemoteSessionOpenError; + + private boolean sessionCreated = false; + private boolean sessionOpenErrorDispatched = false; + + public SessionHandler(final String entityName, final Consumer onRemoteSessionOpen, final BiConsumer onRemoteSessionOpenError) { + this.entityName = entityName; + this.onRemoteSessionOpenError = onRemoteSessionOpenError; + this.onRemoteSessionOpen = onRemoteSessionOpen; + } + + @Override + public void onSessionLocalOpen(Event e) { + if (this.onRemoteSessionOpenError != null) { + + ReactorHandler reactorHandler = null; + final Reactor reactor = e.getReactor(); + final Iterator reactorEventHandlers = reactor.getHandler().children(); + while (reactorEventHandlers.hasNext()) { + final Handler currentHandler = reactorEventHandlers.next(); + if (currentHandler instanceof ReactorHandler) { + reactorHandler = (ReactorHandler) currentHandler; + break; } - - final ReactorDispatcher reactorDispatcher = reactorHandler.getReactorDispatcher(); - final Session session = e.getSession(); - - try { - - reactorDispatcher.invoke(ClientConstants.SESSION_OPEN_TIMEOUT_IN_MS, new SessionTimeoutHandler(session)); - } catch (IOException ignore) { - - if(TRACE_LOGGER.isLoggable(Level.SEVERE)) { - TRACE_LOGGER.log(Level.SEVERE, String.format(Locale.US, "entityName[%s], reactorDispatcherError[%s]", this.entityName, ignore.getMessage())); - } - - session.close(); - this.onRemoteSessionOpenError.accept( - null, - new ServiceBusException( - false, - String.format("underlying IO of reactorDispatcher faulted with error: %s", ignore.getMessage()), - ignore)); + } + + final ReactorDispatcher reactorDispatcher = reactorHandler.getReactorDispatcher(); + final Session session = e.getSession(); + + try { + + reactorDispatcher.invoke(ClientConstants.SESSION_OPEN_TIMEOUT_IN_MS, new SessionTimeoutHandler(session)); + } catch (IOException ignore) { + + if (TRACE_LOGGER.isLoggable(Level.SEVERE)) { + TRACE_LOGGER.log(Level.SEVERE, String.format(Locale.US, "entityName[%s], reactorDispatcherError[%s]", this.entityName, ignore.getMessage())); } + + session.close(); + this.onRemoteSessionOpenError.accept( + null, + new ServiceBusException( + false, + String.format("underlying IO of reactorDispatcher faulted with error: %s", ignore.getMessage()), + ignore)); } } + } - @Override - public void onSessionRemoteOpen(Event e) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], sessionIncCapacity[%s], sessionOutgoingWindow[%s]", - this.entityName, e.getSession().getIncomingCapacity(), e.getSession().getOutgoingWindow())); - } - - final Session session = e.getSession(); - if (session != null && session.getLocalState() == EndpointState.UNINITIALIZED) - { - session.open(); - } - - sessionCreated = true; - if (this.onRemoteSessionOpen != null) - this.onRemoteSessionOpen.accept(session); - } - - - @Override - public void onSessionLocalClose(Event e) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], condition[%s]", this.entityName, - e.getSession().getCondition() == null ? "none" : e.getSession().getCondition().toString())); - } - } - - @Override - public void onSessionRemoteClose(Event e) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], condition[%s]", this.entityName, - e.getSession().getRemoteCondition() == null ? "none" : e.getSession().getRemoteCondition().toString())); - } - - final Session session = e.getSession(); - if (session != null && session.getLocalState() != EndpointState.CLOSED) - { - session.close(); - } - - this.sessionOpenErrorDispatched = true; - if (!sessionCreated && this.onRemoteSessionOpenError != null) - this.onRemoteSessionOpenError.accept(session.getRemoteCondition(), null); - } - - @Override - public void onSessionFinal(Event e) - { - if(TRACE_LOGGER.isLoggable(Level.FINE)) - { - TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s]", this.entityName)); - } - } - - private class SessionTimeoutHandler extends DispatchHandler { - - private final Session session; - - public SessionTimeoutHandler(final Session session) { - this.session = session; - } - - @Override - public void onEvent() { - - // notify - if connection or transport error'ed out before even session open completed - if (!sessionCreated && !sessionOpenErrorDispatched) { - - final Connection connection = session.getConnection(); - - if (connection != null) { - - if (connection.getRemoteCondition() != null && connection.getRemoteCondition().getCondition() != null) { - - session.close(); - onRemoteSessionOpenError.accept(connection.getRemoteCondition(), null); - return; - } - - final Transport transport = connection.getTransport(); - if (transport != null && transport.getCondition() != null && transport.getCondition().getCondition() != null) { - - session.close(); - onRemoteSessionOpenError.accept(transport.getCondition(), null); - return; - } + @Override + public void onSessionRemoteOpen(Event e) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], sessionIncCapacity[%s], sessionOutgoingWindow[%s]", + this.entityName, e.getSession().getIncomingCapacity(), e.getSession().getOutgoingWindow())); + } + + final Session session = e.getSession(); + if (session != null && session.getLocalState() == EndpointState.UNINITIALIZED) { + session.open(); + } + + sessionCreated = true; + if (this.onRemoteSessionOpen != null) + this.onRemoteSessionOpen.accept(session); + } + + + @Override + public void onSessionLocalClose(Event e) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], condition[%s]", this.entityName, + e.getSession().getCondition() == null ? "none" : e.getSession().getCondition().toString())); + } + } + + @Override + public void onSessionRemoteClose(Event e) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s], condition[%s]", this.entityName, + e.getSession().getRemoteCondition() == null ? "none" : e.getSession().getRemoteCondition().toString())); + } + + final Session session = e.getSession(); + if (session != null && session.getLocalState() != EndpointState.CLOSED) { + session.close(); + } + + this.sessionOpenErrorDispatched = true; + if (!sessionCreated && this.onRemoteSessionOpenError != null) + this.onRemoteSessionOpenError.accept(session.getRemoteCondition(), null); + } + + @Override + public void onSessionFinal(Event e) { + if (TRACE_LOGGER.isLoggable(Level.FINE)) { + TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "entityName[%s]", this.entityName)); + } + } + + private class SessionTimeoutHandler extends DispatchHandler { + + private final Session session; + + public SessionTimeoutHandler(final Session session) { + this.session = session; + } + + @Override + public void onEvent() { + + // notify - if connection or transport error'ed out before even session open completed + if (!sessionCreated && !sessionOpenErrorDispatched) { + + final Connection connection = session.getConnection(); + + if (connection != null) { + + if (connection.getRemoteCondition() != null && connection.getRemoteCondition().getCondition() != null) { + + session.close(); + onRemoteSessionOpenError.accept(connection.getRemoteCondition(), null); + return; + } + + final Transport transport = connection.getTransport(); + if (transport != null && transport.getCondition() != null && transport.getCondition().getCondition() != null) { + + session.close(); + onRemoteSessionOpenError.accept(transport.getCondition(), null); + return; } - - session.close(); - onRemoteSessionOpenError.accept(null, new ServiceBusException(false, "session creation timedout.")); } + + session.close(); + onRemoteSessionOpenError.accept(null, new ServiceBusException(false, "session creation timedout.")); } } + } } From 2807eedb1c72492ef307c46895ea6c312a6ed961 Mon Sep 17 00:00:00 2001 From: Sreeram Garlapati Date: Thu, 6 Apr 2017 14:50:21 -0700 Subject: [PATCH 4/4] Fix timeout issue in MsgFactory & thread-safety issue in MsgSender/Receiver (#93) * fix connection open/close timeout paths * use non ForkJoinPool variants while composing completable futures (https://github.com/Azure/azure-event-hubs-java/issues/4) * fix thread-safety in MessageSender & MessageReceiver in error cases(https://github.com/Azure/azure-event-hubs-java/issues/4) --- .../azure/eventhubs/EventHubClient.java | 14 +-- .../azure/eventhubs/PartitionReceiver.java | 4 +- .../azure/eventhubs/PartitionSender.java | 4 +- .../azure/servicebus/CBSChannel.java | 30 +++-- .../azure/servicebus/ClientEntity.java | 8 +- .../azure/servicebus/MessageReceiver.java | 80 ++++++++----- .../azure/servicebus/MessageSender.java | 111 +++++++++++++----- .../azure/servicebus/MessagingFactory.java | 63 ++++++---- 8 files changed, 207 insertions(+), 107 deletions(-) diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java index f75770ea8..85ef8a5d8 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java @@ -132,7 +132,7 @@ public static CompletableFuture createFromConnectionString(final final EventHubClient eventHubClient = new EventHubClient(connStr); return MessagingFactory.createFromConnectionString(connectionString.toString(), retryPolicy) - .thenApplyAsync(new Function() { + .thenApply(new Function() { @Override public EventHubClient apply(MessagingFactory factory) { eventHubClient.underlyingFactory = factory; @@ -205,7 +205,7 @@ public final CompletableFuture send(final EventData data) { throw new IllegalArgumentException("EventData cannot be empty."); } - return this.createInternalSender().thenComposeAsync(new Function>() { + return this.createInternalSender().thenCompose(new Function>() { @Override public CompletableFuture apply(Void voidArg) { return EventHubClient.this.sender.send(data.toAmqpMessage()); @@ -295,7 +295,7 @@ public final CompletableFuture send(final Iterable eventDatas) throw new IllegalArgumentException("Empty batch of EventData cannot be sent."); } - return this.createInternalSender().thenComposeAsync(new Function>() { + return this.createInternalSender().thenCompose(new Function>() { @Override public CompletableFuture apply(Void voidArg) { return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas)); @@ -371,7 +371,7 @@ public final CompletableFuture send(final EventData eventData, final Strin throw new IllegalArgumentException("partitionKey cannot be null"); } - return this.createInternalSender().thenComposeAsync(new Function>() { + return this.createInternalSender().thenCompose(new Function>() { @Override public CompletableFuture apply(Void voidArg) { return EventHubClient.this.sender.send(eventData.toAmqpMessage(partitionKey)); @@ -445,7 +445,7 @@ public final CompletableFuture send(final Iterable eventDatas, String.format(Locale.US, "PartitionKey exceeds the maximum allowed length of partitionKey: {0}", ClientConstants.MAX_PARTITION_KEY_LENGTH)); } - return this.createInternalSender().thenComposeAsync(new Function>() { + return this.createInternalSender().thenCompose(new Function>() { @Override public CompletableFuture apply(Void voidArg) { return EventHubClient.this.sender.send(EventDataUtil.toAmqpMessages(eventDatas, partitionKey)); @@ -1222,7 +1222,7 @@ public CompletableFuture onClose() { if (this.underlyingFactory != null) { synchronized (this.senderCreateSync) { final CompletableFuture internalSenderClose = this.sender != null - ? this.sender.close().thenComposeAsync(new Function>() { + ? this.sender.close().thenCompose(new Function>() { @Override public CompletableFuture apply(Void voidArg) { return EventHubClient.this.underlyingFactory.close(); @@ -1242,7 +1242,7 @@ private CompletableFuture createInternalSender() { synchronized (this.senderCreateSync) { if (!this.isSenderCreateStarted) { this.createSender = MessageSender.create(this.underlyingFactory, StringUtil.getRandomString(), this.eventHubName) - .thenAcceptAsync(new Consumer() { + .thenAccept(new Consumer() { public void accept(MessageSender a) { EventHubClient.this.sender = a; } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java index 420d6ecc0..c36826ec4 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionReceiver.java @@ -124,7 +124,7 @@ static CompletableFuture create(MessagingFactory factory, } final PartitionReceiver receiver = new PartitionReceiver(factory, eventHubName, consumerGroupName, partitionId, startingOffset, offsetInclusive, dateTime, epoch, isEpochReceiver, receiverOptions); - return receiver.createInternalReceiver().thenApplyAsync(new Function() { + return receiver.createInternalReceiver().thenApply(new Function() { public PartitionReceiver apply(Void a) { return receiver; } @@ -136,7 +136,7 @@ private CompletableFuture createInternalReceiver() throws ServiceBusExcept StringUtil.getRandomString(), String.format("%s/ConsumerGroups/%s/Partitions/%s", this.eventHubName, this.consumerGroupName, this.partitionId), PartitionReceiver.DEFAULT_PREFETCH_COUNT, this) - .thenAcceptAsync(new Consumer() { + .thenAccept(new Consumer() { public void accept(MessageReceiver r) { PartitionReceiver.this.internalReceiver = r; } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java index 7abd5ece3..5159c3c4e 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java @@ -37,7 +37,7 @@ private PartitionSender(MessagingFactory factory, String eventHubName, String pa static CompletableFuture Create(MessagingFactory factory, String eventHubName, String partitionId) throws ServiceBusException { final PartitionSender sender = new PartitionSender(factory, eventHubName, partitionId); return sender.createInternalSender() - .thenApplyAsync(new Function() { + .thenApply(new Function() { public PartitionSender apply(Void a) { return sender; } @@ -47,7 +47,7 @@ public PartitionSender apply(Void a) { private CompletableFuture createInternalSender() throws ServiceBusException { return MessageSender.create(this.factory, StringUtil.getRandomString(), String.format("%s/Partitions/%s", this.eventHubName, this.partitionId)) - .thenAcceptAsync(new Consumer() { + .thenAccept(new Consumer() { public void accept(MessageSender a) { PartitionSender.this.internalSender = a; } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java index dc7f23796..ed748a76d 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/CBSChannel.java @@ -10,6 +10,7 @@ import org.apache.qpid.proton.Proton; import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; +import org.apache.qpid.proton.engine.Session; import org.apache.qpid.proton.message.Message; import org.apache.qpid.proton.amqp.messaging.AmqpValue; import org.apache.qpid.proton.amqp.transport.ErrorCondition; @@ -100,21 +101,26 @@ private class OpenRequestResponseChannel implements IOperation operationCallback) { + final Session session = CBSChannel.this.sessionProvider.getSession( + "cbs-session", + null, + new BiConsumer() { + @Override + public void accept(ErrorCondition error, Exception exception) { + if (error != null) + operationCallback.onError(new AmqpException(error)); + else if (exception != null) + operationCallback.onError(exception); + } + }); + + if (session == null) + return; + final RequestResponseChannel requestResponseChannel = new RequestResponseChannel( "cbs", ClientConstants.CBS_ADDRESS, - CBSChannel.this.sessionProvider.getSession( - "cbs-session", - null, - new BiConsumer() { - @Override - public void accept(ErrorCondition error, Exception exception) { - if (error != null) - operationCallback.onError(new AmqpException(error)); - else if (exception != null) - operationCallback.onError(exception); - } - })); + session); requestResponseChannel.open( new IOperationResult() { diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java index 2dfdac07d..6c193f96e 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/ClientEntity.java @@ -102,9 +102,13 @@ public final void closeSync() throws ServiceBusException { } } - protected final void throwIfClosed(Throwable cause) { + protected final void throwIfClosed() { if (this.getIsClosingOrClosed()) { - throw new IllegalStateException(String.format(Locale.US, "Operation not allowed after the %s instance is Closed.", this.getClass().getName()), cause); + throw new IllegalStateException(String.format(Locale.US, "Operation not allowed after the %s instance is Closed.", this.getClass().getName()), this.getLastKnownError()); } } + + protected Exception getLastKnownError() { + return null; + } } diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java index 7500d4d05..20c4a40c2 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java @@ -64,6 +64,7 @@ public final class MessageReceiver extends ClientEntity implements IAmqpReceiver private final ConcurrentLinkedQueue prefetchedMessages; private final ReceiveWork receiveWork; private final CreateAndReceive createAndReceive; + private final Object errorConditionLock; private int prefetchCount; private Receiver receiveLink; @@ -95,6 +96,7 @@ private MessageReceiver(final MessagingFactory factory, this.linkOpen = new WorkItem<>(new CompletableFuture<>(), factory.getOperationTimeout()); this.pendingReceives = new ConcurrentLinkedQueue<>(); + this.errorConditionLock = new Object(); // onOperationTimeout delegate - per receive call this.onOperationTimedout = new Runnable() { @@ -250,7 +252,7 @@ public void setReceiveTimeout(final Duration value) { } public CompletableFuture> receive(final int maxMessageCount) { - this.throwIfClosed(this.lastKnownLinkError); + this.throwIfClosed(); if (maxMessageCount <= 0 || maxMessageCount > this.prefetchCount) { throw new IllegalArgumentException(String.format(Locale.US, "parameter 'maxMessageCount' should be a positive number and should be less than prefetchCount(%s)", this.prefetchCount)); @@ -289,7 +291,9 @@ public void onOpenComplete(Exception exception) { this.openTimer.cancel(false); } - this.lastKnownLinkError = null; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = null; + } this.underlyingFactory.getRetryPolicy().resetRetryCount(this.underlyingFactory.getClientId()); @@ -308,7 +312,9 @@ public void onOpenComplete(Exception exception) { this.openTimer.cancel(false); } - this.lastKnownLinkError = exception; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = exception; + } } } @@ -330,11 +336,6 @@ public void onReceiveComplete(Delivery delivery) { this.receiveWork.onEvent(); } - public void onError(final ErrorCondition error) { - final Exception completionException = ExceptionUtil.toException(error); - this.onError(completionException); - } - @Override public void onError(final Exception exception) { this.prefetchedMessages.clear(); @@ -358,7 +359,9 @@ public void onError(final Exception exception) { this.linkClose.complete(null); } else { - this.lastKnownLinkError = exception == null ? this.lastKnownLinkError : exception; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = exception == null ? this.lastKnownLinkError : exception; + } final Exception completionException = exception == null ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : exception; @@ -452,7 +455,9 @@ public void accept(Session session) { receiver.open(); - MessageReceiver.this.receiveLink = receiver; + synchronized (MessageReceiver.this.errorConditionLock) { + MessageReceiver.this.receiveLink = receiver; + } } }; @@ -460,7 +465,7 @@ public void accept(Session session) { @Override public void accept(ErrorCondition t, Exception u) { if (t != null) - onError(t); + onError((t != null && t.getCondition() != null) ? ExceptionUtil.toException(t) : null); else if (u != null) onError(u); } @@ -526,12 +531,19 @@ private void scheduleLinkOpenTimeout(final TimeoutTracker timeout) { new Runnable() { public void run() { if (!linkOpen.getWork().isDone()) { - Exception operationTimedout = new TimeoutException( - String.format(Locale.US, "%s operation on ReceiveLink(%s) to path(%s) timed out at %s.", "Open", MessageReceiver.this.receiveLink.getName(), MessageReceiver.this.receivePath, ZonedDateTime.now()), - MessageReceiver.this.lastKnownLinkError); + final Receiver link; + final Exception lastReportedLinkError; + synchronized (errorConditionLock) { + link = MessageReceiver.this.receiveLink; + lastReportedLinkError = MessageReceiver.this.lastKnownLinkError; + } + + final Exception operationTimedout = new TimeoutException( + String.format(Locale.US, "%s operation on ReceiveLink(%s) to path(%s) timed out at %s.", "Open", link.getName(), MessageReceiver.this.receivePath, ZonedDateTime.now()), + lastReportedLinkError); if (TRACE_LOGGER.isLoggable(Level.WARNING)) { TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Open"), + String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, link.getName(), "Open"), operationTimedout); } @@ -549,10 +561,15 @@ private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { new Runnable() { public void run() { if (!linkClose.isDone()) { - Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageReceiver.this.receiveLink.getName(), ZonedDateTime.now())); + final Receiver link; + synchronized (errorConditionLock) { + link = MessageReceiver.this.receiveLink; + } + + final Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", link.getName(), ZonedDateTime.now())); if (TRACE_LOGGER.isLoggable(Level.WARNING)) { TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, MessageReceiver.this.receiveLink.getName(), "Close"), + String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, link.getName(), "Close"), operationTimedout); } @@ -567,25 +584,27 @@ public void run() { @Override public void onClose(ErrorCondition condition) { - if (condition == null || condition.getCondition() == null) { - this.onError((Exception) null); - } else { - this.onError(condition); - } + final Exception completionException = (condition != null && condition.getCondition() != null) ? ExceptionUtil.toException(condition) : null; + this.onError(completionException); } @Override public ErrorContext getContext() { + final Receiver link; + synchronized (this.errorConditionLock) { + link = this.receiveLink; + } + final boolean isLinkOpened = this.linkOpen != null && this.linkOpen.getWork().isDone(); - final String referenceId = this.receiveLink != null && this.receiveLink.getRemoteProperties() != null && this.receiveLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) - ? this.receiveLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() - : ((this.receiveLink != null) ? this.receiveLink.getName() : null); + final String referenceId = link != null && link.getRemoteProperties() != null && link.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) + ? link.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() + : ((link != null) ? link.getName() : null); - ReceiverContext errorContext = new ReceiverContext(this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, + final ReceiverContext errorContext = new ReceiverContext(this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, this.receivePath, referenceId, isLinkOpened ? this.prefetchCount : null, - isLinkOpened && this.receiveLink != null ? this.receiveLink.getCredit() : null, + isLinkOpened && link != null ? link.getCredit() : null, isLinkOpened && this.prefetchedMessages != null ? this.prefetchedMessages.size() : null); return errorContext; @@ -628,6 +647,13 @@ public void onEvent() { return this.linkClose; } + @Override + protected Exception getLastKnownError() { + synchronized (this.errorConditionLock) { + return this.lastKnownLinkError; + } + } + private final class ReceiveWork extends DispatchHandler { @Override diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java index a28778351..40ad0f45a 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessageSender.java @@ -72,6 +72,7 @@ public class MessageSender extends ClientEntity implements IAmqpSender, IErrorCo private final DispatchHandler sendWork; private final ActiveClientTokenManager activeClientTokenManager; private final String tokenAudience; + private final Object errorConditionLock; private Sender sendLink; private CompletableFuture linkFirstOpen; @@ -117,6 +118,8 @@ private MessageSender(final MessagingFactory factory, final String sendLinkName, this.retryPolicy = factory.getRetryPolicy(); + this.errorConditionLock = new Object(); + this.pendingSendLock = new Object(); this.pendingSendsData = new ConcurrentHashMap<>(); this.pendingSends = new PriorityQueue<>(1000, new DeliveryTagComparator()); @@ -189,7 +192,7 @@ private CompletableFuture sendCore( final TimeoutTracker tracker, final Exception lastKnownError, final ScheduledFuture timeoutTask) { - this.throwIfClosed(this.lastKnownLinkError); + this.throwIfClosed(); final boolean isRetrySend = (onSend != null); @@ -312,7 +315,10 @@ public void onOpenComplete(Exception completionException) { this.openLinkTracker = null; - this.lastKnownLinkError = null; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = null; + } + this.retryPolicy.resetRetryCount(this.getClientId()); if (!this.linkFirstOpen.isDone()) { @@ -381,8 +387,10 @@ public void onError(final Exception completionException) { return; } else { - this.lastKnownLinkError = completionException == null ? this.lastKnownLinkError : completionException; - this.lastKnownErrorReportedAt = Instant.now(); + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = completionException == null ? this.lastKnownLinkError : completionException; + this.lastKnownErrorReportedAt = Instant.now(); + } final Exception finalCompletionException = completionException == null ? new ServiceBusException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; @@ -440,7 +448,10 @@ public void onSendComplete(final Delivery delivery) { if (pendingSendWorkItem != null) { if (outcome instanceof Accepted) { - this.lastKnownLinkError = null; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = null; + } + this.retryPolicy.resetRetryCount(this.getClientId()); pendingSendWorkItem.getTimeoutTask().cancel(false); @@ -452,8 +463,10 @@ public void onSendComplete(final Delivery delivery) { final Exception exception = ExceptionUtil.toException(error); if (ExceptionUtil.isGeneralSendError(error.getCondition())) { - this.lastKnownLinkError = exception; - this.lastKnownErrorReportedAt = Instant.now(); + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = exception; + this.lastKnownErrorReportedAt = Instant.now(); + } } final Duration retryInterval = this.retryPolicy.getNextRetryInterval( @@ -534,7 +547,9 @@ public void accept(Session session) { MessageSender.this.underlyingFactory.registerForConnectionError(sender); sender.open(); - MessageSender.this.sendLink = sender; + synchronized (MessageSender.this.errorConditionLock) { + MessageSender.this.sendLink = sender; + } } }; @@ -542,7 +557,7 @@ public void accept(Session session) { @Override public void accept(ErrorCondition t, Exception u) { if (t != null) - MessageSender.this.onClose(t); + MessageSender.this.onError((t != null && t.getCondition() != null) ? ExceptionUtil.toException(t) : null); else if (u != null) MessageSender.this.onError(u); } @@ -584,9 +599,18 @@ private void initializeLinkOpen(TimeoutTracker timeout) { new Runnable() { public void run() { if (!MessageSender.this.linkFirstOpen.isDone()) { - Exception operationTimedout = new TimeoutException( - String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", MessageSender.this.sendLink.getName(), MessageSender.this.getSendPath(), ZonedDateTime.now().toString()), - MessageSender.this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? MessageSender.this.lastKnownLinkError : null); + final Exception lastReportedError; + final Instant lastErrorReportedAt; + final Sender link; + synchronized (MessageSender.this.errorConditionLock) { + lastReportedError = MessageSender.this.lastKnownLinkError; + lastErrorReportedAt = MessageSender.this.lastKnownErrorReportedAt; + link = MessageSender.this.sendLink; + } + + final Exception operationTimedout = new TimeoutException( + String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", link.getName(), MessageSender.this.getSendPath(), ZonedDateTime.now().toString()), + lastErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? lastReportedError : null); if (TRACE_LOGGER.isLoggable(Level.WARNING)) { TRACE_LOGGER.log(Level.WARNING, @@ -604,22 +628,29 @@ public void run() { @Override public ErrorContext getContext() { + final Sender link; + synchronized (this.errorConditionLock) { + link = this.sendLink; + } + final boolean isLinkOpened = this.linkFirstOpen != null && this.linkFirstOpen.isDone(); - final String referenceId = this.sendLink != null && this.sendLink.getRemoteProperties() != null && this.sendLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) - ? this.sendLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() - : ((this.sendLink != null) ? this.sendLink.getName() : null); + final String referenceId = link != null && link.getRemoteProperties() != null && link.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY) + ? link.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString() + : ((link != null) ? link.getName() : null); - SenderContext errorContext = new SenderContext( + final SenderContext errorContext = new SenderContext( this.underlyingFactory != null ? this.underlyingFactory.getHostName() : null, this.sendPath, referenceId, - isLinkOpened && this.sendLink != null ? this.sendLink.getCredit() : null); + isLinkOpened && link != null ? link.getCredit() : null); return errorContext; } @Override public void onFlow(final int creditIssued) { - this.lastKnownLinkError = null; + synchronized (this.errorConditionLock) { + this.lastKnownLinkError = null; + } if (creditIssued <= 0) return; @@ -722,18 +753,28 @@ private void processSendWork() { } } - private void throwSenderTimeout(CompletableFuture pendingSendWork, Exception lastKnownException) { + private void throwSenderTimeout(final CompletableFuture pendingSendWork, final Exception lastKnownException) { + Exception cause = lastKnownException; - if (lastKnownException == null && this.lastKnownLinkError != null) { - boolean isServerBusy = ((this.lastKnownLinkError instanceof ServerBusyException) - && (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)))); - cause = isServerBusy || (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusMillis(this.operationTimeout.toMillis()))) - ? this.lastKnownLinkError - : null; + if (lastKnownException == null) { + final Exception lastReportedLinkLevelError; + final Instant lastLinkErrorReportedAt; + synchronized (this.errorConditionLock) { + lastReportedLinkLevelError = this.lastKnownLinkError; + lastLinkErrorReportedAt = this.lastKnownErrorReportedAt; + } + + if (lastReportedLinkLevelError != null) { + boolean isServerBusy = ((lastReportedLinkLevelError instanceof ServerBusyException) + && (lastLinkErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)))); + cause = isServerBusy || (lastLinkErrorReportedAt.isAfter(Instant.now().minusMillis(this.operationTimeout.toMillis()))) + ? lastReportedLinkLevelError + : null; + } } - boolean isClientSideTimeout = (cause == null || !(cause instanceof ServiceBusException)); - ServiceBusException exception = isClientSideTimeout + final boolean isClientSideTimeout = (cause == null || !(cause instanceof ServiceBusException)); + final ServiceBusException exception = isClientSideTimeout ? new TimeoutException(String.format(Locale.US, "%s %s %s.", MessageSender.SEND_TIMED_OUT, " at ", ZonedDateTime.now(), cause)) : (ServiceBusException) cause; @@ -746,10 +787,15 @@ private void scheduleLinkCloseTimeout(final TimeoutTracker timeout) { new Runnable() { public void run() { if (!linkClose.isDone()) { - Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageSender.this.sendLink.getName(), ZonedDateTime.now())); + final Sender link; + synchronized (MessageSender.this.errorConditionLock) { + link = MessageSender.this.sendLink; + } + + final Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", link.getName(), ZonedDateTime.now())); if (TRACE_LOGGER.isLoggable(Level.WARNING)) { TRACE_LOGGER.log(Level.WARNING, - String.format(Locale.US, "message recever(linkName: %s, path: %s) %s call timedout", MessageSender.this.sendLink.getName(), MessageSender.this.sendPath, "Close"), + String.format(Locale.US, "message recever(linkName: %s, path: %s) %s call timedout", link.getName(), MessageSender.this.sendPath, "Close"), operationTimedout); } @@ -790,6 +836,13 @@ public void onEvent() { return this.linkClose; } + @Override + protected Exception getLastKnownError() { + synchronized (this.errorConditionLock) { + return this.lastKnownLinkError; + } + } + private static class WeightedDeliveryTag { private final String deliveryTag; private final int priority; diff --git a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java index a4d1d7b43..d4b7c4c5a 100644 --- a/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java +++ b/azure-eventhubs/src/main/java/com/microsoft/azure/servicebus/MessagingFactory.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; @@ -61,11 +62,9 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I private Duration operationTimeout; private RetryPolicy retryPolicy; private CompletableFuture open; - private CompletableFuture openConnection; + private ScheduledFuture openTimer; + private ScheduledFuture closeTimer; - /** - * @param reactor parameter reactor is purely for testing purposes and the SDK code should always set it to null - */ MessagingFactory(final ConnectionStringBuilder builder, final RetryPolicy retryPolicy) { super("MessagingFactory".concat(StringUtil.getRandomString()), null); @@ -77,7 +76,6 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I this.registeredLinks = new LinkedList<>(); this.reactorLock = new Object(); this.connectionHandler = new ConnectionHandler(this); - this.openConnection = new CompletableFuture<>(); this.cbsChannelCreateLock = new Object(); this.tokenProvider = builder.getSharedAccessSignature() == null ? new SharedAccessSignatureTokenProvider(builder.getSasKeyName(), builder.getSasKey()) @@ -152,6 +150,7 @@ public Session getSession(final String path, final Consumer onRemoteSes if (this.getIsClosingOrClosed()) { onRemoteSessionOpenError.accept(null, new OperationCancelledException("underlying messagingFactory instance is closed")); + return null; } if (this.connection == null || this.connection.getLocalState() == EndpointState.CLOSED || this.connection.getRemoteState() == EndpointState.CLOSED) { @@ -180,7 +179,17 @@ public static CompletableFuture createFromConnectionString(fin public static CompletableFuture createFromConnectionString(final String connectionString, final RetryPolicy retryPolicy) throws IOException { final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString); final MessagingFactory messagingFactory = new MessagingFactory(builder, (retryPolicy != null) ? retryPolicy : RetryPolicy.getDefault()); - + messagingFactory.openTimer = Timer.schedule(new Runnable() { + @Override + public void run() { + if (!messagingFactory.open.isDone()) { + messagingFactory.open.completeExceptionally(new TimeoutException("Opening MessagingFactory timed out.")); + messagingFactory.getReactor().stop(); + } + } + }, + messagingFactory.getOperationTimeout(), + TimerType.OneTimeRun); messagingFactory.createConnection(builder); return messagingFactory.open; } @@ -189,13 +198,16 @@ public static CompletableFuture createFromConnectionString(fin public void onOpenComplete(Exception exception) { if (exception == null) { this.open.complete(this); - this.openConnection.complete(this.connection); + + // if connection creation is in progress and then msgFactory.close call came thru if (this.getIsClosingOrClosed()) this.connection.close(); } else { this.open.completeExceptionally(exception); - this.openConnection.completeExceptionally(exception); } + + if (this.openTimer != null) + this.openTimer.cancel(false); } @Override @@ -204,21 +216,19 @@ public void onConnectionError(ErrorCondition error) { this.onOpenComplete(ExceptionUtil.toException(error)); } else { final Connection currentConnection = this.connection; - for (Link link : this.registeredLinks) { + final List registeredLinksCopy = new LinkedList<>(this.registeredLinks); + for (Link link : registeredLinksCopy) { if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) { link.close(); } } - this.openConnection = new CompletableFuture<>(); - + // if proton-j detects transport error - onConnectionError is invoked, but, the connection state is not set to closed + // in connection recreation we depend on currentConnection state to evaluate need for recreation if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) { currentConnection.close(); } - // Clone of the registeredLinks is needed here - // onClose of link will lead to un-register - which will result into iteratorCollectionModified error - final List registeredLinksCopy = new LinkedList<>(this.registeredLinks); for (Link link : registeredLinksCopy) { final Handler handler = BaseHandler.getHandler(link); if (handler != null && handler instanceof BaseLinkHandler) { @@ -273,6 +283,16 @@ private void onReactorError(Exception cause) { protected CompletableFuture onClose() { if (!this.getIsClosed()) { try { + this.closeTimer = Timer.schedule(new Runnable() { + @Override + public void run() { + if (!closeTask.isDone()) { + closeTask.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); + getReactor().stop(); + } + } + }, + operationTimeout, TimerType.OneTimeRun); this.scheduleOnReactorThread(new CloseWork()); } catch (IOException ioException) { this.closeTask.completeExceptionally(new ServiceBusException(false, "Failed to Close MessagingFactory, see cause for more details.", ioException)); @@ -320,18 +340,6 @@ public void onError(Exception error) { connection.close(); } } - - if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { - Timer.schedule(new Runnable() { - @Override - public void run() { - if (!closeTask.isDone()) { - closeTask.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); - } - } - }, - operationTimeout, TimerType.OneTimeRun); - } } } @@ -387,6 +395,9 @@ public void run() { if (getIsClosingOrClosed() && !closeTask.isDone()) { closeTask.complete(null); + + if (closeTimer != null) + closeTimer.cancel(false); } } }