From eb2dda23e328555ee5fc5f412467304328354dc0 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Tue, 4 Apr 2017 15:52:23 -0700 Subject: [PATCH 01/17] Fixing JavaDoc generation errors. --- .../microsoft/azure/servicebus/SessionHandlerOptions.java | 2 +- .../servicebus/primitives/AuthorizationFailedException.java | 5 ++--- .../azure/servicebus/primitives/ConnectionStringBuilder.java | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SessionHandlerOptions.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SessionHandlerOptions.java index 62958ebe6129..845d4f806776 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SessionHandlerOptions.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SessionHandlerOptions.java @@ -20,7 +20,7 @@ public SessionHandlerOptions() /** * - * @param maxConcurrentCalls + * @param maxConcurrentSessions * @param autoComplete * @param maxAutoRenewDuration - Maximum duration within which the client keeps renewing the session lock if the processing of the session messages or onclose action * is not completed by the handler. diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/AuthorizationFailedException.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/AuthorizationFailedException.java index 5b69fe2ea24b..ce30f039fbc6 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/AuthorizationFailedException.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/AuthorizationFailedException.java @@ -6,9 +6,8 @@ /** * 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 EventHubClient#createFromConnectionStringAsync(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). + * When encountered this exception user should check whether the token/key provided in the connection 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 diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java index f384ec4ec4f7..96cb5f650287 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java @@ -106,7 +106,7 @@ private ConnectionStringBuilder( } /** - * Build a connection string consumable by {@link EventHubClient#createFromConnectionStringAsync(String)} + * Build a connection 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 @@ -123,7 +123,7 @@ public ConnectionStringBuilder( /** - * Build a connection string consumable by {@link EventHubClient#createFromConnectionStringAsync(String)} + * Build a connection 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 From 1d6b481dec075b3ed8fe2b0a10cda51a6f4b2272 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Wed, 5 Apr 2017 18:41:03 -0700 Subject: [PATCH 02/17] Performing close operations on reactor thread --- .../primitives/CoreMessageReceiver.java | 19 ++++++-- .../primitives/CoreMessageSender.java | 19 ++++++-- .../primitives/MessagingFactory.java | 19 ++++++-- .../primitives/RequestResponseLink.java | 44 +++++++++++++++---- 4 files changed, 82 insertions(+), 19 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java index 6a4b11b02b08..016781ad4a40 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java @@ -827,9 +827,22 @@ protected CompletableFuture onClose() { if (this.receiveLink != null && this.receiveLink.getLocalState() != EndpointState.CLOSED) { - this.receiveLink.close(); - this.underlyingFactory.deregisterForConnectionError(this.receiveLink); - this.scheduleLinkCloseTimeout(TimeoutTracker.create(this.operationTimeout)); + try { + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + + @Override + public void onEvent() { + if (CoreMessageReceiver.this.receiveLink != null && CoreMessageReceiver.this.receiveLink.getLocalState() != EndpointState.CLOSED) + { + CoreMessageReceiver.this.receiveLink.close(); + CoreMessageReceiver.this.underlyingFactory.deregisterForConnectionError(CoreMessageReceiver.this.receiveLink); + CoreMessageReceiver.this.scheduleLinkCloseTimeout(TimeoutTracker.create(CoreMessageReceiver.this.operationTimeout)); + } + } + }); + } catch (IOException e) { + AsyncUtil.completeFutureExceptionally(this.linkClose, e); + } } else { diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java index 6f26bd7fc4d8..64a4fadf2849 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java @@ -825,9 +825,22 @@ protected CompletableFuture onClose() { if (this.sendLink != null && this.sendLink.getLocalState() != EndpointState.CLOSED) { - this.underlyingFactory.deregisterForConnectionError(sendLink); - this.sendLink.close(); - this.scheduleLinkCloseTimeout(TimeoutTracker.create(this.operationTimeout)); + try { + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + + @Override + public void onEvent() { + if (CoreMessageSender.this.sendLink != null && CoreMessageSender.this.sendLink.getLocalState() != EndpointState.CLOSED) + { + CoreMessageSender.this.underlyingFactory.deregisterForConnectionError(CoreMessageSender.this.sendLink); + CoreMessageSender.this.sendLink.close(); + CoreMessageSender.this.scheduleLinkCloseTimeout(TimeoutTracker.create(CoreMessageSender.this.operationTimeout)); + } + } + }); + } catch (IOException e) { + AsyncUtil.completeFutureExceptionally(this.linkClose, e); + } } else if (this.sendLink == null || this.sendLink.getRemoteState() == EndpointState.CLOSED) { diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java index 1ac233519735..1a7234f3e024 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java @@ -286,10 +286,21 @@ protected CompletableFuture onClose() if (!this.getIsClosed()) { if (this.connection != null && this.connection.getRemoteState() != EndpointState.CLOSED) - { - if (this.connection.getLocalState() != EndpointState.CLOSED) - { - this.connection.close(); + { + try { + this.scheduleOnReactorThread(new DispatchHandler() + { + @Override + public void onEvent() + { + if (MessagingFactory.this.connection != null && MessagingFactory.this.connection.getLocalState() != EndpointState.CLOSED) + { + MessagingFactory.this.connection.close(); + } + } + }); + } catch (IOException e) { + AsyncUtil.completeFutureExceptionally(this.closeTask, e); } Timer.schedule(new Runnable() diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java index 2b88939a29cd..c41072bf1395 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java @@ -338,7 +338,7 @@ public void onEvent() @Override protected CompletableFuture onClose() { - return this.amqpSender.closeAsync().thenCompose((v) -> this.amqpReceiver.closeAsync()); + return this.amqpSender.closeAsync().thenComposeAsync((v) -> this.amqpReceiver.closeAsync()); } private static void scheduleLinkCloseTimeout(CompletableFuture closeFuture, Duration timeout, String linkName) @@ -385,9 +385,22 @@ protected CompletableFuture onClose() { { if (this.receiveLink != null && this.receiveLink.getLocalState() != EndpointState.CLOSED) { - this.receiveLink.close(); - this.parent.underlyingFactory.deregisterForConnectionError(this.receiveLink); - RequestResponseLink.scheduleLinkCloseTimeout(this.closeFuture, this.parent.underlyingFactory.getOperationTimeout(), this.receiveLink.getName()); + try { + this.parent.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + + @Override + public void onEvent() { + if (InternalReceiver.this.receiveLink != null && InternalReceiver.this.receiveLink.getLocalState() != EndpointState.CLOSED) + { + InternalReceiver.this.receiveLink.close(); + InternalReceiver.this.parent.underlyingFactory.deregisterForConnectionError(InternalReceiver.this.receiveLink); + RequestResponseLink.scheduleLinkCloseTimeout(InternalReceiver.this.closeFuture, InternalReceiver.this.parent.underlyingFactory.getOperationTimeout(), InternalReceiver.this.receiveLink.getName()); + } + } + }); + } catch (IOException e) { + this.closeFuture.completeExceptionally(e); + } } else { @@ -528,13 +541,26 @@ protected InternalSender(String clientId, RequestResponseLink parent) { @Override protected CompletableFuture onClose() { if (!this.getIsClosed()) - { + { if (this.sendLink != null && this.sendLink.getLocalState() != EndpointState.CLOSED) { - this.sendLink.close(); - this.parent.underlyingFactory.deregisterForConnectionError(this.sendLink); - RequestResponseLink.scheduleLinkCloseTimeout(this.closeFuture, this.parent.underlyingFactory.getOperationTimeout(), this.sendLink.getName()); - } + try { + this.parent.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() { + + @Override + public void onEvent() { + if (InternalSender.this.sendLink != null && InternalSender.this.sendLink.getLocalState() != EndpointState.CLOSED) + { + InternalSender.this.sendLink.close(); + InternalSender.this.parent.underlyingFactory.deregisterForConnectionError(InternalSender.this.sendLink); + RequestResponseLink.scheduleLinkCloseTimeout(InternalSender.this.closeFuture, InternalSender.this.parent.underlyingFactory.getOperationTimeout(), InternalSender.this.sendLink.getName()); + } + } + }); + } catch (IOException e) { + this.closeFuture.completeExceptionally(e); + } + } else { this.closeFuture.complete(null); From 68215643b7ace078a34b3c297482a966ee032de8 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Thu, 6 Apr 2017 12:26:10 -0700 Subject: [PATCH 03/17] Fixing a test issue --- .../azure/servicebus/MessageAndSessionPumpTests.java | 10 +++++----- .../microsoft/azure/servicebus/QueueClientTests.java | 4 ++-- .../azure/servicebus/SubscriptionClientTests.java | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java index d692bef4fd9c..7bde23f39376 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java @@ -156,7 +156,7 @@ public static void testSessionPumpAutoCompleteWithOneConcurrentCallPerSession(IM MessageAndSessionPumpTests.testSessionPumpAutoComplete(sender, sessionPump, 1); } - public static void testSessionPumpAutoCompleteWithMultipleConcurrentCallPerSession(IMessageSender sender, IMessageAndSessionPump sessionPump) throws InterruptedException, ServiceBusException + public static void testSessionPumpAutoCompleteWithMultipleConcurrentCallsPerSession(IMessageSender sender, IMessageAndSessionPump sessionPump) throws InterruptedException, ServiceBusException { MessageAndSessionPumpTests.testSessionPumpAutoComplete(sender, sessionPump, DEFAULT_MAX_CONCURRENT_CALLS_PER_SESSION); } @@ -260,8 +260,8 @@ public static void testSessionPumpAbandonOnException(IMessageSender sender, IMes public static void testSessionPumpRenewLock(IMessageSender sender, IMessageAndSessionPump sessionPump) throws InterruptedException, ServiceBusException { - int numSessions = 10; - int numMessagePerSession = 10; + int numSessions = 5; + int numMessagePerSession = 2; ArrayList sessionIds = new ArrayList<>(); for(int i=0; i Date: Fri, 7 Apr 2017 19:14:01 -0700 Subject: [PATCH 04/17] Minor refactoring to tests --- .../resources/access.properties.template | 2 + .../primitives/ConnectionStringBuilder.java | 99 +++++++++---------- .../microsoft/azure/servicebus/TestUtils.java | 45 +++++++-- .../TestConnectionStringBuilder.java | 21 ---- 4 files changed, 83 insertions(+), 84 deletions(-) delete mode 100644 azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/TestConnectionStringBuilder.java diff --git a/azure-servicebus/resources/access.properties.template b/azure-servicebus/resources/access.properties.template index 076f9c1bb58c..cfbbd48b858f 100644 --- a/azure-servicebus/resources/access.properties.template +++ b/azure-servicebus/resources/access.properties.template @@ -20,4 +20,6 @@ topic.sessionful.entitypath=yourentitypath subscription.sessionful.entitypath=yoursubscriptionpath topic.sessionful.sharedaccesskeyname=yoursharedaccesskey topic.sessionful.sharedaccesskey=yoursharedaccesskey +#namespace suffix +namespace.suffix=servicebus.windows.net diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java index 96cb5f650287..9ed0f14a1dd0 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java @@ -35,25 +35,24 @@ */ 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 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 + "|" + EntityPathConfigName + "|" + OperationTimeoutConfigName - + "|" + RetryPolicyConfigName + ")"; - - private static final String KeysWithDelimitersRegex = KeyValuePairDelimiter + AllKeyEnumerateRegex - + KeyValueSeparator; + final static String END_POINT_FORMAT = "amqps://%s.servicebus.windows.net"; + final static String END_POINT_RAW_FORMAT = "amqps://%s"; + + final static String HOSTNAME_CONFIG_NAME = "Hostname"; + final static String ENDPOINT_CONFIG_NAME = "Endpoint"; + final static String SHARED_ACCESS_KEY_NAME_CONFIG_NAME = "SharedAccessKeyName"; + final static String SHARED_ACCESS_KEY_CONFIG_NAME = "SharedAccessKey"; + final static String ENTITY_PATH_CONFIG_NAME = "EntityPath"; + final static String OPERATION_TIMEOUT_CONFIG_NAME = "OperationTimeout"; + final static String RETRY_POLICY_CONFIG_NAME = "RetryPolicy"; + final static String KEY_VALUE_SEPARATOR = "="; + final static String KEY_VALUE_PAIR_DELIMITER = ";"; + + private static final String ALL_KEY_ENUMERATE_REGEX = "(" + HOSTNAME_CONFIG_NAME + "|" + ENDPOINT_CONFIG_NAME + "|" + SHARED_ACCESS_KEY_NAME_CONFIG_NAME + + "|" + SHARED_ACCESS_KEY_CONFIG_NAME + "|" + ENTITY_PATH_CONFIG_NAME + "|" + OPERATION_TIMEOUT_CONFIG_NAME + + "|" + RETRY_POLICY_CONFIG_NAME + ")"; + + private static final String KEYS_WITH_DELIMITERS_REGEX = KEY_VALUE_PAIR_DELIMITER + ALL_KEY_ENUMERATE_REGEX + KEY_VALUE_SEPARATOR; private String connectionString; private URI endpoint; @@ -89,7 +88,7 @@ private ConnectionStringBuilder( { try { - this.endpoint = new URI(String.format(Locale.US, this.getEndPointFormat(), namespaceName)); + this.endpoint = new URI(String.format(Locale.US, END_POINT_FORMAT, namespaceName)); } catch(URISyntaxException exception) { @@ -236,38 +235,38 @@ public String toString() StringBuilder connectionStringBuilder = new StringBuilder(); if (this.endpoint != null) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EndpointConfigName, KeyValueSeparator, - this.endpoint.toString(), KeyValuePairDelimiter)); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", ENDPOINT_CONFIG_NAME, KEY_VALUE_SEPARATOR, + this.endpoint.toString(), KEY_VALUE_PAIR_DELIMITER)); } if (!StringUtil.isNullOrWhiteSpace(this.entityPath)) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", EntityPathConfigName, - KeyValueSeparator, this.entityPath, KeyValuePairDelimiter)); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", ENTITY_PATH_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.entityPath, KEY_VALUE_PAIR_DELIMITER)); } if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKeyName)) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SharedAccessKeyNameConfigName, - KeyValueSeparator, this.sharedAccessKeyName, KeyValuePairDelimiter)); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", SHARED_ACCESS_KEY_NAME_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.sharedAccessKeyName, KEY_VALUE_PAIR_DELIMITER)); } if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessKey)) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s", SharedAccessKeyConfigName, - KeyValueSeparator, this.sharedAccessKey)); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s", SHARED_ACCESS_KEY_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.sharedAccessKey)); } if (this.operationTimeout != null) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", KeyValuePairDelimiter, OperationTimeoutConfigName, - KeyValueSeparator, this.operationTimeout.toString())); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", KEY_VALUE_PAIR_DELIMITER, OPERATION_TIMEOUT_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.operationTimeout.toString())); } if (this.retryPolicy != null) { - connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", KeyValuePairDelimiter, RetryPolicyConfigName, - KeyValueSeparator, this.retryPolicy.toString())); + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", KEY_VALUE_PAIR_DELIMITER, RETRY_POLICY_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.retryPolicy.toString())); } this.connectionString = connectionStringBuilder.toString(); @@ -284,9 +283,9 @@ private void parseConnectionString(String connectionString) throw new IllegalConnectionStringFormatException(String.format("connectionString cannot be empty")); } - String connection = KeyValuePairDelimiter + connectionString; + String connection = KEY_VALUE_PAIR_DELIMITER + connectionString; - Pattern keyValuePattern = Pattern.compile(KeysWithDelimitersRegex, Pattern.CASE_INSENSITIVE); + Pattern keyValuePattern = Pattern.compile(KEYS_WITH_DELIMITERS_REGEX, Pattern.CASE_INSENSITIVE); String[] values = keyValuePattern.split(connection); Matcher keys = keyValuePattern.matcher(connection); @@ -315,13 +314,13 @@ private void parseConnectionString(String connectionString) String.format(Locale.US, "Value for the connection string parameter name: %s, not found", key)); } - if (key.equalsIgnoreCase(EndpointConfigName)) + if (key.equalsIgnoreCase(ENDPOINT_CONFIG_NAME)) { 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)); + String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", ENDPOINT_CONFIG_NAME, HOSTNAME_CONFIG_NAME)); } try @@ -331,43 +330,43 @@ private void parseConnectionString(String connectionString) catch(URISyntaxException exception) { throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "%s should be in format scheme://fullyQualifiedServiceBusNamespaceEndpointName", EndpointConfigName), + String.format(Locale.US, "%s should be in format scheme://fullyQualifiedServiceBusNamespaceEndpointName", ENDPOINT_CONFIG_NAME), exception); } } - else if (key.equalsIgnoreCase(HostnameConfigName)) + else if (key.equalsIgnoreCase(HOSTNAME_CONFIG_NAME)) { 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)); + String.format(Locale.US, "Multiple %s and/or %s detected. Make sure only one is defined", ENDPOINT_CONFIG_NAME, HOSTNAME_CONFIG_NAME)); } try { - this.endpoint = new URI(String.format(Locale.US, endpointRawFormat, values[valueIndex])); + this.endpoint = new URI(String.format(Locale.US, END_POINT_RAW_FORMAT, values[valueIndex])); } catch(URISyntaxException exception) { throw new IllegalConnectionStringFormatException( - String.format(Locale.US, "%s should be a fully quantified host name address", HostnameConfigName), + String.format(Locale.US, "%s should be a fully quantified host name address", HOSTNAME_CONFIG_NAME), exception); } } - else if(key.equalsIgnoreCase(SharedAccessKeyNameConfigName)) + else if(key.equalsIgnoreCase(SHARED_ACCESS_KEY_NAME_CONFIG_NAME)) { this.sharedAccessKeyName = values[valueIndex]; } - else if(key.equalsIgnoreCase(SharedAccessKeyConfigName)) + else if(key.equalsIgnoreCase(SHARED_ACCESS_KEY_CONFIG_NAME)) { this.sharedAccessKey = values[valueIndex]; } - else if (key.equalsIgnoreCase(EntityPathConfigName)) + else if (key.equalsIgnoreCase(ENTITY_PATH_CONFIG_NAME)) { this.entityPath = values[valueIndex]; } - else if (key.equalsIgnoreCase(OperationTimeoutConfigName)) + else if (key.equalsIgnoreCase(OPERATION_TIMEOUT_CONFIG_NAME)) { try { @@ -378,7 +377,7 @@ else if (key.equalsIgnoreCase(OperationTimeoutConfigName)) throw new IllegalConnectionStringFormatException("Invalid value specified for property 'Duration' in the ConnectionString.", exception); } } - else if (key.equalsIgnoreCase(RetryPolicyConfigName)) + else if (key.equalsIgnoreCase(RETRY_POLICY_CONFIG_NAME)) { this.retryPolicy = values[valueIndex].equals(ClientConstants.DEFAULT_RETRY) ? RetryPolicy.getDefault() @@ -387,7 +386,7 @@ else if (key.equalsIgnoreCase(RetryPolicyConfigName)) if (this.retryPolicy == null) throw new IllegalConnectionStringFormatException( String.format(Locale.US, "Connection string parameter '%s'='%s' is not recognized", - RetryPolicyConfigName, values[valueIndex])); + RETRY_POLICY_CONFIG_NAME, values[valueIndex])); } else { @@ -396,10 +395,4 @@ else if (key.equalsIgnoreCase(RetryPolicyConfigName)) } } } - - // Just to override in onebox tests - String getEndPointFormat() - { - return this.endpointFormat; - } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java index 8b6a00190b16..19713278dabb 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java @@ -3,12 +3,15 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.time.Duration; import java.util.Collection; +import java.util.Locale; import java.util.Properties; import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; -import com.microsoft.azure.servicebus.primitives.TestConnectionStringBuilder; +import com.microsoft.azure.servicebus.primitives.IllegalConnectionStringFormatException; public class TestUtils { private static final String TEST_DIR_NAME = "resources"; @@ -40,6 +43,10 @@ public class TestUtils { private static final String SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY = "topic.sessionful.sharedaccesskeyname"; private static final String SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY = "topic.sessionful.sharedaccesskey"; + //Namespace suffix + private static final String NAMESPACE_SUFFIX_PROPERTY = "namespace.suffix"; + private static final String DEFAULT_NAMESPACE_SUFFIX = "servicebus.windows.net"; + private static Properties accessProperties; static @@ -59,42 +66,60 @@ public class TestUtils { private static String getProperty(String propertyName) { - return accessProperties.getProperty(propertyName, ""); - } + String defaultValue = ""; + if(propertyName.equalsIgnoreCase(NAMESPACE_SUFFIX_PROPERTY)) + { + defaultValue = DEFAULT_NAMESPACE_SUFFIX; + } + + return accessProperties.getProperty(propertyName, defaultValue); + } + + private static URI getEndPointURI(String namespace) + { + String namespaceSuffix = getProperty(NAMESPACE_SUFFIX_PROPERTY); + try { + return new URI("amqps://" + namespace + "." + namespaceSuffix); + } catch (URISyntaxException e) { + throw new IllegalArgumentException( + String.format(Locale.US, "Invalid namespace or namespace suffix: %s, %s", namespace, namespaceSuffix), + e); + } + } public static ConnectionStringBuilder getQueueConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(QUEUE_NAMESPACENAME_PROPERTY), getProperty(QUEUE_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(QUEUE_NAMESPACENAME_PROPERTY)), getProperty(QUEUE_ENTITYPATH_PROPERTY), getProperty(QUEUE_SHAREDACCESSKEYNAME_PROPERTY), getProperty(QUEUE_SHAREDACCESSKEY_PROPERTY)); } public static ConnectionStringBuilder getSessionfulQueueConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(SESSIONFUL_QUEUE_NAMESPACENAME_PROPERTY), getProperty(SESSIONFUL_QUEUE_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_QUEUE_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_QUEUE_ENTITYPATH_PROPERTY), getProperty(SESSIONFUL_QUEUE_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_QUEUE_SHAREDACCESSKEY_PROPERTY)); } public static ConnectionStringBuilder getTopicConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(TOPIC_NAMESPACENAME_PROPERTY), getProperty(TOPIC_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(TOPIC_NAMESPACENAME_PROPERTY)), getProperty(TOPIC_ENTITYPATH_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEY_PROPERTY)); } public static ConnectionStringBuilder getSessionfulTopicConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY)); } public static ConnectionStringBuilder getSubscriptionConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(TOPIC_NAMESPACENAME_PROPERTY), getProperty(TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SUBSCRIPTION_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(TOPIC_NAMESPACENAME_PROPERTY)), getProperty(TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SUBSCRIPTION_ENTITYPATH_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEY_PROPERTY)); } public static ConnectionStringBuilder getSessionfulSubscriptionConnectionStringBuilder() { - return new TestConnectionStringBuilder(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SESSIONFUL_SUBSCRIPTION_ENTITYPATH_PROPERTY), + return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SESSIONFUL_SUBSCRIPTION_ENTITYPATH_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY)); - } + } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/TestConnectionStringBuilder.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/TestConnectionStringBuilder.java deleted file mode 100644 index 4710ff9f9742..000000000000 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/TestConnectionStringBuilder.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.microsoft.azure.servicebus.primitives; - -public class TestConnectionStringBuilder extends ConnectionStringBuilder -{ - final static String oneBoxEndpointFormat = "amqps://%s.servicebus.onebox.windows-int.net"; - - public TestConnectionStringBuilder( - final String namespaceName, - final String entityPath, - final String sharedAccessKeyName, - final String sharedAccessKey) - { - super(namespaceName, entityPath, sharedAccessKeyName, sharedAccessKey); - } - - @Override - String getEndPointFormat() - { - return oneBoxEndpointFormat; - } -} From 76c4e9f861b050d62cd814a6307459ff9c893827 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Thu, 13 Apr 2017 11:53:14 -0700 Subject: [PATCH 05/17] Fixing a minor issue in ignoring timeout exception on acceptsession. --- .../azure/servicebus/MessageAndSessionPump.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java index ab9f1eb61f4c..184593c6365c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ScheduledFuture; import com.microsoft.azure.servicebus.primitives.MessageLockLostException; @@ -103,6 +104,11 @@ private void receiveAndPumpMessage() receiveMessageFuture.handleAsync((message, receiveEx) -> { if(receiveEx != null) { + if(receiveEx instanceof CompletionException) + { + receiveEx = receiveEx.getCause(); + } + this.messageHandler.notifyException(receiveEx, ExceptionPhase.RECEIVE); this.receiveAndPumpMessage(); } @@ -205,6 +211,11 @@ private void acceptSessionsAndPumpMessage() acceptSessionFuture.handleAsync((session, acceptSessionEx) -> { if(acceptSessionEx != null) { + if(acceptSessionEx instanceof CompletionException) + { + acceptSessionEx = acceptSessionEx.getCause(); + } + if(!(acceptSessionEx instanceof TimeoutException)) { // Timeout exception means no session available.. it is expected so no need to notify client @@ -245,6 +256,11 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) receiverFuture.handleAsync((message, receiveEx) -> { if(receiveEx != null) { + if(receiveEx instanceof CompletionException) + { + receiveEx = receiveEx.getCause(); + } + this.sessionHandler.notifyException(receiveEx, ExceptionPhase.RECEIVE); sessionTracker.shouldRetryOnNoMessageOrException().thenAcceptAsync((shouldRetry) -> { if(shouldRetry) From edae792706ffb564aa25ec0ba8bb78a28c91bcb4 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Fri, 14 Apr 2017 15:18:26 -0700 Subject: [PATCH 06/17] Copied and merged changes to core amqp classes from event hubs java client. --- .../azure/servicebus/amqp/AmqpConstants.java | 4 + .../servicebus/amqp/BaseLinkHandler.java | 146 +++++++++--------- .../servicebus/amqp/ConnectionHandler.java | 10 ++ .../servicebus/amqp/ReactorDispatcher.java | 17 +- .../primitives/ClientConstants.java | 23 +++ 5 files changed, 112 insertions(+), 88 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java index 71f86f8255c6..0bad4f291c9d 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java @@ -21,4 +21,8 @@ private AmqpConstants() { } public static final int MAX_FRAME_SIZE = 65536; public static final String MANAGEMENT_ADDRESS_SEGMENT = "/$management"; + + public static final Symbol PRODUCT = Symbol.valueOf("product"); + public static final Symbol VERSION = Symbol.valueOf("version"); + public static final Symbol PLATFORM = Symbol.valueOf("platform"); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java index 4bf6f26f0af3..94606af8cb9f 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/BaseLinkHandler.java @@ -12,79 +12,75 @@ import com.microsoft.azure.servicebus.primitives.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())); - } - } - } - - @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); - } - } - - @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()); - } - } - - 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); - } +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-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java index 2cb538f823e5..a4e5056f0b31 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java @@ -4,10 +4,13 @@ */ package com.microsoft.azure.servicebus.amqp; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.qpid.proton.Proton; +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; @@ -48,6 +51,13 @@ public void onConnectionInit(Event event) final String hostName = event.getReactor().getConnectionAddress(connection); connection.setHostname(hostName); connection.setContainer(StringUtil.getShortRandomString()); + + 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(); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java index c9ef4223a0e3..cf76df657d36 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ReactorDispatcher.java +++ b/azure-servicebus/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; @@ -114,20 +113,12 @@ public void run(Selectable selectable) catch(IOException ioException) { 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-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java index 2b8806ad4213..17d21e13e4b6 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java @@ -15,6 +15,10 @@ public final class ClientConstants { private ClientConstants() { } + 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 int LOCKTOKENSIZE = 16; public static final String ENQUEUEDTIMEUTCNAME = "x-opt-enqueued-time"; public static final String SCHEDULEDENQUEUETIMENAME = "x-opt-scheduled-enqueue-time"; @@ -140,5 +144,24 @@ private ClientConstants() { } public static final int REQUEST_RESPONSE_NOTFOUND_STATUS_CODE = 0x194; public static final int REQUEST_RESPONSE_UNDEFINED_STATUS_CODE = -1; public static final int REQUEST_RESPONSE_SERVER_BUSY_STATUS_CODE = 0x1f7; + + 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(); + } } From 1f626a5fb6440f7e4f294907d4ae8949c4b524ba Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Fri, 14 Apr 2017 17:20:33 -0700 Subject: [PATCH 07/17] Exposing ClientFactory so users create Senders and Receivers. --- .../main/java/com/microsoft/azure/servicebus/ClientFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java index d9e4d9e42e89..dc7daf681c76 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java @@ -6,7 +6,7 @@ import com.microsoft.azure.servicebus.primitives.MessagingFactory; import com.microsoft.azure.servicebus.primitives.ServiceBusException; -class ClientFactory { +public class ClientFactory { private static final ReceiveMode DEFAULTRECEIVEMODE = ReceiveMode.PeekLock; From 90938ea08a28e20bb4de1e72f8ae2552c76edbd0 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Tue, 18 Apr 2017 18:22:30 -0700 Subject: [PATCH 08/17] Changes to test cases to accept connection string as a system parameter --- azure-servicebus/pom.xml | 5 + .../resources/access.properties.template | 25 ---- azure-servicebus/resources/test.properties | 11 ++ .../primitives/ConnectionStringBuilder.java | 15 ++- .../MessageAndSessionPumpTests.java | 4 +- .../azure/servicebus/QueueClientTests.java | 8 +- .../servicebus/QueueSendReceiveTests.java | 4 +- .../azure/servicebus/QueueSessionTests.java | 4 +- .../servicebus/SubscriptionClientTests.java | 12 +- .../microsoft/azure/servicebus/TestUtils.java | 109 ++++++------------ .../servicebus/TopicSendReceiveTests.java | 4 +- .../azure/servicebus/TopicSessionTests.java | 4 +- 12 files changed, 86 insertions(+), 119 deletions(-) delete mode 100644 azure-servicebus/resources/access.properties.template create mode 100644 azure-servicebus/resources/test.properties diff --git a/azure-servicebus/pom.xml b/azure-servicebus/pom.xml index 65482baa5987..e9f570dc50a3 100644 --- a/azure-servicebus/pom.xml +++ b/azure-servicebus/pom.xml @@ -26,6 +26,11 @@ true + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + diff --git a/azure-servicebus/resources/access.properties.template b/azure-servicebus/resources/access.properties.template deleted file mode 100644 index cfbbd48b858f..000000000000 --- a/azure-servicebus/resources/access.properties.template +++ /dev/null @@ -1,25 +0,0 @@ -# non-partitioned queue with no sessions -queue.namespacename=yournamespace -queue.entitypath=yourentitypath -queue.sharedaccesskeyname=yoursharedaccesskey -queue.sharedaccesskey=yoursharedaccesskey -# non-partitioned queue with sessions -queue.sessionful.namespacename=yournamespace -queue.sessionful.entitypath=yourentitypath -queue.sessionful.sharedaccesskeyname=yoursharedaccesskey -queue.sessionful.sharedaccesskey=yoursharedaccesskey -# non-partitioned topic/subscription with no sessions -topic.namespacename=yournamespace -topic.entitypath=yourentitypath -subscription.entitypath=yoursubscriptionpath -topic.sharedaccesskeyname=yoursharedaccesskey -topic.sharedaccesskey=yoursharedaccesskey -# non-partitioned topic/subscription with sessions -topic.sessionful.namespacename=yournamespace -topic.sessionful.entitypath=yourentitypath -subscription.sessionful.entitypath=yoursubscriptionpath -topic.sessionful.sharedaccesskeyname=yoursharedaccesskey -topic.sessionful.sharedaccesskey=yoursharedaccesskey -#namespace suffix -namespace.suffix=servicebus.windows.net - diff --git a/azure-servicebus/resources/test.properties b/azure-servicebus/resources/test.properties new file mode 100644 index 000000000000..1c4e474f6330 --- /dev/null +++ b/azure-servicebus/resources/test.properties @@ -0,0 +1,11 @@ +#entity names coming from the deployment template +partitioned.queue.name = partitioned-queue +non.partitioned.queue.name = non-partitioned-queue +session.partitioned.queue.name = partitioned-session-queue +session.non.partitioned.queue.name = non-partitioned-session-queue +partitioned.topic.name = partitioned-topic +non.partitioned.topic.name = non-partitioned-topic +session.partitioned.topic.name = partitioned-session-topic +session.non.partitioned.topic.name = non-partitioned-session-topic +subscription.name = subscription +session.subscription.name = session-subscription diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java index 9ed0f14a1dd0..ed8282deb5b8 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java @@ -145,8 +145,19 @@ public ConnectionStringBuilder( */ public ConnectionStringBuilder(String connectionString) { - this.parseConnectionString(connectionString); - this.connectionString = connectionString; + this.parseConnectionString(connectionString); + } + + /** + * ConnectionString format: + * Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY + * @param namespaceConnectionString connections string of the ServiceBus namespace. This doesn't include the entity path. + * @param entityPath path to the entity within the namespace + */ + public ConnectionStringBuilder(String namespaceConnectionString, String entityPath) + { + this(namespaceConnectionString); + this.entityPath = entityPath; } /** diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java index 7bde23f39376..e970c258a5aa 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java @@ -329,7 +329,7 @@ private static class CountingMessageHandler extends TestMessageHandler public CompletableFuture onMessageAsync(IMessage message) { CompletableFuture countingFuture = CompletableFuture.runAsync(() -> { this.maxConcurrencyCounter.incrementCount(); - System.out.println("Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread()); + //System.out.println("Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread()); if(this.firstThrowException && message.getDeliveryCount() == 0) { this.messageCountDownLatch.countDown(); @@ -412,7 +412,7 @@ public CompletableFuture onMessageAsync(IMessageSession session, IMessage CompletableFuture countingFuture = CompletableFuture.runAsync(() -> { this.maxConcurrencyCounter.incrementCount(); this.receivedSeesions.add(session.getSessionId()); - System.out.println("SessionID:" + session.getSessionId() + " - Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread()); + //System.out.println("SessionID:" + session.getSessionId() + " - Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread()); if(this.firstThrowException && message.getDeliveryCount() == 0) { this.messageCountDownLatch.countDown(); diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java index de7dd1af29a3..4540a42da3d3 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java @@ -22,12 +22,12 @@ public void tearDown() throws ServiceBusException, InterruptedException if(this.queueClient != null) { this.queueClient.close(); - TestCommons.drainAllMessages(TestUtils.getQueueConnectionStringBuilder()); + TestCommons.drainAllMessages(TestUtils.getNonPartitionedQueueConnectionStringBuilder()); } if(this.sessionfulQueueClient != null) { - TestCommons.drainAllSessions(this.sessionfulQueueClient, TestUtils.getSessionfulQueueConnectionStringBuilder()); + TestCommons.drainAllSessions(this.sessionfulQueueClient, TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder()); this.sessionfulQueueClient.close(); } } @@ -44,12 +44,12 @@ private void createSessionfulQueueClient() throws InterruptedException, ServiceB private void createQueueClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.queueClient = new QueueClient(TestUtils.getQueueConnectionStringBuilder().toString(), receiveMode); + this.queueClient = new QueueClient(TestUtils.getNonPartitionedQueueConnectionStringBuilder().toString(), receiveMode); } private void createSessionfulQueueClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.sessionfulQueueClient = new QueueClient(TestUtils.getSessionfulQueueConnectionStringBuilder().toString(), receiveMode); + this.sessionfulQueueClient = new QueueClient(TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder().toString(), receiveMode); } @Test diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSendReceiveTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSendReceiveTests.java index 36626fe76c11..e4de59604137 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSendReceiveTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSendReceiveTests.java @@ -12,12 +12,12 @@ public class QueueSendReceiveTests extends SendReceiveTests // private int counter = 0; @Override public ConnectionStringBuilder getSenderConnectionStringBuilder() { - return TestUtils.getQueueConnectionStringBuilder(); + return TestUtils.getNonPartitionedQueueConnectionStringBuilder(); } @Override public ConnectionStringBuilder getReceiverConnectionStringBuilder() { - return TestUtils.getQueueConnectionStringBuilder(); + return TestUtils.getNonPartitionedQueueConnectionStringBuilder(); } // @org.junit.Test diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSessionTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSessionTests.java index d9f5ba632406..ac33e0e2efa6 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSessionTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueSessionTests.java @@ -6,11 +6,11 @@ public class QueueSessionTests extends SessionTests { @Override public ConnectionStringBuilder getSenderConnectionStringBuilder() { - return TestUtils.getSessionfulQueueConnectionStringBuilder(); + return TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder(); } @Override public ConnectionStringBuilder getReceiverConnectionStringBuilder() { - return TestUtils.getSessionfulQueueConnectionStringBuilder(); + return TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder(); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java index fe800d9c99c5..f7c201dffad0 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java @@ -41,12 +41,12 @@ public void tearDown() throws ServiceBusException, InterruptedException if(this.subscriptionClient != null) { this.subscriptionClient.close(); - TestCommons.drainAllMessages(TestUtils.getSubscriptionConnectionStringBuilder()); + TestCommons.drainAllMessages(TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder()); } if(this.sessionfulSubscriptionClient != null) { - TestCommons.drainAllSessions(this.sessionfulSubscriptionClient, TestUtils.getSessionfulSubscriptionConnectionStringBuilder()); + TestCommons.drainAllSessions(this.sessionfulSubscriptionClient, TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder()); this.sessionfulSubscriptionClient.close(); } } @@ -63,14 +63,14 @@ private void createSessionfulSubscriptionClient() throws InterruptedException, S private void createSubscriptionClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.topicClient = new TopicClient(TestUtils.getTopicConnectionStringBuilder().toString()); - this.subscriptionClient = new SubscriptionClient(TestUtils.getSubscriptionConnectionStringBuilder().toString(), receiveMode); + this.topicClient = new TopicClient(TestUtils.getNonPartitionedTopicConnectionStringBuilder().toString()); + this.subscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder().toString(), receiveMode); } private void createSessionfulSubscriptionClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.sessionfulTopicClient = new TopicClient(TestUtils.getSessionfulTopicConnectionStringBuilder().toString()); - this.sessionfulSubscriptionClient = new SubscriptionClient(TestUtils.getSessionfulSubscriptionConnectionStringBuilder().toString(), receiveMode); + this.sessionfulTopicClient = new TopicClient(TestUtils.getNonPartitionedSessionfulTopicConnectionStringBuilder().toString()); + this.sessionfulSubscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder().toString(), receiveMode); } @Test diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java index 19713278dabb..0ed0ccef7f4e 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java @@ -3,51 +3,32 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Duration; -import java.util.Collection; -import java.util.Locale; import java.util.Properties; import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; -import com.microsoft.azure.servicebus.primitives.IllegalConnectionStringFormatException; public class TestUtils { private static final String TEST_DIR_NAME = "resources"; - private static final String ACCESS_PROPERTIES_FILE_NAME = "access.properties"; + private static final String TEST_PROPERTIES_FILE_NAME = "test.properties"; - //Queue - private static final String QUEUE_NAMESPACENAME_PROPERTY = "queue.namespacename"; - private static final String QUEUE_ENTITYPATH_PROPERTY = "queue.entitypath"; - private static final String QUEUE_SHAREDACCESSKEYNAME_PROPERTY = "queue.sharedaccesskeyname"; - private static final String QUEUE_SHAREDACCESSKEY_PROPERTY = "queue.sharedaccesskey"; + private static final String NAMESPACE_CONNECTION_STRING_PROPERTY_NAME = "azure.servicebus.java.unit.test.connection.string"; - //Sessionful Queue - private static final String SESSIONFUL_QUEUE_NAMESPACENAME_PROPERTY = "queue.sessionful.namespacename"; - private static final String SESSIONFUL_QUEUE_ENTITYPATH_PROPERTY = "queue.sessionful.entitypath"; - private static final String SESSIONFUL_QUEUE_SHAREDACCESSKEYNAME_PROPERTY = "queue.sessionful.sharedaccesskeyname"; - private static final String SESSIONFUL_QUEUE_SHAREDACCESSKEY_PROPERTY = "queue.sessionful.sharedaccesskey"; + //Queue + private static final String NON_PARTITIONED_QUEUE_NAME_PROPERTY = "non.partitioned.queue.name"; - //Topic and Subscription - private static final String TOPIC_NAMESPACENAME_PROPERTY = "topic.namespacename"; - private static final String TOPIC_ENTITYPATH_PROPERTY = "topic.entitypath"; - private static final String SUBSCRIPTION_ENTITYPATH_PROPERTY = "subscription.entitypath"; - private static final String TOPIC_SHAREDACCESSKEYNAME_PROPERTY = "topic.sharedaccesskeyname"; - private static final String TOPIC_SHAREDACCESSKEY_PROPERTY = "topic.sharedaccesskey"; - - //Sessionful Topic and Subscription - private static final String SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY = "topic.sessionful.namespacename"; - private static final String SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY = "topic.sessionful.entitypath"; - private static final String SESSIONFUL_SUBSCRIPTION_ENTITYPATH_PROPERTY = "subscription.sessionful.entitypath"; - private static final String SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY = "topic.sessionful.sharedaccesskeyname"; - private static final String SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY = "topic.sessionful.sharedaccesskey"; + //Sessionful Queue + private static final String NON_PARTITIONED_SESSIONFUL_QUEUE_NAME_PROPERTY = "session.non.partitioned.queue.name"; - //Namespace suffix - private static final String NAMESPACE_SUFFIX_PROPERTY = "namespace.suffix"; - private static final String DEFAULT_NAMESPACE_SUFFIX = "servicebus.windows.net"; + //Topic and Subscription + private static final String NON_PARTITIONED_TOPIC_NAME_PROPERTY = "non.partitioned.topic.name"; + private static final String SUBSCRIPTION_NAME_PROPERTY = "subscription.name"; + + //Sessionful Topic and Subscription + private static final String NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY = "non.partitioned.topic.name"; + private static final String SESSIONFUL_SUBSCRIPTION_NAME_PROPERTY = "session.subscription.name"; private static Properties accessProperties; + private static String namespaceConnectionString; static { @@ -55,71 +36,55 @@ public class TestUtils { String workingDir = System.getProperty("user.dir"); try { - accessProperties.load(new FileReader(workingDir + File.separator + TEST_DIR_NAME + File.separator + ACCESS_PROPERTIES_FILE_NAME)); + accessProperties.load(new FileReader(workingDir + File.separator + TEST_DIR_NAME + File.separator + TEST_PROPERTIES_FILE_NAME)); } catch(IOException ioe) { // User properties file not found. Don't do anything, properties remain empty. - System.err.println(ACCESS_PROPERTIES_FILE_NAME + " file not found. Tests will not be able to connecto to any service bus entity."); + System.err.println(TEST_PROPERTIES_FILE_NAME + " file not found. Tests will not be able to connecto to any service bus entity."); + } + + // Read connection string + namespaceConnectionString = System.getProperty(NAMESPACE_CONNECTION_STRING_PROPERTY_NAME); + if(namespaceConnectionString == null || namespaceConnectionString.isEmpty()) + { + System.err.println(NAMESPACE_CONNECTION_STRING_PROPERTY_NAME + " system property not set. Tests will not be able to connecto to any service bus entity."); } } private static String getProperty(String propertyName) { - String defaultValue = ""; - if(propertyName.equalsIgnoreCase(NAMESPACE_SUFFIX_PROPERTY)) - { - defaultValue = DEFAULT_NAMESPACE_SUFFIX; - } - + String defaultValue = ""; return accessProperties.getProperty(propertyName, defaultValue); - } - - private static URI getEndPointURI(String namespace) - { - String namespaceSuffix = getProperty(NAMESPACE_SUFFIX_PROPERTY); - try { - return new URI("amqps://" + namespace + "." + namespaceSuffix); - } catch (URISyntaxException e) { - throw new IllegalArgumentException( - String.format(Locale.US, "Invalid namespace or namespace suffix: %s, %s", namespace, namespaceSuffix), - e); - } - } + } - public static ConnectionStringBuilder getQueueConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedQueueConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(QUEUE_NAMESPACENAME_PROPERTY)), getProperty(QUEUE_ENTITYPATH_PROPERTY), - getProperty(QUEUE_SHAREDACCESSKEYNAME_PROPERTY), getProperty(QUEUE_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_QUEUE_NAME_PROPERTY)); } - public static ConnectionStringBuilder getSessionfulQueueConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedSessionfulQueueConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_QUEUE_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_QUEUE_ENTITYPATH_PROPERTY), - getProperty(SESSIONFUL_QUEUE_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_QUEUE_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_SESSIONFUL_QUEUE_NAME_PROPERTY)); } - public static ConnectionStringBuilder getTopicConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedTopicConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(TOPIC_NAMESPACENAME_PROPERTY)), getProperty(TOPIC_ENTITYPATH_PROPERTY), - getProperty(TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_TOPIC_NAME_PROPERTY)); } - public static ConnectionStringBuilder getSessionfulTopicConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedSessionfulTopicConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY), - getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY)); } - public static ConnectionStringBuilder getSubscriptionConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedSubscriptionConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(TOPIC_NAMESPACENAME_PROPERTY)), getProperty(TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SUBSCRIPTION_ENTITYPATH_PROPERTY), - getProperty(TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(TOPIC_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_TOPIC_NAME_PROPERTY) + "/subscriptions/" + getProperty(SUBSCRIPTION_NAME_PROPERTY)); } - public static ConnectionStringBuilder getSessionfulSubscriptionConnectionStringBuilder() + public static ConnectionStringBuilder getNonPartitionedSessionfulSubscriptionConnectionStringBuilder() { - return new ConnectionStringBuilder(getEndPointURI(getProperty(SESSIONFUL_TOPIC_NAMESPACENAME_PROPERTY)), getProperty(SESSIONFUL_TOPIC_ENTITYPATH_PROPERTY) + "/subscriptions/" + getProperty(SESSIONFUL_SUBSCRIPTION_ENTITYPATH_PROPERTY), - getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEYNAME_PROPERTY), getProperty(SESSIONFUL_TOPIC_SHAREDACCESSKEY_PROPERTY)); + return new ConnectionStringBuilder(namespaceConnectionString, getProperty(NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY) + "/subscriptions/" + getProperty(SESSIONFUL_SUBSCRIPTION_NAME_PROPERTY)); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSendReceiveTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSendReceiveTests.java index 5ae09b95486d..017b92fcbc70 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSendReceiveTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSendReceiveTests.java @@ -5,11 +5,11 @@ public class TopicSendReceiveTests extends SendReceiveTests { @Override public ConnectionStringBuilder getSenderConnectionStringBuilder() { - return TestUtils.getTopicConnectionStringBuilder(); + return TestUtils.getNonPartitionedTopicConnectionStringBuilder(); } @Override public ConnectionStringBuilder getReceiverConnectionStringBuilder() { - return TestUtils.getSubscriptionConnectionStringBuilder(); + return TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder(); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSessionTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSessionTests.java index afbacf8e698c..37568a9d313e 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSessionTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TopicSessionTests.java @@ -6,11 +6,11 @@ public class TopicSessionTests extends SessionTests { @Override public ConnectionStringBuilder getSenderConnectionStringBuilder() { - return TestUtils.getSessionfulTopicConnectionStringBuilder(); + return TestUtils.getNonPartitionedSessionfulTopicConnectionStringBuilder(); } @Override public ConnectionStringBuilder getReceiverConnectionStringBuilder() { - return TestUtils.getSessionfulSubscriptionConnectionStringBuilder(); + return TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder(); } } From 2da6a6daef64c00ed9ec074f78975d8ea60fc8c1 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Tue, 25 Apr 2017 16:40:40 -0700 Subject: [PATCH 09/17] Fixing some test issues. Also changing name of the namespace connection string environment variable. --- .../MessageAndSessionPumpTests.java | 5 ++- .../azure/servicebus/QueueClientTests.java | 4 +- .../azure/servicebus/SendReceiveTests.java | 14 +------ .../servicebus/SubscriptionClientTests.java | 4 +- .../azure/servicebus/TestCommons.java | 37 +++++++++++-------- .../azure/servicebus/TestSessionHandler.java | 3 +- .../microsoft/azure/servicebus/TestUtils.java | 10 ++--- 7 files changed, 37 insertions(+), 40 deletions(-) diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java index e970c258a5aa..cdff70cae3bd 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/MessageAndSessionPumpTests.java @@ -1,6 +1,7 @@ package com.microsoft.azure.servicebus; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -412,7 +413,7 @@ public CompletableFuture onMessageAsync(IMessageSession session, IMessage CompletableFuture countingFuture = CompletableFuture.runAsync(() -> { this.maxConcurrencyCounter.incrementCount(); this.receivedSeesions.add(session.getSessionId()); - //System.out.println("SessionID:" + session.getSessionId() + " - Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread()); + //System.out.println("SessionID:" + session.getSessionId() + " - Message Received - " + message.getMessageId() + " - delivery count:" + message.getDeliveryCount() + " - Thread:" + Thread.currentThread() + ":" + Instant.now()); if(this.firstThrowException && message.getDeliveryCount() == 0) { this.messageCountDownLatch.countDown(); @@ -448,7 +449,7 @@ public CompletableFuture onMessageAsync(IMessageSession session, IMessage @Override public CompletableFuture OnCloseSessionAsync(IMessageSession session) { - System.out.println("Session closed.-" + session.getSessionId()); + System.out.println("Session closed.-" + session.getSessionId() + ":" + Instant.now()); return CompletableFuture.completedFuture(null); } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java index 4540a42da3d3..26b940fc2796 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java @@ -27,8 +27,8 @@ public void tearDown() throws ServiceBusException, InterruptedException if(this.sessionfulQueueClient != null) { - TestCommons.drainAllSessions(this.sessionfulQueueClient, TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder()); - this.sessionfulQueueClient.close(); + this.sessionfulQueueClient.close(); + TestCommons.drainAllSessions(TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder(), true); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SendReceiveTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SendReceiveTests.java index 8cb98f2d3c9f..eaa8c8d75666 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SendReceiveTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SendReceiveTests.java @@ -40,19 +40,7 @@ public void tearDown() throws ServiceBusException, InterruptedException, Executi public abstract ConnectionStringBuilder getSenderConnectionStringBuilder(); - public abstract ConnectionStringBuilder getReceiverConnectionStringBuilder(); - - @Test - public void testBasicSend() throws InterruptedException, ServiceBusException - { - TestCommons.testBasicSend(this.sender); - } - - @Test - public void testBasicSendBatch() throws InterruptedException, ServiceBusException - { - TestCommons.testBasicSendBatch(this.sender); - } + public abstract ConnectionStringBuilder getReceiverConnectionStringBuilder(); @Test public void testBasicReceiveAndDelete() throws InterruptedException, ServiceBusException, ExecutionException diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java index f7c201dffad0..d7c4e96dccd4 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java @@ -46,8 +46,8 @@ public void tearDown() throws ServiceBusException, InterruptedException if(this.sessionfulSubscriptionClient != null) { - TestCommons.drainAllSessions(this.sessionfulSubscriptionClient, TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder()); - this.sessionfulSubscriptionClient.close(); + this.sessionfulSubscriptionClient.close(); + TestCommons.drainAllSessions(TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder(), false); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java index 391ece6834b6..2631067fbb51 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java @@ -14,18 +14,16 @@ import java.util.concurrent.ExecutionException; import org.junit.Assert; -import org.junit.Test; import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; import com.microsoft.azure.servicebus.primitives.MessageNotFoundException; -import com.microsoft.azure.servicebus.primitives.MessagingFactory; import com.microsoft.azure.servicebus.primitives.ServiceBusException; import com.microsoft.azure.servicebus.primitives.StringUtil; -import com.microsoft.azure.servicebus.primitives.TimeoutException; public class TestCommons { - private static Duration shortWaitTime = Duration.ofSeconds(5); + private static final Duration SHORT_WAIT_TIME = Duration.ofSeconds(5); + private static final Duration DRAIN_MESSAGES_WAIT_TIME = Duration.ofSeconds(5); public static void testBasicSend(IMessageSender sender) throws InterruptedException, ServiceBusException { @@ -56,7 +54,7 @@ public static void testBasicReceiveAndDelete(IMessageSender sender, String sessi IMessage receivedMessage = receiver.receive(); Assert.assertNotNull("Message not received", receivedMessage); Assert.assertEquals("Message Id did not match", messageId, receivedMessage.getMessageId()); - receivedMessage = receiver.receive(shortWaitTime); + receivedMessage = receiver.receive(SHORT_WAIT_TIME); Assert.assertNull("Message received again", receivedMessage); } @@ -84,7 +82,7 @@ public static void testBasicReceiveBatchAndDelete(IMessageSender sender, String } Assert.assertEquals("All messages not received", numMessages, totalReceivedMessages); - receivedMessages = receiver.receiveBatch(numMessages, shortWaitTime); + receivedMessages = receiver.receiveBatch(numMessages, SHORT_WAIT_TIME); Assert.assertNull("Messages received again", receivedMessages); } @@ -103,7 +101,7 @@ public static void testBasicReceiveAndComplete(IMessageSender sender, String ses Assert.assertNotNull("Message not received", receivedMessage); Assert.assertEquals("Message Id did not match", messageId, receivedMessage.getMessageId()); receiver.complete(receivedMessage.getLockToken()); - receivedMessage = receiver.receive(shortWaitTime); + receivedMessage = receiver.receive(SHORT_WAIT_TIME); Assert.assertNull("Message was not properly completed", receivedMessage); } @@ -145,7 +143,7 @@ public static void testBasicReceiveAndDeadLetter(IMessageSender sender, String s Assert.assertEquals("Message Id did not match", messageId, receivedMessage.getMessageId()); String deadLetterReason = "java client deadletter test"; receiver.deadLetter(receivedMessage.getLockToken(), deadLetterReason, null); - receivedMessage = receiver.receive(shortWaitTime); + receivedMessage = receiver.receive(SHORT_WAIT_TIME); Assert.assertNull("Message was not properly deadlettered", receivedMessage); } @@ -195,7 +193,7 @@ public static void testBasicReceiveAndRenewLockBatch(IMessageSender sender, Stri receivedMessages = receiver.receiveBatch(numMessages); totalReceivedMessages.addAll(receivedMessages); } - Assert.assertEquals("All messages not received", numMessages, totalReceivedMessages.size()); + Assert.assertEquals("All messages not received", numMessages, totalReceivedMessages.size()); ArrayList oldLockTimes = new ArrayList(); for(IMessage message : totalReceivedMessages) @@ -247,7 +245,7 @@ public static void testBasicReceiveBatchAndComplete(IMessageSender sender, Strin } Assert.assertEquals("All messages not received", numMessages, totalMessagesReceived); - receivedMessages = receiver.receiveBatch(numMessages, shortWaitTime); + receivedMessages = receiver.receiveBatch(numMessages, SHORT_WAIT_TIME); Assert.assertNull("Messages received again", receivedMessages); } @@ -565,10 +563,9 @@ public static void drainAllMessagesFromReceiver(IMessageReceiver receiver) throw } public static void drainAllMessagesFromReceiver(IMessageReceiver receiver, boolean cleanDeferredMessages) throws InterruptedException, ServiceBusException - { - Duration waitTime = Duration.ofSeconds(5); + { final int batchSize = 10; - Collection messages = receiver.receiveBatch(batchSize, waitTime); + Collection messages = receiver.receiveBatch(batchSize, DRAIN_MESSAGES_WAIT_TIME); while(messages !=null && messages.size() > 0) { if(receiver.getReceiveMode() == ReceiveMode.PeekLock) @@ -578,7 +575,7 @@ public static void drainAllMessagesFromReceiver(IMessageReceiver receiver, boole receiver.complete(message.getLockToken()); } } - messages = receiver.receiveBatch(batchSize, waitTime); + messages = receiver.receiveBatch(batchSize, DRAIN_MESSAGES_WAIT_TIME); } if(cleanDeferredMessages) @@ -610,9 +607,19 @@ public static void drainAllMessages(ConnectionStringBuilder connectionStringBuil receiver.close(); } - public static void drainAllSessions(IMessageSessionEntity sessionsClient, ConnectionStringBuilder connectionStringBuilder) throws InterruptedException, ServiceBusException + public static void drainAllSessions(ConnectionStringBuilder connectionStringBuilder, boolean isQueue) throws InterruptedException, ServiceBusException { int numParallelSessionDrains = 5; + IMessageSessionEntity sessionsClient; + if(isQueue) + { + sessionsClient = new QueueClient(connectionStringBuilder.toString(), ReceiveMode.ReceiveAndDelete); + } + else + { + sessionsClient = new SubscriptionClient(connectionStringBuilder.toString(), ReceiveMode.ReceiveAndDelete); + } + Collection browsableSessions = sessionsClient.getMessageSessions(); if(browsableSessions != null && browsableSessions.size() > 0) { diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestSessionHandler.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestSessionHandler.java index ad9850e7da21..868c1fa33ea2 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestSessionHandler.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestSessionHandler.java @@ -1,11 +1,12 @@ package com.microsoft.azure.servicebus; +import java.time.Instant; import java.util.concurrent.CompletableFuture; public abstract class TestSessionHandler implements ISessionHandler { @Override public void notifyException(Throwable exception, ExceptionPhase phase) { - System.out.println(phase + "-" + exception.getMessage()); + System.out.println(phase + "-" + exception.getMessage() + ":" + Instant.now()); } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java index 0ed0ccef7f4e..0c04ae813837 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java @@ -11,7 +11,7 @@ public class TestUtils { private static final String TEST_DIR_NAME = "resources"; private static final String TEST_PROPERTIES_FILE_NAME = "test.properties"; - private static final String NAMESPACE_CONNECTION_STRING_PROPERTY_NAME = "azure.servicebus.java.unit.test.connection.string"; + private static final String NAMESPACE_CONNECTION_STRING_ENVIRONMENT_VARIABLE_NAME = "AZURE_SERVICEBUS_JAVA_CLIENT_TEST_CONNECTION_STRING"; //Queue private static final String NON_PARTITIONED_QUEUE_NAME_PROPERTY = "non.partitioned.queue.name"; @@ -24,7 +24,7 @@ public class TestUtils { private static final String SUBSCRIPTION_NAME_PROPERTY = "subscription.name"; //Sessionful Topic and Subscription - private static final String NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY = "non.partitioned.topic.name"; + private static final String NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY = "session.non.partitioned.topic.name"; private static final String SESSIONFUL_SUBSCRIPTION_NAME_PROPERTY = "session.subscription.name"; private static Properties accessProperties; @@ -45,10 +45,10 @@ public class TestUtils { } // Read connection string - namespaceConnectionString = System.getProperty(NAMESPACE_CONNECTION_STRING_PROPERTY_NAME); + namespaceConnectionString = System.getenv(NAMESPACE_CONNECTION_STRING_ENVIRONMENT_VARIABLE_NAME); if(namespaceConnectionString == null || namespaceConnectionString.isEmpty()) { - System.err.println(NAMESPACE_CONNECTION_STRING_PROPERTY_NAME + " system property not set. Tests will not be able to connecto to any service bus entity."); + System.err.println(NAMESPACE_CONNECTION_STRING_ENVIRONMENT_VARIABLE_NAME + " environment variable not set. Tests will not be able to connecto to any service bus entity."); } } @@ -56,7 +56,7 @@ private static String getProperty(String propertyName) { String defaultValue = ""; return accessProperties.getProperty(propertyName, defaultValue); - } + } public static ConnectionStringBuilder getNonPartitionedQueueConnectionStringBuilder() { From e2648e2c05021843d235e291a62bce2b06623b69 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Tue, 25 Apr 2017 17:01:08 -0700 Subject: [PATCH 10/17] Some minor fixes to session pump. --- .../servicebus/MessageAndSessionPump.java | 136 +++++++++--------- .../com/microsoft/azure/servicebus/Utils.java | 15 +- .../servicebus/primitives/ExceptionUtil.java | 2 - .../primitives/RequestResponseLink.java | 24 ++-- 4 files changed, 88 insertions(+), 89 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java index 184593c6365c..ffd5864a5a6a 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import com.microsoft.azure.servicebus.primitives.MessageLockLostException; @@ -26,6 +26,7 @@ class MessageAndSessionPump extends InitializableEntity implements IMessageAndSe private static final Duration MAXIMUM_RENEW_LOCK_BUFFER = Duration.ofSeconds(10); private static final Duration SLEEP_DURATION_ON_ACCEPT_SESSION_EXCEPTION = Duration.ofMinutes(1); + private final ConcurrentHashMap openSessions; private final MessagingFactory factory; private final String entityPath; private final ReceiveMode receiveMode; @@ -35,7 +36,7 @@ class MessageAndSessionPump extends InitializableEntity implements IMessageAndSe private IMessageHandler messageHandler; private ISessionHandler sessionHandler; private MessageHandlerOptions messageHandlerOptions; - private SessionHandlerOptions sessionHandlerOptions; + private SessionHandlerOptions sessionHandlerOptions; public MessageAndSessionPump(MessagingFactory factory, String entityPath, ReceiveMode receiveMode) { @@ -43,6 +44,7 @@ public MessageAndSessionPump(MessagingFactory factory, String entityPath, Receiv this.factory = factory; this.entityPath = entityPath; this.receiveMode = receiveMode; + this.openSessions = new ConcurrentHashMap<>(); } @Override @@ -104,12 +106,8 @@ private void receiveAndPumpMessage() receiveMessageFuture.handleAsync((message, receiveEx) -> { if(receiveEx != null) { - if(receiveEx instanceof CompletionException) - { - receiveEx = receiveEx.getCause(); - } - - this.messageHandler.notifyException(receiveEx, ExceptionPhase.RECEIVE); + receiveEx = Utils.extractAsyncCompletionCause(receiveEx); + this.notifyExceptionToMessageHandler(receiveEx, ExceptionPhase.RECEIVE); this.receiveAndPumpMessage(); } else @@ -125,7 +123,7 @@ private void receiveAndPumpMessage() if(this.innerReceiver.getReceiveMode() == ReceiveMode.PeekLock) { Instant stopRenewMessageLockAt = Instant.now().plus(this.messageHandlerOptions.getMaxAutoRenewDuration()); - renewLockLoop = new MessgeRenewLockLoop(this.innerReceiver, new HandlerWrapper(this.messageHandler), message, stopRenewMessageLockAt); + renewLockLoop = new MessgeRenewLockLoop(this.innerReceiver, this, message, stopRenewMessageLockAt); renewLockLoop.startLoop(); } else @@ -136,18 +134,19 @@ private void receiveAndPumpMessage() CompletableFuture onMessageFuture; try { - onMessageFuture = this.messageHandler.onMessageAsync(message); + onMessageFuture = this.messageHandler.onMessageAsync(message); } catch(Exception onMessageSyncEx) { onMessageFuture = new CompletableFuture(); - onMessageFuture.completeExceptionally(onMessageSyncEx); + onMessageFuture.completeExceptionally(onMessageSyncEx); } onMessageFuture.handleAsync((v, onMessageEx) -> { if(onMessageEx != null) { - this.messageHandler.notifyException(onMessageEx, ExceptionPhase.USERCALLBACK); + onMessageEx = Utils.extractAsyncCompletionCause(onMessageEx); + this.notifyExceptionToMessageHandler(onMessageEx, ExceptionPhase.USERCALLBACK); } if(this.innerReceiver.getReceiveMode() == ReceiveMode.PeekLock) { @@ -180,8 +179,8 @@ private void receiveAndPumpMessage() updateDispositionFuture.handleAsync((u, updateDispositionEx) -> { if(updateDispositionEx != null) { - System.out.println(message.getMessageId()); - this.messageHandler.notifyException(updateDispositionEx, dispositionPhase); + updateDispositionEx = Utils.extractAsyncCompletionCause(updateDispositionEx); + this.notifyExceptionToMessageHandler(updateDispositionEx, dispositionPhase); } this.receiveAndPumpMessage(); return null; @@ -211,15 +210,12 @@ private void acceptSessionsAndPumpMessage() acceptSessionFuture.handleAsync((session, acceptSessionEx) -> { if(acceptSessionEx != null) { - if(acceptSessionEx instanceof CompletionException) - { - acceptSessionEx = acceptSessionEx.getCause(); - } + acceptSessionEx = Utils.extractAsyncCompletionCause(acceptSessionEx); if(!(acceptSessionEx instanceof TimeoutException)) { // Timeout exception means no session available.. it is expected so no need to notify client - this.sessionHandler.notifyException(acceptSessionEx, ExceptionPhase.ACCEPTSESSION); + this.notifyExceptionToSessionHandler(acceptSessionEx, ExceptionPhase.ACCEPTSESSION); } if(!(acceptSessionEx instanceof OperationCancelledException)) @@ -232,7 +228,8 @@ private void acceptSessionsAndPumpMessage() else { // Received a session.. Now pump messages.. - SessionRenewLockLoop sessionRenewLockLoop = new SessionRenewLockLoop(session, new HandlerWrapper(this.sessionHandler)); + this.openSessions.put(session.getSessionId(), session); + SessionRenewLockLoop sessionRenewLockLoop = new SessionRenewLockLoop(session, this); sessionRenewLockLoop.startLoop(); SessionTracker sessionTracker = new SessionTracker(this, session, sessionRenewLockLoop); for(int i=0; i { if(receiveEx != null) { - if(receiveEx instanceof CompletionException) - { - receiveEx = receiveEx.getCause(); - } - - this.sessionHandler.notifyException(receiveEx, ExceptionPhase.RECEIVE); + receiveEx = Utils.extractAsyncCompletionCause(receiveEx); + this.notifyExceptionToSessionHandler(receiveEx, ExceptionPhase.RECEIVE); sessionTracker.shouldRetryOnNoMessageOrException().thenAcceptAsync((shouldRetry) -> { if(shouldRetry) { @@ -292,7 +285,6 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) try { onMessageFuture = this.sessionHandler.onMessageAsync(session, message); - } catch(Exception onMessageSyncEx) { @@ -304,7 +296,8 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) renewCancelTimer.cancel(true); if(onMessageEx != null) { - this.sessionHandler.notifyException(onMessageEx, ExceptionPhase.USERCALLBACK); + onMessageEx = Utils.extractAsyncCompletionCause(onMessageEx); + this.notifyExceptionToSessionHandler(onMessageEx, ExceptionPhase.USERCALLBACK); } if(this.receiveMode == ReceiveMode.PeekLock) { @@ -333,7 +326,8 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) updateDispositionFuture.handleAsync((u, updateDispositionEx) -> { if(updateDispositionEx != null) { - this.sessionHandler.notifyException(updateDispositionEx, dispositionPhase); + updateDispositionEx = Utils.extractAsyncCompletionCause(updateDispositionEx); + this.notifyExceptionToSessionHandler(updateDispositionEx, dispositionPhase); } this.receiveFromSessionAndPumpMessage(sessionTracker); return null; @@ -362,7 +356,14 @@ CompletableFuture initializeAsync(){ @Override protected CompletableFuture onClose() { - return this.innerReceiver == null ? CompletableFuture.completedFuture(null) : this.innerReceiver.closeAsync(); + CompletableFuture[] closeFutures = new CompletableFuture[this.openSessions.size() + 1]; + int arrayIndex = 0; + for(IMessageSession session : this.openSessions.values()) + { + closeFutures[arrayIndex++] = session.closeAsync(); + } + closeFutures[arrayIndex] = this.innerReceiver == null ? CompletableFuture.completedFuture(null) : this.innerReceiver.closeAsync(); + return CompletableFuture.allOf(closeFutures); } private static class SessionTracker @@ -428,7 +429,8 @@ synchronized CompletableFuture shouldRetryOnNoMessageOrException() renewCancelTimer.cancel(true); if(onCloseEx != null) { - this.messageAndSessionPump.sessionHandler.notifyException(onCloseEx, ExceptionPhase.USERCALLBACK); + onCloseEx = Utils.extractAsyncCompletionCause(onCloseEx); + this.messageAndSessionPump.notifyExceptionToSessionHandler(onCloseEx, ExceptionPhase.USERCALLBACK); } this.sessionRenewLockLoop.cancelLoop(); @@ -436,9 +438,11 @@ synchronized CompletableFuture shouldRetryOnNoMessageOrException() { if(closeEx != null) { - this.messageAndSessionPump.sessionHandler.notifyException(closeEx, ExceptionPhase.SESSIONCLOSE); + closeEx = Utils.extractAsyncCompletionCause(closeEx); + this.messageAndSessionPump.notifyExceptionToSessionHandler(closeEx, ExceptionPhase.SESSIONCLOSE); } + this.messageAndSessionPump.openSessions.remove(this.session.getSessionId()); this.messageAndSessionPump.acceptSessionsAndPumpMessage(); return null; }); @@ -451,34 +455,6 @@ synchronized CompletableFuture shouldRetryOnNoMessageOrException() } } - private static class HandlerWrapper - { - private IMessageHandler messageHandler = null; - private ISessionHandler sessionHandler = null; - - HandlerWrapper(IMessageHandler messageHandler) - { - this.messageHandler = messageHandler; - } - - HandlerWrapper(ISessionHandler sessionHandler) - { - this.sessionHandler = sessionHandler; - } - - void notifyException(Throwable exception, ExceptionPhase phase) - { - if(this.messageHandler != null) - { - this.messageHandler.notifyException(exception, phase); - } - else - { - this.sessionHandler.notifyException(exception, phase); - } - } - } - private abstract static class RenewLockLoop { private boolean cancelled = false; @@ -523,24 +499,24 @@ protected static Duration getNextRenewInterval(Instant lockedUntilUtc) remainingTime = MessageAndSessionPump.MINIMUM_MESSAGE_LOCK_VALIDITY; } - Duration buffer = remainingTime.dividedBy(2).compareTo(MAXIMUM_RENEW_LOCK_BUFFER) > 0 ? MAXIMUM_RENEW_LOCK_BUFFER : remainingTime.dividedBy(2); - return remainingTime.minus(buffer); + Duration buffer = remainingTime.dividedBy(2).compareTo(MAXIMUM_RENEW_LOCK_BUFFER) > 0 ? MAXIMUM_RENEW_LOCK_BUFFER : remainingTime.dividedBy(2); + return remainingTime.minus(buffer); } } private static class MessgeRenewLockLoop extends RenewLockLoop { private IMessageReceiver innerReceiver; - private HandlerWrapper handlerWrapper; + private MessageAndSessionPump messageAndSessionPump; private IMessage message; private Instant stopRenewalAt; ScheduledFuture timerFuture; - MessgeRenewLockLoop(IMessageReceiver innerReceiver, HandlerWrapper handlerWrapper, IMessage message, Instant stopRenewalAt) + MessgeRenewLockLoop(IMessageReceiver innerReceiver, MessageAndSessionPump messageAndSessionPump, IMessage message, Instant stopRenewalAt) { super(); this.innerReceiver = innerReceiver; - this.handlerWrapper = handlerWrapper; + this.messageAndSessionPump = messageAndSessionPump; this.message = message; this.stopRenewalAt = stopRenewalAt; } @@ -564,7 +540,8 @@ protected void loop() { if(renewLockEx != null) { - this.handlerWrapper.notifyException(renewLockEx, ExceptionPhase.RENEWMESSAGELOCK); + renewLockEx = Utils.extractAsyncCompletionCause(renewLockEx); + this.messageAndSessionPump.notifyExceptionToMessageHandler(renewLockEx, ExceptionPhase.RENEWMESSAGELOCK); if(!(renewLockEx instanceof MessageLockLostException || renewLockEx instanceof OperationCancelledException)) { this.loop(); @@ -599,14 +576,14 @@ private Duration getNextRenewInterval() private static class SessionRenewLockLoop extends RenewLockLoop { private IMessageSession session; - private HandlerWrapper handlerWrapper; + private MessageAndSessionPump messageAndSessionPump; ScheduledFuture timerFuture; - SessionRenewLockLoop(IMessageSession session, HandlerWrapper handlerWrapper) + SessionRenewLockLoop(IMessageSession session, MessageAndSessionPump messageAndSessionPump) { super(); this.session = session; - this.handlerWrapper = handlerWrapper; + this.messageAndSessionPump = messageAndSessionPump; } @Override @@ -628,7 +605,9 @@ protected void loop() { if(renewLockEx != null) { - this.handlerWrapper.notifyException(renewLockEx, ExceptionPhase.RENEWSESSIONLOCK); + renewLockEx = Utils.extractAsyncCompletionCause(renewLockEx); + System.out.println(this.session.getSessionId() + "-" + renewLockEx.getMessage() + ":" + Instant.now()); + this.messageAndSessionPump.notifyExceptionToSessionHandler(renewLockEx, ExceptionPhase.RENEWSESSIONLOCK); if(!(renewLockEx instanceof SessionLockLostException || renewLockEx instanceof OperationCancelledException)) { this.loop(); @@ -761,4 +740,21 @@ private void checkInnerReceiveCreated() throw new UnsupportedOperationException("This operation is not supported on a message received from a session. Use the session to perform the operation."); } } + + // Don't notify handler if the pump is already closed + private void notifyExceptionToSessionHandler(Throwable ex, ExceptionPhase phase) + { + if(!(ex instanceof IllegalStateException && this.getIsClosingOrClosed())) + { + this.sessionHandler.notifyException(ex, phase); + } + } + + private void notifyExceptionToMessageHandler(Throwable ex, ExceptionPhase phase) + { + if(!(ex instanceof IllegalStateException && this.getIsClosingOrClosed())) + { + this.messageHandler.notifyException(ex, phase); + } + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java index afed9a9e5c18..51ad6fba29d6 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java @@ -1,6 +1,7 @@ package com.microsoft.azure.servicebus; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import com.microsoft.azure.servicebus.primitives.ServiceBusException; @@ -36,5 +37,17 @@ static void assertNonNull(String argumentName, Object argument) { if(argument == null) throw new IllegalArgumentException("Argument '" + argumentName +"' is null."); - } + } + + static Throwable extractAsyncCompletionCause(Throwable completionEx) + { + if(completionEx instanceof CompletionException || completionEx instanceof ExecutionException) + { + return completionEx.getCause(); + } + else + { + return completionEx; + } + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java index 98abd3d1dbb5..80f79b801b94 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java @@ -8,8 +8,6 @@ import java.util.Locale; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; - import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.transport.ErrorCondition; diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java index c41072bf1395..8c2d4787911c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java @@ -234,23 +234,15 @@ private void completeAllPendingRequestsWithException(Exception exception) public CompletableFuture requestAysnc(Message requestMessage, Duration timeout) { + this.throwIfClosed(null); CompletableFuture responseFuture = new CompletableFuture(); - - if(this.getIsClosingOrClosed()) - { - responseFuture.completeExceptionally(new ServiceBusException(false, "RequestResponseLink is closing or closed.")); - } - else - { - RequestResponseWorkItem workItem = new RequestResponseWorkItem(requestMessage, responseFuture, timeout); - String requestId = "request:" + this.requestCounter.incrementAndGet(); - requestMessage.setMessageId(requestId); - requestMessage.setReplyTo(this.replyTo); - this.pendingRequests.put(requestId, workItem); - workItem.setTimeoutTask(this.scheduleRequestTimeout(requestId, timeout)); - this.amqpSender.sendRequest(requestMessage, false); - } - + RequestResponseWorkItem workItem = new RequestResponseWorkItem(requestMessage, responseFuture, timeout); + String requestId = "request:" + this.requestCounter.incrementAndGet(); + requestMessage.setMessageId(requestId); + requestMessage.setReplyTo(this.replyTo); + this.pendingRequests.put(requestId, workItem); + workItem.setTimeoutTask(this.scheduleRequestTimeout(requestId, timeout)); + this.amqpSender.sendRequest(requestMessage, false); return responseFuture; } From 5d7dca6e398944db1f2bfd7758f5c5e2d2c6724e Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Tue, 25 Apr 2017 19:10:33 -0700 Subject: [PATCH 11/17] Adding prefetchcount getter and setters to queue client and subscription client. --- .../azure/servicebus/BrowsableMessageSession.java | 4 ++-- .../azure/servicebus/IMessageAndSessionPump.java | 4 ++++ .../microsoft/azure/servicebus/IMessageReceiver.java | 4 ++-- .../azure/servicebus/ISubscriptionClient.java | 2 +- .../azure/servicebus/MessageAndSessionPump.java | 12 ++++++++++++ .../microsoft/azure/servicebus/MessageReceiver.java | 4 ++-- .../com/microsoft/azure/servicebus/QueueClient.java | 10 ++++++++++ .../azure/servicebus/SubscriptionClient.java | 12 +++++++++++- 8 files changed, 44 insertions(+), 8 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java index 99d95dc8b780..fe633430e023 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java @@ -45,13 +45,13 @@ public Instant getLockedUntilUtc() { } @Override - public int getMessagePrefetchCount() + public int getPrefetchCount() { throw new UnsupportedOperationException(INVALID_OPERATION_ERROR_MESSAGE); } @Override - public void setMessagePrefetchCount(int prefetchCount) throws ServiceBusException + public void setPrefetchCount(int prefetchCount) throws ServiceBusException { throw new UnsupportedOperationException(INVALID_OPERATION_ERROR_MESSAGE); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java index b5a14d3f270b..5ef5befa3cd2 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java @@ -51,4 +51,8 @@ interface IMessageAndSessionPump CompletableFuture deadLetterAsync(UUID lockToken, String deadLetterReason, String deadLetterErrorDescription); CompletableFuture deadLetterAsync(UUID lockToken, String deadLetterReason, String deadLetterErrorDescription, Map propertiesToModify); + + int getPrefetchCount(); + + void setPrefetchCount(int prefetchCount) throws ServiceBusException; } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java index f6769c93546c..0e8af3f92b0e 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java @@ -84,7 +84,7 @@ public interface IMessageReceiver extends IMessageEntity, IMessageBrowser{ //Collection renewMessageLockBatch(Collection messages) throws InterruptedException, ServiceBusException; - int getMessagePrefetchCount(); + int getPrefetchCount(); - void setMessagePrefetchCount(int prefetchCount) throws ServiceBusException; + void setPrefetchCount(int prefetchCount) throws ServiceBusException; } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java index bdbc16ed6103..3f2057644b71 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java @@ -8,7 +8,7 @@ public interface ISubscriptionClient extends IMessageEntity, IMessageSessionEntity, IMessageAndSessionPump { - public ReceiveMode getReceiveMode(); + public ReceiveMode getReceiveMode(); public void addRule(RuleDescription ruleDescription) throws InterruptedException, ServiceBusException; public CompletableFuture addRuleAsync(RuleDescription ruleDescription); public void addRule(String ruleName, Filter filter) throws InterruptedException, ServiceBusException; diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java index ffd5864a5a6a..e1c544784601 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java @@ -757,4 +757,16 @@ private void notifyExceptionToMessageHandler(Throwable ex, ExceptionPhase phase) this.messageHandler.notifyException(ex, phase); } } + + @Override + public int getPrefetchCount() { + this.checkInnerReceiveCreated(); + return this.innerReceiver.getPrefetchCount(); + } + + @Override + public void setPrefetchCount(int prefetchCount) throws ServiceBusException { + this.checkInnerReceiveCreated(); + this.innerReceiver.setPrefetchCount(prefetchCount); + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java index 403126bdc03f..0c533d6199ad 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java @@ -444,13 +444,13 @@ protected CompletableFuture onClose() { } @Override - public int getMessagePrefetchCount() + public int getPrefetchCount() { return this.messagePrefetchCount; } @Override - public void setMessagePrefetchCount(int prefetchCount) throws ServiceBusException + public void setPrefetchCount(int prefetchCount) throws ServiceBusException { this.messagePrefetchCount = prefetchCount; if(this.isInitialized) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java index d49c0f067096..25b2768dfb69 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java @@ -249,4 +249,14 @@ public CompletableFuture deadLetterAsync(UUID lockToken, String deadLetter public CompletableFuture deadLetterAsync(UUID lockToken, String deadLetterReason, String deadLetterErrorDescription, Map propertiesToModify) { return this.messageAndSessionPump.deadLetterAsync(lockToken, deadLetterReason, deadLetterErrorDescription, propertiesToModify); } + + @Override + public int getPrefetchCount() { + return this.messageAndSessionPump.getPrefetchCount(); + } + + @Override + public void setPrefetchCount(int prefetchCount) throws ServiceBusException { + this.messageAndSessionPump.setPrefetchCount(prefetchCount); + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java index 709c749047fe..4a7ff2d70341 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java @@ -237,5 +237,15 @@ public CompletableFuture deadLetterAsync(UUID lockToken, String deadLetter @Override public CompletableFuture deadLetterAsync(UUID lockToken, String deadLetterReason, String deadLetterErrorDescription, Map propertiesToModify) { return this.messageAndSessionPump.deadLetterAsync(lockToken, deadLetterReason, deadLetterErrorDescription, propertiesToModify); - } + } + + @Override + public int getPrefetchCount() { + return this.messageAndSessionPump.getPrefetchCount(); + } + + @Override + public void setPrefetchCount(int prefetchCount) throws ServiceBusException { + this.messageAndSessionPump.setPrefetchCount(prefetchCount); + } } From b076fb30dd99f707283948daf60281c3ec770620 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Fri, 28 Apr 2017 18:47:01 -0700 Subject: [PATCH 12/17] Implementation of Claims Based authentication. --- .../servicebus/MessageAndSessionPump.java | 23 +-- .../com/microsoft/azure/servicebus/Utils.java | 13 -- .../azure/servicebus/amqp/AmqpConstants.java | 3 +- .../servicebus/amqp/ConnectionHandler.java | 10 +- .../primitives/ClientConstants.java | 12 ++ ...a => CommonRequestResponseOperations.java} | 28 ++- .../primitives/ConnectionStringBuilder.java | 178 +++++++++++++----- .../primitives/CoreMessageReceiver.java | 107 +++++++---- .../primitives/CoreMessageSender.java | 102 ++++++---- .../servicebus/primitives/ExceptionUtil.java | 17 +- .../primitives/MessagingFactory.java | 67 ++++++- .../MiscRequestResponseOperationHandler.java | 8 +- .../primitives/RequestResponseLink.java | 9 +- .../primitives/RequestResponseUtils.java | 47 +++-- .../azure/servicebus/primitives/SASUtil.java | 75 ++++++++ .../servicebus/primitives/StringUtil.java | 3 +- .../azure/servicebus/primitives/Util.java | 2 +- .../{ => primitives}/UtilsTests.java | 2 +- pom.xml | 1 - 19 files changed, 525 insertions(+), 182 deletions(-) rename azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/{MessageBrowserUtil.java => CommonRequestResponseOperations.java} (58%) create mode 100644 azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/SASUtil.java rename azure-servicebus/src/test/java/com/microsoft/azure/servicebus/{ => primitives}/UtilsTests.java (97%) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java index e1c544784601..081f5074a1b4 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java @@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; +import com.microsoft.azure.servicebus.primitives.ExceptionUtil; import com.microsoft.azure.servicebus.primitives.MessageLockLostException; import com.microsoft.azure.servicebus.primitives.MessagingFactory; import com.microsoft.azure.servicebus.primitives.OperationCancelledException; @@ -106,7 +107,7 @@ private void receiveAndPumpMessage() receiveMessageFuture.handleAsync((message, receiveEx) -> { if(receiveEx != null) { - receiveEx = Utils.extractAsyncCompletionCause(receiveEx); + receiveEx = ExceptionUtil.extractAsyncCompletionCause(receiveEx); this.notifyExceptionToMessageHandler(receiveEx, ExceptionPhase.RECEIVE); this.receiveAndPumpMessage(); } @@ -145,7 +146,7 @@ private void receiveAndPumpMessage() onMessageFuture.handleAsync((v, onMessageEx) -> { if(onMessageEx != null) { - onMessageEx = Utils.extractAsyncCompletionCause(onMessageEx); + onMessageEx = ExceptionUtil.extractAsyncCompletionCause(onMessageEx); this.notifyExceptionToMessageHandler(onMessageEx, ExceptionPhase.USERCALLBACK); } if(this.innerReceiver.getReceiveMode() == ReceiveMode.PeekLock) @@ -179,7 +180,7 @@ private void receiveAndPumpMessage() updateDispositionFuture.handleAsync((u, updateDispositionEx) -> { if(updateDispositionEx != null) { - updateDispositionEx = Utils.extractAsyncCompletionCause(updateDispositionEx); + updateDispositionEx = ExceptionUtil.extractAsyncCompletionCause(updateDispositionEx); this.notifyExceptionToMessageHandler(updateDispositionEx, dispositionPhase); } this.receiveAndPumpMessage(); @@ -210,7 +211,7 @@ private void acceptSessionsAndPumpMessage() acceptSessionFuture.handleAsync((session, acceptSessionEx) -> { if(acceptSessionEx != null) { - acceptSessionEx = Utils.extractAsyncCompletionCause(acceptSessionEx); + acceptSessionEx = ExceptionUtil.extractAsyncCompletionCause(acceptSessionEx); if(!(acceptSessionEx instanceof TimeoutException)) { @@ -253,7 +254,7 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) receiverFuture.handleAsync((message, receiveEx) -> { if(receiveEx != null) { - receiveEx = Utils.extractAsyncCompletionCause(receiveEx); + receiveEx = ExceptionUtil.extractAsyncCompletionCause(receiveEx); this.notifyExceptionToSessionHandler(receiveEx, ExceptionPhase.RECEIVE); sessionTracker.shouldRetryOnNoMessageOrException().thenAcceptAsync((shouldRetry) -> { if(shouldRetry) @@ -296,7 +297,7 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) renewCancelTimer.cancel(true); if(onMessageEx != null) { - onMessageEx = Utils.extractAsyncCompletionCause(onMessageEx); + onMessageEx = ExceptionUtil.extractAsyncCompletionCause(onMessageEx); this.notifyExceptionToSessionHandler(onMessageEx, ExceptionPhase.USERCALLBACK); } if(this.receiveMode == ReceiveMode.PeekLock) @@ -326,7 +327,7 @@ private void receiveFromSessionAndPumpMessage(SessionTracker sessionTracker) updateDispositionFuture.handleAsync((u, updateDispositionEx) -> { if(updateDispositionEx != null) { - updateDispositionEx = Utils.extractAsyncCompletionCause(updateDispositionEx); + updateDispositionEx = ExceptionUtil.extractAsyncCompletionCause(updateDispositionEx); this.notifyExceptionToSessionHandler(updateDispositionEx, dispositionPhase); } this.receiveFromSessionAndPumpMessage(sessionTracker); @@ -429,7 +430,7 @@ synchronized CompletableFuture shouldRetryOnNoMessageOrException() renewCancelTimer.cancel(true); if(onCloseEx != null) { - onCloseEx = Utils.extractAsyncCompletionCause(onCloseEx); + onCloseEx = ExceptionUtil.extractAsyncCompletionCause(onCloseEx); this.messageAndSessionPump.notifyExceptionToSessionHandler(onCloseEx, ExceptionPhase.USERCALLBACK); } @@ -438,7 +439,7 @@ synchronized CompletableFuture shouldRetryOnNoMessageOrException() { if(closeEx != null) { - closeEx = Utils.extractAsyncCompletionCause(closeEx); + closeEx = ExceptionUtil.extractAsyncCompletionCause(closeEx); this.messageAndSessionPump.notifyExceptionToSessionHandler(closeEx, ExceptionPhase.SESSIONCLOSE); } @@ -540,7 +541,7 @@ protected void loop() { if(renewLockEx != null) { - renewLockEx = Utils.extractAsyncCompletionCause(renewLockEx); + renewLockEx = ExceptionUtil.extractAsyncCompletionCause(renewLockEx); this.messageAndSessionPump.notifyExceptionToMessageHandler(renewLockEx, ExceptionPhase.RENEWMESSAGELOCK); if(!(renewLockEx instanceof MessageLockLostException || renewLockEx instanceof OperationCancelledException)) { @@ -605,7 +606,7 @@ protected void loop() { if(renewLockEx != null) { - renewLockEx = Utils.extractAsyncCompletionCause(renewLockEx); + renewLockEx = ExceptionUtil.extractAsyncCompletionCause(renewLockEx); System.out.println(this.session.getSessionId() + "-" + renewLockEx.getMessage() + ":" + Instant.now()); this.messageAndSessionPump.notifyExceptionToSessionHandler(renewLockEx, ExceptionPhase.RENEWSESSIONLOCK); if(!(renewLockEx instanceof SessionLockLostException || renewLockEx instanceof OperationCancelledException)) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java index 51ad6fba29d6..182ebdaf6a26 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Utils.java @@ -1,7 +1,6 @@ package com.microsoft.azure.servicebus; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import com.microsoft.azure.servicebus.primitives.ServiceBusException; @@ -38,16 +37,4 @@ static void assertNonNull(String argumentName, Object argument) if(argument == null) throw new IllegalArgumentException("Argument '" + argumentName +"' is null."); } - - static Throwable extractAsyncCompletionCause(Throwable completionEx) - { - if(completionEx instanceof CompletionException || completionEx instanceof ExecutionException) - { - return completionEx.getCause(); - } - else - { - return completionEx; - } - } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java index 0bad4f291c9d..c0001a54070f 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/AmqpConstants.java @@ -20,7 +20,8 @@ private AmqpConstants() { } public static final int MAX_FRAME_SIZE = 65536; - public static final String MANAGEMENT_ADDRESS_SEGMENT = "/$management"; + public static final String MANAGEMENT_NODE_ADDRESS_SEGMENT = "$management"; + public static final String CBS_NODE_ADDRESS_SEGMENT = "$cbs"; public static final Symbol PRODUCT = Symbol.valueOf("product"); public static final Symbol VERSION = Symbol.valueOf("version"); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java index a4e5056f0b31..7858d6ad0ede 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java @@ -30,17 +30,11 @@ public final class ConnectionHandler extends BaseHandler { private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - - private final String username; - private final String password; private final IAmqpConnection messagingFactory; - public ConnectionHandler(final IAmqpConnection messagingFactory, final String username, final String password) + public ConnectionHandler(final IAmqpConnection messagingFactory) { add(new Handshaker()); - - this.username = username; - this.password = password; this.messagingFactory = messagingFactory; } @@ -70,7 +64,7 @@ public void onConnectionBound(Event event) transport.ssl(domain); Sasl sasl = transport.sasl(); - sasl.plain(this.username, this.password); + sasl.setMechanisms("ANONYMOUS"); } @Override diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java index 17d21e13e4b6..01148c427b7a 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ClientConstants.java @@ -91,6 +91,9 @@ private ClientConstants() { } public static final String REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION = AmqpConstants.VENDOR + ":get-message-sessions"; public static final String REQUEST_RESPONSE_ADD_RULE_OPERATION = AmqpConstants.VENDOR + ":add-rule"; public static final String REQUEST_RESPONSE_REMOVE_RULE_OPERATION = AmqpConstants.VENDOR + ":remove-rule"; + public static final String REQUEST_RESPONSE_PUT_TOKEN_OPERATION = "put-token"; + public static final String REQUEST_RESPONSE_PUT_TOKEN_TYPE = "type"; + public static final String REQUEST_RESPONSE_PUT_TOKEN_AUDIENCE = "name"; public static final String REQUEST_RESPONSE_LOCKTOKENS = "lock-tokens"; public static final String REQUEST_RESPONSE_LOCKTOKEN = "lock-token"; public static final String REQUEST_RESPONSE_EXPIRATION = "expiration"; @@ -109,6 +112,10 @@ private ClientConstants() { } public static final String REQUEST_RESPONSE_STATUS_CODE = "statusCode"; public static final String REQUEST_RESPONSE_STATUS_DESCRIPTION = "statusDescription"; public static final String REQUEST_RESPONSE_ERROR_CONDITION = "errorCondition"; + // Legacy property names are used in CBS responses + public static final String REQUEST_RESPONSE_LEGACY_STATUS_CODE = "status-code"; + public static final String REQUEST_RESPONSE_LEGACY_STATUS_DESCRIPTION = "status-description"; + public static final String REQUEST_RESPONSE_LEGACY_ERROR_CONDITION = "error-condition"; public static final String REQUEST_RESPONSE_DISPOSITION_STATUS = "disposition-status"; public static final String REQUEST_RESPONSE_DEADLETTER_REASON = "deadletter-reason"; public static final String REQUEST_RESPONSE_DEADLETTER_DESCRIPTION = "deadletter-description"; @@ -140,11 +147,16 @@ private ClientConstants() { } // public static final String DISPOSITION_STATUS_UNLOCKED = "unlocked"; public static final int REQUEST_RESPONSE_OK_STATUS_CODE = 200; + public static final int REQUEST_RESPONSE_ACCEPTED_STATUS_CODE = 0xca; public static final int REQUEST_RESPONSE_NOCONTENT_STATUS_CODE = 0xcc; public static final int REQUEST_RESPONSE_NOTFOUND_STATUS_CODE = 0x194; public static final int REQUEST_RESPONSE_UNDEFINED_STATUS_CODE = -1; public static final int REQUEST_RESPONSE_SERVER_BUSY_STATUS_CODE = 0x1f7; + static final String SAS_TOKEN_TYPE = "servicebus.windows.net:sastoken"; + static final int DEFAULT_SAS_TOKEN_VALIDITY_IN_SECONDS = 20*60; // 20 minutes + static final String SAS_TOKEN_AUDIENCE_FORMAT = "amqp://%s/%s"; + private static String getPlatformInfo() { final Package javaRuntimeClassPkg = Runtime.class.getPackage(); final StringBuilder patformInfo = new StringBuilder(); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessageBrowserUtil.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CommonRequestResponseOperations.java similarity index 58% rename from azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessageBrowserUtil.java rename to azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CommonRequestResponseOperations.java index c47665f9ccbb..30bceb1bb89f 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessageBrowserUtil.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CommonRequestResponseOperations.java @@ -12,8 +12,8 @@ import org.apache.qpid.proton.amqp.messaging.AmqpValue; import org.apache.qpid.proton.message.Message; -final class MessageBrowserUtil { - public static CompletableFuture> peekMessagesAsync(RequestResponseLink requestResponseLink, Duration operationTimeout, long fromSequenceNumber, int messageCount, String sessionId) +final class CommonRequestResponseOperations { + static CompletableFuture> peekMessagesAsync(RequestResponseLink requestResponseLink, Duration operationTimeout, long fromSequenceNumber, int messageCount, String sessionId) { HashMap requestBodyMap = new HashMap(); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_FROM_SEQUENCE_NUMER, fromSequenceNumber); @@ -22,7 +22,7 @@ public static CompletableFuture> peekMessagesAsync(RequestRe { requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, sessionId); } - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_PEEK_OPERATION, requestBodyMap, Util.adjustServerTimeout(operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_PEEK_OPERATION, requestBodyMap, Util.adjustServerTimeout(operationTimeout)); CompletableFuture responseFuture = requestResponseLink.requestAysnc(requestMessage, operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture> returningFuture = new CompletableFuture>(); @@ -63,4 +63,26 @@ else if(statusCode == ClientConstants.REQUEST_RESPONSE_NOCONTENT_STATUS_CODE || return returningFuture; }); } + + static CompletableFuture sendCBSTokenAsync(RequestResponseLink requestResponseLink, Duration operationTimeout, String token, String tokenType, String tokenAudience) + { + Message requestMessage = RequestResponseUtils.createRequestMessageFromValueBody(ClientConstants.REQUEST_RESPONSE_PUT_TOKEN_OPERATION, token, Util.adjustServerTimeout(operationTimeout)); + requestMessage.getApplicationProperties().getValue().put(ClientConstants.REQUEST_RESPONSE_PUT_TOKEN_TYPE, tokenType); + requestMessage.getApplicationProperties().getValue().put(ClientConstants.REQUEST_RESPONSE_PUT_TOKEN_AUDIENCE, tokenAudience); + CompletableFuture responseFuture = requestResponseLink.requestAysnc(requestMessage, operationTimeout); + return responseFuture.thenComposeAsync((responseMessage) -> { + CompletableFuture returningFuture = new CompletableFuture(); + int statusCode = RequestResponseUtils.getResponseStatusCode(responseMessage); + if(statusCode == ClientConstants.REQUEST_RESPONSE_OK_STATUS_CODE || statusCode == ClientConstants.REQUEST_RESPONSE_ACCEPTED_STATUS_CODE) + { + returningFuture.complete(null); + } + else + { + // error response + returningFuture.completeExceptionally(RequestResponseUtils.genereateExceptionFromResponse(responseMessage)); + } + return returningFuture; + }); + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java index ed8282deb5b8..ff7b285c3eaf 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ConnectionStringBuilder.java @@ -35,21 +35,22 @@ */ public class ConnectionStringBuilder { - final static String END_POINT_FORMAT = "amqps://%s.servicebus.windows.net"; - final static String END_POINT_RAW_FORMAT = "amqps://%s"; - - final static String HOSTNAME_CONFIG_NAME = "Hostname"; - final static String ENDPOINT_CONFIG_NAME = "Endpoint"; - final static String SHARED_ACCESS_KEY_NAME_CONFIG_NAME = "SharedAccessKeyName"; - final static String SHARED_ACCESS_KEY_CONFIG_NAME = "SharedAccessKey"; - final static String ENTITY_PATH_CONFIG_NAME = "EntityPath"; - final static String OPERATION_TIMEOUT_CONFIG_NAME = "OperationTimeout"; - final static String RETRY_POLICY_CONFIG_NAME = "RetryPolicy"; - final static String KEY_VALUE_SEPARATOR = "="; - final static String KEY_VALUE_PAIR_DELIMITER = ";"; + private final static String END_POINT_FORMAT = "amqps://%s.servicebus.windows.net"; + private final static String END_POINT_RAW_FORMAT = "amqps://%s"; + + private final static String HOSTNAME_CONFIG_NAME = "Hostname"; + private final static String ENDPOINT_CONFIG_NAME = "Endpoint"; + private final static String SHARED_ACCESS_KEY_NAME_CONFIG_NAME = "SharedAccessKeyName"; + private final static String SHARED_ACCESS_KEY_CONFIG_NAME = "SharedAccessKey"; + private final static String SHARED_ACCESS_SIGNATURE_TOKEN_CONFIG_NAME = "SharedAccessSignatureToken"; + private final static String ENTITY_PATH_CONFIG_NAME = "EntityPath"; + private final static String OPERATION_TIMEOUT_CONFIG_NAME = "OperationTimeout"; + private final static String RETRY_POLICY_CONFIG_NAME = "RetryPolicy"; + private final static String KEY_VALUE_SEPARATOR = "="; + private final static String KEY_VALUE_PAIR_DELIMITER = ";"; private static final String ALL_KEY_ENUMERATE_REGEX = "(" + HOSTNAME_CONFIG_NAME + "|" + ENDPOINT_CONFIG_NAME + "|" + SHARED_ACCESS_KEY_NAME_CONFIG_NAME - + "|" + SHARED_ACCESS_KEY_CONFIG_NAME + "|" + ENTITY_PATH_CONFIG_NAME + "|" + OPERATION_TIMEOUT_CONFIG_NAME + + "|" + SHARED_ACCESS_KEY_CONFIG_NAME + "|" + SHARED_ACCESS_SIGNATURE_TOKEN_CONFIG_NAME + "|" + ENTITY_PATH_CONFIG_NAME + "|" + OPERATION_TIMEOUT_CONFIG_NAME + "|" + RETRY_POLICY_CONFIG_NAME + ")"; private static final String KEYS_WITH_DELIMITERS_REGEX = KEY_VALUE_PAIR_DELIMITER + ALL_KEY_ENUMERATE_REGEX + KEY_VALUE_SEPARATOR; @@ -58,99 +59,145 @@ public class ConnectionStringBuilder private URI endpoint; private String sharedAccessKeyName; private String sharedAccessKey; + private String sharedAccessSingatureToken; private String entityPath; private Duration operationTimeout; private RetryPolicy retryPolicy; private ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, + final URI endpointAddress, + final String entityPath, + final Duration operationTimeout, + final RetryPolicy retryPolicy) + { + this.endpoint = endpointAddress; + this.operationTimeout = operationTimeout; + this.retryPolicy = retryPolicy; + this.entityPath = entityPath; + } + + private ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, final String sharedAccessKeyName, final String sharedAccessKey, - final Duration operationTimeout, + final Duration operationTimeout, final RetryPolicy retryPolicy) { - this.endpoint = endpointAddress; + this(endpointAddress, entityPath, operationTimeout, retryPolicy); this.sharedAccessKey = sharedAccessKey; this.sharedAccessKeyName = sharedAccessKeyName; - this.operationTimeout = operationTimeout; - this.retryPolicy = retryPolicy; - this.entityPath = entityPath; } private ConnectionStringBuilder( - final String namespaceName, - final String entityPath, + final URI endpointAddress, + final String entityPath, + final String sharedAccessSingatureToken, + final Duration operationTimeout, + final RetryPolicy retryPolicy) + { + this(endpointAddress, entityPath, operationTimeout, retryPolicy); + this.sharedAccessSingatureToken = sharedAccessSingatureToken; + } + + private ConnectionStringBuilder( + final String namespaceName, + final String entityPath, final String sharedAccessKeyName, - final String sharedAccessKey, - final Duration operationTimeout, + final String sharedAccessKey, + final Duration operationTimeout, final RetryPolicy retryPolicy) { - try - { - this.endpoint = new URI(String.format(Locale.US, END_POINT_FORMAT, 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; + this(convertNamespaceToEndPointURI(namespaceName), entityPath, sharedAccessKeyName, sharedAccessKey, operationTimeout, retryPolicy); } + + private ConnectionStringBuilder( + final String namespaceName, + final String entityPath, + final String sharedAccessSingatureToken, + final Duration operationTimeout, + final RetryPolicy retryPolicy) + { + this(convertNamespaceToEndPointURI(namespaceName), entityPath, sharedAccessSingatureToken, operationTimeout, retryPolicy); + } /** * Build a connection 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 entityPath Entity path. For queue or topic, use name. For subscription use /subscriptions/. * @param sharedAccessKeyName Shared Access Key name * @param sharedAccessKey Shared Access Key */ public ConnectionStringBuilder( - final String namespaceName, - final String entityPath, + 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 + * @param namespaceName Namespace name (dns suffix - ex: .servicebus.windows.net is not required) + * @param entityPath Entity path. For queue or topic, use name. For subscription use /subscriptions/. + * @param sharedAccessSingature Shared Access Signature already generated + */ + public ConnectionStringBuilder( + final String namespaceName, + final String entityPath, + final String sharedAccessSingatureToken) + { + this(namespaceName, entityPath, sharedAccessSingatureToken, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); + } + /** * Build a connection 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 entityPath Entity path. For queue or topic, use name. For subscription use /subscriptions/. * @param sharedAccessKeyName Shared Access Key name * @param sharedAccessKey Shared Access Key */ public ConnectionStringBuilder( - final URI endpointAddress, - final String entityPath, + 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 + * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName + * @param entityPath Entity path. For queue or topic, use name. For subscription use /subscriptions/. + * @param sharedAccessSingature Shared Access Signature already generated + */ + public ConnectionStringBuilder( + final URI endpointAddress, + final String entityPath, + final String sharedAccessSingature) + { + this(endpointAddress, entityPath, sharedAccessSingature, MessagingFactory.DefaultOperationTimeout, RetryPolicy.getDefault()); + } /** * ConnectionString format: * Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY + * or Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessSignatureToken=SHARED_ACCESS_SIGNATURE_TOKEN * @param connectionString ServiceBus ConnectionString * @throws IllegalConnectionStringFormatException when the format of the ConnectionString is not valid */ public ConnectionStringBuilder(String connectionString) { - this.parseConnectionString(connectionString); + this.parseConnectionString(connectionString); } /** * ConnectionString format: * Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY + * or Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessSignatureToken=SHARED_ACCESS_SIGNATURE_TOKEN * @param namespaceConnectionString connections string of the ServiceBus namespace. This doesn't include the entity path. * @param entityPath path to the entity within the namespace */ @@ -170,7 +217,7 @@ public URI getEndpoint() } /** - * Get the shared access policy key value from the connection string + * Get the shared access policy key value from the connection string or null. * @return Shared Access Signature key */ public String getSasKey() @@ -179,13 +226,22 @@ public String getSasKey() } /** - * Get the shared access policy owner name from the connection string + * Get the shared access policy owner name from the connection string or null. * @return Shared Access Signature key name. */ public String getSasKeyName() { return this.sharedAccessKeyName; } + + /** + * Returns the shared access signature token from the connection string or null. + * @return Shared Access Signature Token + */ + public String getSharedAccessSignatureToken() + { + return this.sharedAccessSingatureToken; + } /** * Get the entity path value from the connection string @@ -267,6 +323,12 @@ public String toString() connectionStringBuilder.append(String.format(Locale.US, "%s%s%s", SHARED_ACCESS_KEY_CONFIG_NAME, KEY_VALUE_SEPARATOR, this.sharedAccessKey)); } + + if (!StringUtil.isNullOrWhiteSpace(this.sharedAccessSingatureToken)) + { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s", SHARED_ACCESS_SIGNATURE_TOKEN_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.sharedAccessSingatureToken)); + } if (this.operationTimeout != null) { @@ -285,6 +347,20 @@ public String toString() return this.connectionString; } + + private static URI convertNamespaceToEndPointURI(String namespaceName) + { + try + { + return new URI(String.format(Locale.US, END_POINT_FORMAT, namespaceName)); + } + catch(URISyntaxException exception) + { + throw new IllegalConnectionStringFormatException( + String.format(Locale.US, "Invalid namespace name: %s", namespaceName), + exception); + } + } private void parseConnectionString(String connectionString) { @@ -373,6 +449,10 @@ else if(key.equalsIgnoreCase(SHARED_ACCESS_KEY_CONFIG_NAME)) { this.sharedAccessKey = values[valueIndex]; } + else if(key.equalsIgnoreCase(SHARED_ACCESS_SIGNATURE_TOKEN_CONFIG_NAME)) + { + this.sharedAccessSingatureToken = values[valueIndex]; + } else if (key.equalsIgnoreCase(ENTITY_PATH_CONFIG_NAME)) { this.entityPath = values[valueIndex]; diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java index 016781ad4a40..f68048bc7d9b 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java @@ -23,11 +23,20 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.apache.qpid.proton.Proton; +import org.apache.qpid.proton.amqp.Binary; +import org.apache.qpid.proton.amqp.UnsignedInteger; +import org.apache.qpid.proton.amqp.messaging.Accepted; +import org.apache.qpid.proton.amqp.messaging.AmqpValue; +import org.apache.qpid.proton.amqp.messaging.Modified; +import org.apache.qpid.proton.amqp.messaging.Outcome; +import org.apache.qpid.proton.amqp.messaging.Rejected; +import org.apache.qpid.proton.amqp.messaging.Released; import org.apache.qpid.proton.amqp.messaging.Source; import org.apache.qpid.proton.amqp.messaging.Target; import org.apache.qpid.proton.amqp.transport.DeliveryState; @@ -41,21 +50,11 @@ import org.apache.qpid.proton.engine.Receiver; import org.apache.qpid.proton.engine.Session; import org.apache.qpid.proton.message.Message; -import org.apache.qpid.proton.amqp.Binary; -import org.apache.qpid.proton.amqp.UnsignedInteger; -import org.apache.qpid.proton.amqp.messaging.Accepted; -import org.apache.qpid.proton.amqp.messaging.AmqpValue; -import org.apache.qpid.proton.amqp.messaging.Modified; -import org.apache.qpid.proton.amqp.messaging.Outcome; -import org.apache.qpid.proton.amqp.messaging.Rejected; -import org.apache.qpid.proton.amqp.messaging.Released; -import com.microsoft.azure.servicebus.amqp.AmqpConstants; import com.microsoft.azure.servicebus.amqp.DispatchHandler; import com.microsoft.azure.servicebus.amqp.IAmqpReceiver; import com.microsoft.azure.servicebus.amqp.ReceiveLinkHandler; import com.microsoft.azure.servicebus.amqp.SessionHandler; -import com.microsoft.azure.servicebus.rules.RuleDescription; /** * Common Receiver that abstracts all amqp related details @@ -73,6 +72,7 @@ public class CoreMessageReceiver extends ClientEntity implements IAmqpReceiver, private final ConcurrentHashMap tagsToDeliveriesMap; private final MessagingFactory underlyingFactory; private final String receivePath; + private final String sasTokenAudienceURI; private final Duration operationTimeout; private final CompletableFuture linkClose; private final Object prefetchCountSync; @@ -93,6 +93,7 @@ public class CoreMessageReceiver extends ClientEntity implements IAmqpReceiver, private Exception lastKnownLinkError; private Instant lastKnownErrorReportedAt; private int nextCreditToFlow; + private ScheduledFuture sasTokenRenewTimerFuture; private final Runnable timedOutUpdateStateRequestsDaemon; @@ -111,6 +112,7 @@ private CoreMessageReceiver(final MessagingFactory factory, this.underlyingFactory = factory; this.operationTimeout = factory.getOperationTimeout(); this.receivePath = recvPath; + this.sasTokenAudienceURI = String.format(ClientConstants.SAS_TOKEN_AUDIENCE_FORMAT, factory.getHostName(), recvPath); this.sessionId = sessionId; this.isSessionReceiver = false; this.isBrowsableSession = false; @@ -194,22 +196,34 @@ public static CompletableFuture create( private CompletableFuture createLink() { this.linkOpen = new WorkItem(new CompletableFuture(), this.operationTimeout); - this.scheduleLinkOpenTimeout(this.linkOpen.getTimeoutTracker()); - try - { - this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - CoreMessageReceiver.this.createReceiveLink(); - } - }); - } - catch (IOException ioException) - { - this.linkOpen.getWork().completeExceptionally(new ServiceBusException(false, "Failed to create Receiver, see cause for more details.", ioException)); - } + this.scheduleLinkOpenTimeout(this.linkOpen.getTimeoutTracker()); + this.sendSASTokenAndSetRenewTimer().handleAsync((v, sasTokenEx) -> { + if(sasTokenEx != null) + { + this.linkOpen.getWork().completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(sasTokenEx)); + } + else + { + try + { + this.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() + { + @Override + public void onEvent() + { + CoreMessageReceiver.this.createReceiveLink(); + } + }); + } + catch (IOException ioException) + { + this.cancelSASTokenRenewTimer(); + this.linkOpen.getWork().completeExceptionally(new ServiceBusException(false, "Failed to create Receiver, see cause for more details.", ioException)); + } + } + + return null; + }); return this.linkOpen.getWork(); } @@ -220,7 +234,7 @@ private CompletableFuture createRequestResponseLink() { if(this.requestResponseLink == null) { - String requestResponseLinkPath = RequestResponseLink.getRequestResponseLinkPath(this.receivePath); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.receivePath); CompletableFuture crateAndAssignRequestResponseLink = RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); return crateAndAssignRequestResponseLink; @@ -283,6 +297,27 @@ private void createReceiveLink() this.receiveLink = receiver; } + + CompletableFuture sendSASTokenAndSetRenewTimer() + { + if(this.getIsClosingOrClosed()) + { + return CompletableFuture.completedFuture(null); + } + else + { + CompletableFuture> sendTokenFuture = this.underlyingFactory.sendSASTokenAndSetRenewTimer(this.sasTokenAudienceURI, () -> this.sendSASTokenAndSetRenewTimer()); + return sendTokenFuture.thenAccept((f) -> {this.sasTokenRenewTimerFuture = f;}); + } + } + + private void cancelSASTokenRenewTimer() + { + if(this.sasTokenRenewTimerFuture != null && !this.sasTokenRenewTimerFuture.isDone()) + { + this.sasTokenRenewTimerFuture.cancel(true); + } + } private List receiveCore(final int messageCount) { @@ -501,6 +536,7 @@ public void onOpenComplete(Exception exception) { if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) { + this.cancelSASTokenRenewTimer(); this.setClosed(); ExceptionUtil.completeExceptionally(this.linkOpen.getWork(), exception, this, true); } @@ -744,6 +780,7 @@ public void run() { if (!linkOpen.getWork().isDone()) { + CoreMessageReceiver.this.cancelSASTokenRenewTimer(); Exception operationTimedout = new TimeoutException( String.format(Locale.US, "%s operation on ReceiveLink(%s) to path(%s) timed out at %s.", "Open", CoreMessageReceiver.this.receiveLink.getName(), CoreMessageReceiver.this.receivePath, ZonedDateTime.now()), CoreMessageReceiver.this.lastKnownLinkError); @@ -850,6 +887,8 @@ public void onEvent() { } } + this.cancelSASTokenRenewTimer(); + return this.linkClose.thenCompose((v) -> { return this.requestResponseLink == null ? CompletableFuture.completedFuture(null) : this.requestResponseLink.closeAsync();}); } @@ -1038,7 +1077,7 @@ public CompletableFuture> renewMessageLocksAsync(UUID[] lock requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); } - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_RENEWLOCK_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_RENEWLOCK_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture> returningFuture = new CompletableFuture>(); @@ -1069,7 +1108,7 @@ public CompletableFuture> receiveBySequenceNumb requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); } - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture> returningFuture = new CompletableFuture>(); @@ -1140,7 +1179,7 @@ public CompletableFuture updateDispositionAsync(UUID[] lockTokens, String requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); } - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -1165,7 +1204,7 @@ public CompletableFuture renewSessionLocksAsync() HashMap requestBodyMap = new HashMap(); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_RENEW_SESSIONLOCK_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_RENEW_SESSIONLOCK_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -1192,7 +1231,7 @@ public CompletableFuture getSessionStateAsync() HashMap requestBodyMap = new HashMap(); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -1230,7 +1269,7 @@ public CompletableFuture setSessionStateAsync(byte[] sessionState) requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSIONID, this.getSessionId()); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SESSION_STATE, sessionState == null ? null : new Binary(sessionState)); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.operationTimeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.operationTimeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -1253,7 +1292,7 @@ public CompletableFuture setSessionStateAsync(byte[] sessionState) public CompletableFuture> peekMessagesAsync(long fromSequenceNumber, int messageCount, String sessionId) { return this.createRequestResponseLink().thenComposeAsync((v) -> { - return MessageBrowserUtil.peekMessagesAsync(this.requestResponseLink, this.operationTimeout, fromSequenceNumber, messageCount, sessionId); + return CommonRequestResponseOperations.peekMessagesAsync(this.requestResponseLink, this.operationTimeout, fromSequenceNumber, messageCount, sessionId); }); } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java index 64a4fadf2849..1f6711b159c4 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java @@ -5,37 +5,29 @@ package com.microsoft.azure.servicebus.primitives; import java.io.IOException; -import java.nio.BufferOverflowException; import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; -import java.util.UUID; import java.util.PriorityQueue; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; - -import javax.xml.ws.handler.MessageContext; import org.apache.qpid.proton.Proton; import org.apache.qpid.proton.amqp.Binary; import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.messaging.Accepted; -import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; import org.apache.qpid.proton.amqp.messaging.Data; -import org.apache.qpid.proton.amqp.messaging.MessageAnnotations; import org.apache.qpid.proton.amqp.messaging.Rejected; import org.apache.qpid.proton.amqp.messaging.Released; import org.apache.qpid.proton.amqp.messaging.Source; @@ -52,7 +44,6 @@ import org.apache.qpid.proton.engine.impl.DeliveryImpl; import org.apache.qpid.proton.message.Message; -import com.microsoft.azure.servicebus.IMessage; import com.microsoft.azure.servicebus.amqp.AmqpConstants; import com.microsoft.azure.servicebus.amqp.DispatchHandler; import com.microsoft.azure.servicebus.amqp.IAmqpSender; @@ -71,6 +62,7 @@ public class CoreMessageSender extends ClientEntity implements IAmqpSender, IErr private final Object requestResonseLinkCreationLock = new Object(); private final MessagingFactory underlyingFactory; private final String sendPath; + private final String sasTokenAudienceURI; private final Duration operationTimeout; private final RetryPolicy retryPolicy; private final CompletableFuture linkClose; @@ -83,9 +75,9 @@ public class CoreMessageSender extends ClientEntity implements IAmqpSender, IErr private RequestResponseLink requestResponseLink; private CompletableFuture linkFirstOpen; private int linkCredit; - private TimeoutTracker openLinkTracker; private Exception lastKnownLinkError; private Instant lastKnownErrorReportedAt; + private ScheduledFuture sasTokenRenewTimerFuture; public static CompletableFuture create( final MessagingFactory factory, @@ -93,24 +85,37 @@ public static CompletableFuture create( final String senderPath) { final CoreMessageSender msgSender = new CoreMessageSender(factory, sendLinkName, senderPath); - msgSender.openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout()); - msgSender.initializeLinkOpen(msgSender.openLinkTracker); + TimeoutTracker openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout()); + msgSender.initializeLinkOpen(openLinkTracker); + + msgSender.sendSASTokenAndSetRenewTimer().handleAsync((v, sasTokenEx) -> { + if(sasTokenEx != null) + { + msgSender.linkFirstOpen.completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(sasTokenEx)); + } + else + { + try + { + msgSender.underlyingFactory.scheduleOnReactorThread(new DispatchHandler() + { + @Override + public void onEvent() + { + msgSender.createSendLink(); + } + }); + } + catch (IOException ioException) + { + msgSender.cancelSASTokenRenewTimer(); + msgSender.linkFirstOpen.completeExceptionally(new ServiceBusException(false, "Failed to create Sender, see cause for more details.", ioException)); + } + } + + return null; + }); - 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; } @@ -120,7 +125,7 @@ private CompletableFuture createRequestResponseLink() synchronized (this.requestResonseLinkCreationLock) { if(this.requestResponseLink == null) { - String requestResponseLinkPath = RequestResponseLink.getRequestResponseLinkPath(this.sendPath); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.sendPath); CompletableFuture crateAndAssignRequestResponseLink = RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); return crateAndAssignRequestResponseLink; @@ -137,6 +142,7 @@ private CoreMessageSender(final MessagingFactory factory, final String sendLinkN super(sendLinkName, factory); this.sendPath = senderPath; + this.sasTokenAudienceURI = String.format(ClientConstants.SAS_TOKEN_AUDIENCE_FORMAT, factory.getHostName(), senderPath); this.underlyingFactory = factory; this.operationTimeout = factory.getOperationTimeout(); @@ -310,8 +316,6 @@ public void onOpenComplete(Exception completionException) { if (completionException == null) { - this.openLinkTracker = null; - this.lastKnownLinkError = null; this.retryPolicy.resetRetryCount(this.getClientId()); @@ -351,6 +355,7 @@ public void onOpenComplete(Exception completionException) if (!this.linkFirstOpen.isDone()) { this.setClosed(); + this.cancelSASTokenRenewTimer(); ExceptionUtil.completeExceptionally(this.linkFirstOpen, completionException, this, true); } } @@ -430,7 +435,7 @@ public void onEvent() { if (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED) { - createSendLink(); + CoreMessageSender.this.recreateSendLink(); } } }); @@ -550,7 +555,6 @@ private void cleanupFailedSend(final SendWorkItem failedSend, final Except private void createSendLink() { final Connection connection = this.underlyingFactory.getConnection(); - final Session session = connection.session(); session.setOutgoingWindow(Integer.MAX_VALUE); session.open(); @@ -589,7 +593,28 @@ private void createSendLink() this.sendLink = sender; } - + + CompletableFuture sendSASTokenAndSetRenewTimer() + { + if(this.getIsClosingOrClosed()) + { + return CompletableFuture.completedFuture(null); + } + else + { + CompletableFuture> sendTokenFuture = this.underlyingFactory.sendSASTokenAndSetRenewTimer(this.sasTokenAudienceURI, () -> this.sendSASTokenAndSetRenewTimer()); + return sendTokenFuture.thenAccept((f) -> {this.sasTokenRenewTimerFuture = f;}); + } + } + + private void cancelSASTokenRenewTimer() + { + if(this.sasTokenRenewTimerFuture != null && !this.sasTokenRenewTimerFuture.isDone()) + { + this.sasTokenRenewTimerFuture.cancel(true); + } + } + // TODO: consolidate common-code written for timeouts in Sender/Receiver private void initializeLinkOpen(TimeoutTracker timeout) { @@ -603,6 +628,7 @@ public void run() { if (!CoreMessageSender.this.linkFirstOpen.isDone()) { + CoreMessageSender.this.cancelSASTokenRenewTimer(); Exception operationTimedout = new TimeoutException( String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", CoreMessageSender.this.sendLink.getName(), CoreMessageSender.this.getSendPath(), ZonedDateTime.now().toString()), CoreMessageSender.this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? CoreMessageSender.this.lastKnownLinkError : null); @@ -848,6 +874,8 @@ else if (this.sendLink == null || this.sendLink.getRemoteState() == EndpointStat } } + this.cancelSASTokenRenewTimer(); + return this.linkClose.thenCompose((v) -> { return this.requestResponseLink == null ? CompletableFuture.completedFuture(null) : this.requestResponseLink.closeAsync();}); } @@ -922,7 +950,7 @@ public CompletableFuture scheduleMessageAsync(Message[] messages, Durati messageList.add(messageEntry); } requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_MESSAGES, messageList); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, requestBodyMap, Util.adjustServerTimeout(timeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, requestBodyMap, Util.adjustServerTimeout(timeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, timeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -948,7 +976,7 @@ public CompletableFuture cancelScheduledMessageAsync(Long[] sequenceNumber HashMap requestBodyMap = new HashMap(); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_SEQUENCE_NUMBERS, sequenceNumbers); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_CANCEL_CHEDULE_MESSAGE_OPERATION, requestBodyMap, Util.adjustServerTimeout(timeout)); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_CANCEL_CHEDULE_MESSAGE_OPERATION, requestBodyMap, Util.adjustServerTimeout(timeout)); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, timeout); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -972,7 +1000,7 @@ public CompletableFuture> peekMessagesAsync(long fromSequenc { return this.createRequestResponseLink().thenComposeAsync((v) -> { - return MessageBrowserUtil.peekMessagesAsync(this.requestResponseLink, this.operationTimeout, fromSequenceNumber, messageCount, null); + return CommonRequestResponseOperations.peekMessagesAsync(this.requestResponseLink, this.operationTimeout, fromSequenceNumber, messageCount, null); }); } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java index 80f79b801b94..1e04a2a49a28 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/ExceptionUtil.java @@ -8,13 +8,16 @@ import java.util.Locale; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.transport.ErrorCondition; import com.microsoft.azure.servicebus.amqp.AmqpErrorCode; import com.microsoft.azure.servicebus.amqp.AmqpException; -final class ExceptionUtil +public final class ExceptionUtil { static Exception toException(ErrorCondition errorCondition) { @@ -173,4 +176,16 @@ static String toStackTraceString(final Throwable exception, final String customE return builder.toString(); } + + public static Throwable extractAsyncCompletionCause(Throwable completionEx) + { + if(completionEx instanceof CompletionException || completionEx instanceof ExecutionException) + { + return completionEx.getCause(); + } + else + { + return completionEx; + } + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java index 1a7234f3e024..d46be6fc7d16 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java @@ -6,11 +6,13 @@ import java.io.IOException; import java.nio.channels.UnresolvedAddressException; +import java.security.InvalidKeyException; import java.time.Duration; import java.util.LinkedList; import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,6 +43,8 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I public static final Duration DefaultOperationTimeout = Duration.ofSeconds(30); private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private final Object requestResponseLinkCreationLock = new Object(); + private final ConnectionStringBuilder builder; private final String hostName; private final CompletableFuture closeTask; private final ConnectionHandler connectionHandler; @@ -56,7 +60,8 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I private RetryPolicy retryPolicy; private CompletableFuture open; private CompletableFuture openConnection; - + private RequestResponseLink cbsLink; + /** * @param reactor parameter reactor is purely for testing purposes and the SDK code should always set it to null */ @@ -65,6 +70,7 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I super("MessagingFactory".concat(StringUtil.getShortRandomString()), null); Timer.register(this.getClientId()); + this.builder = builder; this.hostName = builder.getEndpoint().getHost(); this.operationTimeout = builder.getOperationTimeout(); @@ -72,7 +78,7 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I this.registeredLinks = new LinkedList(); this.closeTask = new CompletableFuture(); this.reactorLock = new Object(); - this.connectionHandler = new ConnectionHandler(this, builder.getSasKeyName(), builder.getSasKey()); + this.connectionHandler = new ConnectionHandler(this); this.open = new CompletableFuture(); this.openConnection = new CompletableFuture(); @@ -408,5 +414,60 @@ public void scheduleOnReactorThread(final DispatchHandler handler) throws IOExce public void scheduleOnReactorThread(final int delay, final DispatchHandler handler) throws IOException { this.getReactorScheduler().invoke(delay, handler); - } + } + + CompletableFuture> sendSASTokenAndSetRenewTimer(String sasTokenAudienceURI, Runnable validityRenewer) + { + boolean isSasTokenGenerated = false; + String sasToken = this.builder.getSharedAccessSignatureToken(); + try + { + if(sasToken == null) + { + sasToken = SASUtil.generateSharedAccessSignatureToken(builder.getSasKeyName(), builder.getSasKey(), sasTokenAudienceURI, ClientConstants.DEFAULT_SAS_TOKEN_VALIDITY_IN_SECONDS);; + isSasTokenGenerated = true; + } + } catch (InvalidKeyException e) { + CompletableFuture> exceptionFuture = new CompletableFuture<>(); + exceptionFuture.completeExceptionally(e); + return exceptionFuture; + } + + final String finalSasToken = sasToken; + final boolean finalIsSasTokenGenerated = isSasTokenGenerated; + + CompletableFuture sendTokenFuture = this.createCBSLink().thenComposeAsync((v) -> { + return CommonRequestResponseOperations.sendCBSTokenAsync(this.cbsLink, Util.adjustServerTimeout(this.operationTimeout), finalSasToken, ClientConstants.SAS_TOKEN_TYPE, sasTokenAudienceURI); + }); + return sendTokenFuture.thenApplyAsync((v) -> { + if(finalIsSasTokenGenerated) + { + // It will eventually expire. Renew it + int renewInterval = SASUtil.getCBSTokenRenewIntervalInSeconds(ClientConstants.DEFAULT_SAS_TOKEN_VALIDITY_IN_SECONDS); + return Timer.schedule(validityRenewer, Duration.ofSeconds(renewInterval), TimerType.OneTimeRun); + } + else + { + // User provided signature. We can't renew it. + return null; + } + }); + } + + private CompletableFuture createCBSLink() + { + synchronized (this.requestResponseLinkCreationLock) { + if(this.cbsLink == null) + { + String requestResponseLinkPath = RequestResponseLink.getCBSNodeLinkPath(); + CompletableFuture crateAndAssignRequestResponseLink = + RequestResponseLink.createAsync(this, this.getClientId() + "-cbs", requestResponseLinkPath).thenAccept((rrlink) -> {this.cbsLink = rrlink;}); + return crateAndAssignRequestResponseLink; + } + else + { + return CompletableFuture.completedFuture(null); + } + } + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java index 1eedee015e85..e4aae24035ee 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java @@ -42,7 +42,7 @@ private CompletableFuture createRequestResponseLink() synchronized (this.requestResonseLinkCreationLock) { if(this.requestResponseLink == null) { - String requestResponseLinkPath = RequestResponseLink.getRequestResponseLinkPath(this.entityPath); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.entityPath); CompletableFuture crateAndAssignRequestResponseLink = RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); return crateAndAssignRequestResponseLink; @@ -66,7 +66,7 @@ public CompletableFuture> getMessageSessionsAsync(Date l requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_LAST_SESSION_ID, lastSessionId); } - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_GET_MESSAGE_SESSIONS_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.underlyingFactory.getOperationTimeout()); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture> returningFuture = new CompletableFuture>(); @@ -99,7 +99,7 @@ public CompletableFuture removeRuleAsync(String ruleName) HashMap requestBodyMap = new HashMap(); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_RULENAME, ruleName); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_REMOVE_RULE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_REMOVE_RULE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.underlyingFactory.getOperationTimeout()); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); @@ -125,7 +125,7 @@ public CompletableFuture addRuleAsync(RuleDescription ruleDescription) requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_RULENAME, ruleDescription.getName()); requestBodyMap.put(ClientConstants.REQUEST_RESPONSE_RULEDESCRIPTION, RequestResponseUtils.encodeRuleDescriptionToMap(ruleDescription)); - Message requestMessage = RequestResponseUtils.createRequestMessage(ClientConstants.REQUEST_RESPONSE_ADD_RULE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); + Message requestMessage = RequestResponseUtils.createRequestMessageFromPropertyBag(ClientConstants.REQUEST_RESPONSE_ADD_RULE_OPERATION, requestBodyMap, Util.adjustServerTimeout(this.underlyingFactory.getOperationTimeout())); CompletableFuture responseFuture = this.requestResponseLink.requestAysnc(requestMessage, this.underlyingFactory.getOperationTimeout()); return responseFuture.thenComposeAsync((responseMessage) -> { CompletableFuture returningFuture = new CompletableFuture(); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java index 8c2d4787911c..b30d20a1b4e1 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseLink.java @@ -105,11 +105,16 @@ public void onEvent() return requestReponseLink.createFuture; } - public static String getRequestResponseLinkPath(String entityPath) + public static String getManagementNodeLinkPath(String entityPath) { - return entityPath + AmqpConstants.MANAGEMENT_ADDRESS_SEGMENT; + return String.format("%s/%s", entityPath, AmqpConstants.MANAGEMENT_NODE_ADDRESS_SEGMENT); } + public static String getCBSNodeLinkPath() + { + return AmqpConstants.CBS_NODE_ADDRESS_SEGMENT; + } + private RequestResponseLink(MessagingFactory messagingFactory, String linkName, String linkPath) { super(linkName, null); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseUtils.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseUtils.java index 317f74983bd6..4d5d4cc83fd6 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseUtils.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/RequestResponseUtils.java @@ -10,28 +10,36 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.message.Message; -import com.microsoft.azure.servicebus.amqp.AmqpErrorCode; import com.microsoft.azure.servicebus.rules.CorrelationFilter; import com.microsoft.azure.servicebus.rules.RuleDescription; import com.microsoft.azure.servicebus.rules.SqlFilter; import com.microsoft.azure.servicebus.rules.SqlRuleAction; public class RequestResponseUtils { - public static Message createRequestMessage(String operation, Map propertyBag, Duration timeout) + public static Message createRequestMessageFromPropertyBag(String operation, Map propertyBag, Duration timeout) { - Message requestMessage = Message.Factory.create(); - requestMessage.setBody(new AmqpValue(propertyBag)); - HashMap applicationPropertiesMap = new HashMap(); - applicationPropertiesMap.put(ClientConstants.REQUEST_RESPONSE_OPERATION_NAME, operation); - applicationPropertiesMap.put(ClientConstants.REQUEST_RESPONSE_TIMEOUT, timeout.toMillis()); - requestMessage.setApplicationProperties(new ApplicationProperties(applicationPropertiesMap)); - return requestMessage; + return createRequestMessageFromValueBody(operation, propertyBag, timeout); } + public static Message createRequestMessageFromValueBody(String operation, Object valueBody, Duration timeout) + { + Message requestMessage = Message.Factory.create(); + requestMessage.setBody(new AmqpValue(valueBody)); + HashMap applicationPropertiesMap = new HashMap(); + applicationPropertiesMap.put(ClientConstants.REQUEST_RESPONSE_OPERATION_NAME, operation); + applicationPropertiesMap.put(ClientConstants.REQUEST_RESPONSE_TIMEOUT, timeout.toMillis()); + requestMessage.setApplicationProperties(new ApplicationProperties(applicationPropertiesMap)); + return requestMessage; + } + public static int getResponseStatusCode(Message responseMessage) { int statusCode = ClientConstants.REQUEST_RESPONSE_UNDEFINED_STATUS_CODE; Object codeObject = responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_STATUS_CODE); + if(codeObject == null) + { + codeObject = responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_LEGACY_STATUS_CODE); + } if(codeObject != null) { statusCode = (int)codeObject; @@ -42,9 +50,24 @@ public static int getResponseStatusCode(Message responseMessage) public static Symbol getResponseErrorCondition(Message responseMessage) { - return (Symbol)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_ERROR_CONDITION); + Symbol errorCondition = (Symbol)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_ERROR_CONDITION); + if(errorCondition == null) + { + errorCondition = (Symbol)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_LEGACY_ERROR_CONDITION); + } + return errorCondition; } + public static String getResponseStatusDescription(Message responseMessage) + { + String statusDescription = (String)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_STATUS_DESCRIPTION); + if(statusDescription == null) + { + statusDescription = (String)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_LEGACY_STATUS_DESCRIPTION); + } + return statusDescription; + } + public static Map getResponseBody(Message responseMessage) { return (Map)((AmqpValue)responseMessage.getBody()).getValue(); @@ -52,8 +75,8 @@ public static Map getResponseBody(Message responseMessage) public static Exception genereateExceptionFromResponse(Message responseMessage) { - Symbol errorCondition = (Symbol)responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_ERROR_CONDITION); - Object statusDescription = responseMessage.getApplicationProperties().getValue().get(ClientConstants.REQUEST_RESPONSE_STATUS_DESCRIPTION); + Symbol errorCondition = getResponseErrorCondition(responseMessage); + Object statusDescription = getResponseStatusDescription(responseMessage); return generateExceptionFromError(errorCondition, statusDescription == null ? errorCondition.toString() : (String) statusDescription); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/SASUtil.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/SASUtil.java new file mode 100644 index 000000000000..593af84305ab --- /dev/null +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/SASUtil.java @@ -0,0 +1,75 @@ +package com.microsoft.azure.servicebus.primitives; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.Base64; +import java.util.Locale; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class SASUtil { + private static final String SAS_FORMAT = "SharedAccessSignature sr=%s&sig=%s&se=%s&skn=%s"; + private static final String HMACAlgorithm = "HMACSHA256"; + private static final Base64.Encoder base64Encoder = Base64.getEncoder(); + + public static String generateSharedAccessSignatureToken(String sasKeyName, String sasKey, String resourceURI, int validityInSeconds) throws InvalidKeyException + { + if(StringUtil.isNullOrWhiteSpace(sasKey)) + { + throw new IllegalArgumentException("Invalid SAS key"); + } + + if(StringUtil.isNullOrWhiteSpace(resourceURI)) + { + throw new IllegalArgumentException("Invalid resourceURI"); + } + + if(validityInSeconds <= 0) + { + throw new IllegalArgumentException("validityInSeconds should be positive"); + } + + String validUntil = String.valueOf(Instant.now().getEpochSecond() + validityInSeconds); + try + { + String utf8EncodingName = StandardCharsets.UTF_8.name(); + String encodedResourceURI = URLEncoder.encode(resourceURI, utf8EncodingName); + String secretToSign = encodedResourceURI + "\n" + validUntil; + Mac hmac = Mac.getInstance(HMACAlgorithm); + SecretKeySpec secretKey = new SecretKeySpec(StringUtil.convertStringToBytes(sasKey), HMACAlgorithm); + hmac.init(secretKey); + byte[] signatureBytes = hmac.doFinal(StringUtil.convertStringToBytes(secretToSign)); + String signature = base64Encoder.encodeToString(signatureBytes); + + return String.format(Locale.US, SAS_FORMAT, + encodedResourceURI, + URLEncoder.encode(signature, utf8EncodingName), + validUntil, + URLEncoder.encode(sasKeyName, utf8EncodingName)); + } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { + // These exceptions shouldn't occur. + throw new RuntimeException("UTF-8 encoding or HMACSHA256 algorithm is missing in the java runtime."); + } + } + + static int getCBSTokenRenewIntervalInSeconds(int tokenValidityInSeconds) + { + if(tokenValidityInSeconds >= 300) + { + return 30; + } + else if(tokenValidityInSeconds >= 60) + { + return 10; + } + else + { + return 1; + } + } +} diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/StringUtil.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/StringUtil.java index d05ee4d2ba54..5a02828a7cb8 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/StringUtil.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/StringUtil.java @@ -5,12 +5,13 @@ package com.microsoft.azure.servicebus.primitives; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.UUID; public final class StringUtil { public final static String EMPTY = ""; - private final static Charset UTF8CharSet = Charset.forName("UTF-8"); + private final static Charset UTF8CharSet = StandardCharsets.UTF_8; public static boolean isNullOrEmpty(String string) { diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/Util.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/Util.java index 20127eb127de..2afac3e9e2d9 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/Util.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/Util.java @@ -379,7 +379,7 @@ static int encodeMessageToCustomArray(Message message, byte[] encodedBytes, int } // Pass little less than client timeout to the server so client doesn't time out before server times out - public static Duration adjustServerTimeout(Duration clientTimeout) + static Duration adjustServerTimeout(Duration clientTimeout) { return clientTimeout.minusMillis(100); } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/UtilsTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/UtilsTests.java similarity index 97% rename from azure-servicebus/src/test/java/com/microsoft/azure/servicebus/UtilsTests.java rename to azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/UtilsTests.java index e0d2ef863ae0..b8418533863f 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/UtilsTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/primitives/UtilsTests.java @@ -1,4 +1,4 @@ -package com.microsoft.azure.servicebus; +package com.microsoft.azure.servicebus.primitives; import java.time.Instant; import java.util.UUID; diff --git a/pom.xml b/pom.xml index 53d86f654c08..6417e66bd3d8 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,5 @@ azure-servicebus - azure-servicebus-samples \ No newline at end of file From 7431a102c5c362b33656791111ced51437fa3d80 Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Thu, 11 May 2017 02:24:56 -0700 Subject: [PATCH 13/17] Fix to stop reactor when messagingfactory is closed. --- .../azure/servicebus/QueueClient.java | 4 ++- .../azure/servicebus/SubscriptionClient.java | 6 ++-- .../primitives/MessagingFactory.java | 30 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java index 25b2768dfb69..1c9ce5f9a9aa 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java @@ -16,6 +16,7 @@ public final class QueueClient extends InitializableEntity implements IQueueClient { private final ReceiveMode receiveMode; + private MessagingFactory factory; private IMessageSender sender; private MessageAndSessionPump messageAndSessionPump; private SessionBrowser sessionBrowser; @@ -43,6 +44,7 @@ public QueueClient(MessagingFactory factory, String queuePath, ReceiveMode recei private CompletableFuture createInternals(MessagingFactory factory, String queuePath, ReceiveMode receiveMode) { + this.factory = factory; CompletableFuture senderFuture = ClientFactory.createMessageSenderFromEntityPathAsync(factory, queuePath); CompletableFuture postSenderFuture = senderFuture.thenAcceptAsync((s) -> { this.sender = s; @@ -137,7 +139,7 @@ CompletableFuture initializeAsync() throws Exception { @Override protected CompletableFuture onClose() { - return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.sender.closeAsync().thenCompose((u) -> this.miscRequestResponseHandler.closeAsync())); + return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.sender.closeAsync().thenCompose((u) -> this.miscRequestResponseHandler.closeAsync().thenCompose((w) -> this.factory.closeAsync()))); } @Override diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java index 4a7ff2d70341..373a9e6289ec 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java @@ -19,6 +19,7 @@ public final class SubscriptionClient extends InitializableEntity implements ISu { private final ReceiveMode receiveMode; private String subscriptionPath; + private MessagingFactory factory; private MessageAndSessionPump messageAndSessionPump; private SessionBrowser sessionBrowser; private MiscRequestResponseOperationHandler miscRequestResponseHandler; @@ -46,7 +47,8 @@ public SubscriptionClient(MessagingFactory factory, String subscriptionPath, Rec } private CompletableFuture createPumpAndBrowserAsync(MessagingFactory factory) - { + { + this.factory = factory; CompletableFuture postSessionBrowserFuture = MiscRequestResponseOperationHandler.create(factory, this.subscriptionPath).thenAcceptAsync((msoh) -> { this.miscRequestResponseHandler = msoh; this.sessionBrowser = new SessionBrowser(factory, this.subscriptionPath, msoh); @@ -126,7 +128,7 @@ CompletableFuture initializeAsync() throws Exception { @Override protected CompletableFuture onClose() { - return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.miscRequestResponseHandler.closeAsync()); + return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.miscRequestResponseHandler.closeAsync().thenCompose((w) -> this.factory.closeAsync())); } @Override diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java index d46be6fc7d16..e9a21a5e056c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java @@ -125,7 +125,7 @@ private void startReactor(ReactorHandler reactorHandler) throws IOException this.reactorScheduler = new ReactorDispatcher(newReactor); } - final Thread reactorThread = new Thread(new RunReactor(newReactor)); + final Thread reactorThread = new Thread(new RunReactor()); reactorThread.start(); } @@ -233,7 +233,7 @@ public void onConnectionError(ErrorCondition error) if (this.getIsClosingOrClosed() && !this.closeTask.isDone()) { - this.closeTask.complete(null); + this.closeTask.complete(null); Timer.unregister(this.getClientId()); } } @@ -246,6 +246,11 @@ private void onReactorError(Exception cause) } else { + if(this.getIsClosingOrClosed()) + { + return; + } + final Connection currentConnection = this.connection; try @@ -324,10 +329,10 @@ public void run() } else if(this.connection == null || this.connection.getRemoteState() == EndpointState.CLOSED) { - AsyncUtil.completeFuture(this.closeTask, null); + this.closeTask.complete(null); } - } - + } + return this.closeTask; } @@ -335,9 +340,9 @@ private class RunReactor implements Runnable { final private Reactor rctr; - public RunReactor(final Reactor reactor) + public RunReactor() { - this.rctr = reactor; + this.rctr = MessagingFactory.this.getReactor(); } public void run() @@ -351,7 +356,16 @@ public void run() { this.rctr.setTimeout(3141); this.rctr.start(); - while(!Thread.interrupted() && this.rctr.process()) {} + boolean continuteProcessing = true; + while(!Thread.interrupted() && continuteProcessing) + { + // If factory is closed, stop reactor too + if(MessagingFactory.this.getIsClosed()) + { + break; + } + continuteProcessing = this.rctr.process(); + } this.rctr.stop(); } catch (HandlerException handlerException) From 22b6aa6c170867a7d3765066e9f860825e45766e Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Mon, 15 May 2017 19:59:13 -0700 Subject: [PATCH 14/17] Refactoring to hide some public methods and to bring similarity with core clr client. --- .../servicebus/BrowsableMessageSession.java | 4 +- .../azure/servicebus/ClientFactory.java | 20 ++++---- .../microsoft/azure/servicebus/IMessage.java | 2 +- .../servicebus/IMessageAndSessionPump.java | 6 +-- .../azure/servicebus/IMessageReceiver.java | 4 +- .../azure/servicebus/IMessageSession.java | 4 +- .../azure/servicebus/IQueueClient.java | 4 +- .../azure/servicebus/ISubscriptionClient.java | 4 +- .../azure/servicebus/ITopicClient.java | 2 +- .../microsoft/azure/servicebus/Message.java | 2 +- .../servicebus/MessageAndSessionPump.java | 10 ++-- .../azure/servicebus/MessageConverter.java | 4 +- .../azure/servicebus/MessageReceiver.java | 6 +-- .../azure/servicebus/MessageSession.java | 6 +-- .../azure/servicebus/QueueClient.java | 41 +++++++++------- .../azure/servicebus/SubscriptionClient.java | 48 ++++++++++++------- .../azure/servicebus/TopicClient.java | 12 +++-- .../azure/servicebus/QueueClientTests.java | 4 +- .../azure/servicebus/SessionTests.java | 4 +- .../servicebus/SubscriptionClientTests.java | 15 ++++-- .../azure/servicebus/TestCommons.java | 38 ++++++++------- .../microsoft/azure/servicebus/TestUtils.java | 4 +- 22 files changed, 142 insertions(+), 102 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java index fe633430e023..cf17e4b25262 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/BrowsableMessageSession.java @@ -62,7 +62,7 @@ public CompletableFuture setStateAsync(byte[] sessionState) { } @Override - public CompletableFuture renewLockAsync() { + public CompletableFuture renewSessionLockAsync() { throw new UnsupportedOperationException(INVALID_OPERATION_ERROR_MESSAGE); } @@ -113,7 +113,7 @@ public CompletableFuture receiveAsync(Duration serverWaitTime) } @Override - public CompletableFuture receiveAsync(long sequenceNumber) + public CompletableFuture receiveBySequenceNumberAsync(long sequenceNumber) { throw new UnsupportedOperationException(INVALID_OPERATION_ERROR_MESSAGE); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java index dc7daf681c76..ba34992b12e0 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ClientFactory.java @@ -21,7 +21,7 @@ public static IMessageSender createMessageSenderFromConnectionStringBuilder(Conn return Utils.completeFuture(createMessageSenderFromConnectionStringBuilderAsync(amqpConnectionStringBuilder)); } - public static IMessageSender createMessageSenderFromEntityPath(MessagingFactory messagingFactory, String entityPath) throws InterruptedException, ServiceBusException + static IMessageSender createMessageSenderFromEntityPath(MessagingFactory messagingFactory, String entityPath) throws InterruptedException, ServiceBusException { return Utils.completeFuture(createMessageSenderFromEntityPathAsync(messagingFactory, entityPath)); } @@ -39,7 +39,7 @@ public static CompletableFuture createMessageSenderFromConnectio return sender.initializeAsync().thenApply((v) -> sender); } - public static CompletableFuture createMessageSenderFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath) + static CompletableFuture createMessageSenderFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath) { Utils.assertNonNull("messagingFactory", messagingFactory); MessageSender sender = new MessageSender(messagingFactory, entityPath); @@ -68,12 +68,12 @@ public static IMessageReceiver createMessageReceiverFromConnectionStringBuilder( return Utils.completeFuture(createMessageReceiverFromConnectionStringBuilderAsync(amqpConnectionStringBuilder, receiveMode)); } - public static IMessageReceiver createMessageReceiverFromEntityPath(MessagingFactory messagingFactory, String entityPath) throws InterruptedException, ServiceBusException + static IMessageReceiver createMessageReceiverFromEntityPath(MessagingFactory messagingFactory, String entityPath) throws InterruptedException, ServiceBusException { return createMessageReceiverFromEntityPath(messagingFactory, entityPath, DEFAULTRECEIVEMODE); } - public static IMessageReceiver createMessageReceiverFromEntityPath(MessagingFactory messagingFactory, String entityPath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + static IMessageReceiver createMessageReceiverFromEntityPath(MessagingFactory messagingFactory, String entityPath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { return Utils.completeFuture(createMessageReceiverFromEntityPathAsync(messagingFactory, entityPath, receiveMode)); } @@ -101,12 +101,12 @@ public static CompletableFuture createMessageReceiverFromConne return receiver.initializeAsync().thenApply((v) -> receiver); } - public static CompletableFuture createMessageReceiverFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath) + static CompletableFuture createMessageReceiverFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath) { return createMessageReceiverFromEntityPathAsync(messagingFactory, entityPath, DEFAULTRECEIVEMODE); } - public static CompletableFuture createMessageReceiverFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, ReceiveMode receiveMode) + static CompletableFuture createMessageReceiverFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, ReceiveMode receiveMode) { Utils.assertNonNull("messagingFactory", messagingFactory); MessageReceiver receiver = new MessageReceiver(messagingFactory, entityPath, receiveMode); @@ -134,12 +134,12 @@ public static IMessageSession acceptSessionFromConnectionStringBuilder(Connectio return Utils.completeFuture(acceptSessionFromConnectionStringBuilderAsync(amqpConnectionStringBuilder, sessionId, receiveMode)); } - public static IMessageSession acceptSessionFromEntityPath(MessagingFactory messagingFactory, String entityPath, String sessionId) throws InterruptedException, ServiceBusException + static IMessageSession acceptSessionFromEntityPath(MessagingFactory messagingFactory, String entityPath, String sessionId) throws InterruptedException, ServiceBusException { return acceptSessionFromEntityPath(messagingFactory, entityPath, sessionId, DEFAULTRECEIVEMODE); } - public static IMessageSession acceptSessionFromEntityPath(MessagingFactory messagingFactory, String entityPath, String sessionId, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + static IMessageSession acceptSessionFromEntityPath(MessagingFactory messagingFactory, String entityPath, String sessionId, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { return Utils.completeFuture(acceptSessionFromEntityPathAsync(messagingFactory, entityPath, sessionId, receiveMode)); } @@ -167,12 +167,12 @@ public static CompletableFuture acceptSessionFromConnectionStri return session.initializeAsync().thenApply((v) -> session); } - public static CompletableFuture acceptSessionFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, String sessionId) + static CompletableFuture acceptSessionFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, String sessionId) { return acceptSessionFromEntityPathAsync(messagingFactory, entityPath, sessionId, DEFAULTRECEIVEMODE); } - public static CompletableFuture acceptSessionFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, String sessionId, ReceiveMode receiveMode) + static CompletableFuture acceptSessionFromEntityPathAsync(MessagingFactory messagingFactory, String entityPath, String sessionId, ReceiveMode receiveMode) { Utils.assertNonNull("messagingFactory", messagingFactory); MessageSession session = new MessageSession(messagingFactory, entityPath, sessionId, receiveMode); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessage.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessage.java index 9b9fb13ebe3b..65552eb3b1bc 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessage.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessage.java @@ -36,7 +36,7 @@ public interface IMessage { public void setSessionId(String sessionId); - public byte[] getContent(); + public byte[] getBody(); public void setContent(byte[] content); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java index 5ef5befa3cd2..67f701d392cc 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageAndSessionPump.java @@ -28,9 +28,9 @@ interface IMessageAndSessionPump CompletableFuture completeAsync(UUID lockToken); - void defer(UUID lockToken) throws InterruptedException, ServiceBusException; - - void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException; +// void defer(UUID lockToken) throws InterruptedException, ServiceBusException; +// +// void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException; CompletableFuture deferAsync(UUID lockToken); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java index 0e8af3f92b0e..864208664164 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageReceiver.java @@ -56,7 +56,7 @@ public interface IMessageReceiver extends IMessageEntity, IMessageBrowser{ IMessage receive(Duration serverWaitTime) throws InterruptedException, ServiceBusException; - IMessage receive(long sequenceNumber) throws InterruptedException, ServiceBusException; + IMessage receiveBySequenceNumber(long sequenceNumber) throws InterruptedException, ServiceBusException; Collection receiveBatch(int maxMessageCount) throws InterruptedException, ServiceBusException; @@ -68,7 +68,7 @@ public interface IMessageReceiver extends IMessageEntity, IMessageBrowser{ CompletableFuture receiveAsync(Duration serverWaitTime); - CompletableFuture receiveAsync(long sequenceNumber); + CompletableFuture receiveBySequenceNumberAsync(long sequenceNumber); CompletableFuture> receiveBatchAsync(int maxMessageCount); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageSession.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageSession.java index 95291cc5441d..b1331d7fb70c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageSession.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IMessageSession.java @@ -11,9 +11,9 @@ public interface IMessageSession extends IMessageReceiver { Instant getLockedUntilUtc(); - void renewLock() throws InterruptedException, ServiceBusException; + void renewSessionLock() throws InterruptedException, ServiceBusException; - CompletableFuture renewLockAsync(); + CompletableFuture renewSessionLockAsync(); void setState(byte[] state) throws InterruptedException, ServiceBusException; diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IQueueClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IQueueClient.java index 0fb841418193..508dc734e73f 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IQueueClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/IQueueClient.java @@ -1,6 +1,8 @@ package com.microsoft.azure.servicebus; -public interface IQueueClient extends IMessageSender, IMessageSessionEntity, IMessageAndSessionPump, IMessageEntity +public interface IQueueClient extends IMessageSender, IMessageAndSessionPump, IMessageEntity { public ReceiveMode getReceiveMode(); + + public String getQueueName(); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java index 3f2057644b71..d1f1111b715d 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ISubscriptionClient.java @@ -6,7 +6,7 @@ import com.microsoft.azure.servicebus.rules.Filter; import com.microsoft.azure.servicebus.rules.RuleDescription; -public interface ISubscriptionClient extends IMessageEntity, IMessageSessionEntity, IMessageAndSessionPump +public interface ISubscriptionClient extends IMessageEntity, IMessageAndSessionPump { public ReceiveMode getReceiveMode(); public void addRule(RuleDescription ruleDescription) throws InterruptedException, ServiceBusException; @@ -15,4 +15,6 @@ public interface ISubscriptionClient extends IMessageEntity, IMessageSessionEnti public CompletableFuture addRuleAsync(String ruleName, Filter filter); public CompletableFuture removeRuleAsync(String ruleName); public void removeRule(String ruleName) throws InterruptedException, ServiceBusException; + public String getTopicName(); + public String getSubscriptionName(); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ITopicClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ITopicClient.java index 6de619c3afe8..9b7ba183d81c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ITopicClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/ITopicClient.java @@ -2,5 +2,5 @@ // Should we allow browse/peek on topic? public interface ITopicClient extends IMessageSender, IMessageBrowser, IMessageEntity { - + public String getTopicName(); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Message.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Message.java index 7424afd5f3d1..305e6215a767 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Message.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/Message.java @@ -183,7 +183,7 @@ public void setSessionId(String sessionId) { } @Override - public byte[] getContent() { + public byte[] getBody() { return this.content; } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java index 081f5074a1b4..549e4ac5103c 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageAndSessionPump.java @@ -602,7 +602,7 @@ protected void loop() if(renewInterval != null && !renewInterval.isNegative()) { this.timerFuture = Timer.schedule(() -> { - this.session.renewLockAsync().handleAsync((v, renewLockEx) -> + this.session.renewSessionLockAsync().handleAsync((v, renewLockEx) -> { if(renewLockEx != null) { @@ -664,14 +664,14 @@ public CompletableFuture completeAsync(UUID lockToken) { return this.innerReceiver.completeAsync(lockToken); } - @Override - public void defer(UUID lockToken) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken) throws InterruptedException, ServiceBusException { this.checkInnerReceiveCreated(); this.innerReceiver.defer(lockToken); } - @Override - public void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { this.checkInnerReceiveCreated(); this.innerReceiver.defer(lockToken, propertiesToModify); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageConverter.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageConverter.java index 8f793ee394b6..78ff0a9fc520 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageConverter.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageConverter.java @@ -25,9 +25,9 @@ public class MessageConverter public static org.apache.qpid.proton.message.Message convertBrokeredMessageToAmqpMessage(Message brokeredMessage) { org.apache.qpid.proton.message.Message amqpMessage = Proton.message(); - if(brokeredMessage.getContent() != null) + if(brokeredMessage.getBody() != null) { - amqpMessage.setBody(new Data(new Binary(brokeredMessage.getContent()))); + amqpMessage.setBody(new Data(new Binary(brokeredMessage.getBody()))); } if(brokeredMessage.getProperties() != null) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java index 0c533d6199ad..9f5e04c96085 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageReceiver.java @@ -319,8 +319,8 @@ public IMessage receive(Duration serverWaitTime) throws InterruptedException, Se } @Override - public IMessage receive(long sequenceNumber) throws InterruptedException, ServiceBusException{ - return Utils.completeFuture(this.receiveAsync(sequenceNumber)); + public IMessage receiveBySequenceNumber(long sequenceNumber) throws InterruptedException, ServiceBusException{ + return Utils.completeFuture(this.receiveBySequenceNumberAsync(sequenceNumber)); } @Override @@ -392,7 +392,7 @@ else if (c.isEmpty()) } @Override - public CompletableFuture receiveAsync(long sequenceNumber) { + public CompletableFuture receiveBySequenceNumberAsync(long sequenceNumber) { ArrayList list = new ArrayList<>(); list.add(sequenceNumber); return this.receiveBatchAsync(list).thenApplyAsync(c -> diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageSession.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageSession.java index d81a9a9c9363..a9f2eacc6e13 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageSession.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/MessageSession.java @@ -51,12 +51,12 @@ public Instant getLockedUntilUtc() { } @Override - public void renewLock() throws InterruptedException, ServiceBusException { - Utils.completeFuture(this.renewLockAsync()); + public void renewSessionLock() throws InterruptedException, ServiceBusException { + Utils.completeFuture(this.renewSessionLockAsync()); } @Override - public CompletableFuture renewLockAsync() { + public CompletableFuture renewSessionLockAsync() { return this.getInternalReceiver().renewSessionLocksAsync(); } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java index 1c9ce5f9a9aa..0d33fc2f67a2 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/QueueClient.java @@ -28,15 +28,14 @@ private QueueClient(ReceiveMode receiveMode) this.receiveMode = receiveMode; } - public QueueClient(String amqpConnectionString, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + public QueueClient(ConnectionStringBuilder amqpConnectionStringBuilder, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { this(receiveMode); - ConnectionStringBuilder builder = new ConnectionStringBuilder(amqpConnectionString); - CompletableFuture factoryFuture = MessagingFactory.createFromConnectionStringBuilderAsync(builder); - Utils.completeFuture(factoryFuture.thenComposeAsync((f) -> this.createInternals(f, builder.getEntityPath(), receiveMode))); + CompletableFuture factoryFuture = MessagingFactory.createFromConnectionStringBuilderAsync(amqpConnectionStringBuilder); + Utils.completeFuture(factoryFuture.thenComposeAsync((f) -> this.createInternals(f, amqpConnectionStringBuilder.getEntityPath(), receiveMode))); } - public QueueClient(MessagingFactory factory, String queuePath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + QueueClient(MessagingFactory factory, String queuePath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { this(receiveMode); Utils.completeFuture(this.createInternals(factory, queuePath, receiveMode)); @@ -142,23 +141,23 @@ protected CompletableFuture onClose() { return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.sender.closeAsync().thenCompose((u) -> this.miscRequestResponseHandler.closeAsync().thenCompose((w) -> this.factory.closeAsync()))); } - @Override - public Collection getMessageSessions() throws InterruptedException, ServiceBusException { +// @Override + Collection getMessageSessions() throws InterruptedException, ServiceBusException { return Utils.completeFuture(this.getMessageSessionsAsync()); } - @Override - public Collection getMessageSessions(Instant lastUpdatedTime) throws InterruptedException, ServiceBusException { +// @Override + Collection getMessageSessions(Instant lastUpdatedTime) throws InterruptedException, ServiceBusException { return Utils.completeFuture(this.getMessageSessionsAsync(lastUpdatedTime)); } - @Override - public CompletableFuture> getMessageSessionsAsync() { +// @Override + CompletableFuture> getMessageSessionsAsync() { return this.sessionBrowser.getMessageSessionsAsync(); } - @Override - public CompletableFuture> getMessageSessionsAsync(Instant lastUpdatedTime) { +// @Override + CompletableFuture> getMessageSessionsAsync(Instant lastUpdatedTime) { return this.sessionBrowser.getMessageSessionsAsync(Date.from(lastUpdatedTime)); } @@ -192,13 +191,13 @@ public CompletableFuture completeAsync(UUID lockToken) { return this.messageAndSessionPump.completeAsync(lockToken); } - @Override - public void defer(UUID lockToken) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken) throws InterruptedException, ServiceBusException { this.messageAndSessionPump.defer(lockToken); } - @Override - public void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { this.messageAndSessionPump.defer(lockToken, propertiesToModify); } @@ -260,5 +259,11 @@ public int getPrefetchCount() { @Override public void setPrefetchCount(int prefetchCount) throws ServiceBusException { this.messageAndSessionPump.setPrefetchCount(prefetchCount); - } + } + + @Override + public String getQueueName() + { + return this.getEntityPath(); + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java index 373a9e6289ec..2de6a384ad0f 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java @@ -17,6 +17,7 @@ public final class SubscriptionClient extends InitializableEntity implements ISubscriptionClient { + private static final String SUBSCRIPTIONS_DELIMITER = "/subscriptions/"; private final ReceiveMode receiveMode; private String subscriptionPath; private MessagingFactory factory; @@ -30,16 +31,15 @@ private SubscriptionClient(ReceiveMode receiveMode) this.receiveMode = receiveMode; } - public SubscriptionClient(String amqpConnectionString, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + public SubscriptionClient(ConnectionStringBuilder amqpConnectionStringBuilder, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this(receiveMode); - ConnectionStringBuilder builder = new ConnectionStringBuilder(amqpConnectionString); - this.subscriptionPath = builder.getEntityPath(); - CompletableFuture factoryFuture = MessagingFactory.createFromConnectionStringBuilderAsync(builder); + this(receiveMode); + this.subscriptionPath = amqpConnectionStringBuilder.getEntityPath(); + CompletableFuture factoryFuture = MessagingFactory.createFromConnectionStringBuilderAsync(amqpConnectionStringBuilder); Utils.completeFuture(factoryFuture.thenComposeAsync((f) -> this.createPumpAndBrowserAsync(f))); } - public SubscriptionClient(MessagingFactory factory, String subscriptionPath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException + SubscriptionClient(MessagingFactory factory, String subscriptionPath, ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { this(receiveMode); this.subscriptionPath = subscriptionPath; @@ -131,23 +131,23 @@ protected CompletableFuture onClose() { return this.messageAndSessionPump.closeAsync().thenCompose((v) -> this.miscRequestResponseHandler.closeAsync().thenCompose((w) -> this.factory.closeAsync())); } - @Override - public Collection getMessageSessions() throws InterruptedException, ServiceBusException { +// @Override + Collection getMessageSessions() throws InterruptedException, ServiceBusException { return Utils.completeFuture(this.getMessageSessionsAsync()); } - @Override - public Collection getMessageSessions(Instant lastUpdatedTime) throws InterruptedException, ServiceBusException { +// @Override + Collection getMessageSessions(Instant lastUpdatedTime) throws InterruptedException, ServiceBusException { return Utils.completeFuture(this.getMessageSessionsAsync(lastUpdatedTime)); } - @Override - public CompletableFuture> getMessageSessionsAsync() { +// @Override + CompletableFuture> getMessageSessionsAsync() { return this.sessionBrowser.getMessageSessionsAsync(); } - @Override - public CompletableFuture> getMessageSessionsAsync(Instant lastUpdatedTime) { +// @Override + CompletableFuture> getMessageSessionsAsync(Instant lastUpdatedTime) { return this.sessionBrowser.getMessageSessionsAsync(Date.from(lastUpdatedTime)); } @@ -181,13 +181,13 @@ public CompletableFuture completeAsync(UUID lockToken) { return this.messageAndSessionPump.completeAsync(lockToken); } - @Override - public void defer(UUID lockToken) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken) throws InterruptedException, ServiceBusException { this.messageAndSessionPump.defer(lockToken); } - @Override - public void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { +// @Override + void defer(UUID lockToken, Map propertiesToModify) throws InterruptedException, ServiceBusException { this.messageAndSessionPump.defer(lockToken, propertiesToModify); } @@ -250,4 +250,16 @@ public int getPrefetchCount() { public void setPrefetchCount(int prefetchCount) throws ServiceBusException { this.messageAndSessionPump.setPrefetchCount(prefetchCount); } + + @Override + public String getTopicName() { + String entityPath = this.getEntityPath(); + return entityPath.substring(0, entityPath.indexOf(SUBSCRIPTIONS_DELIMITER)); + } + + @Override + public String getSubscriptionName() { + String entityPath = this.getEntityPath(); + return entityPath.substring(entityPath.indexOf(SUBSCRIPTIONS_DELIMITER) + SUBSCRIPTIONS_DELIMITER.length()); + } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/TopicClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/TopicClient.java index 9452a94762c7..1696443ffe7a 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/TopicClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/TopicClient.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.concurrent.CompletableFuture; +import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; import com.microsoft.azure.servicebus.primitives.MessagingFactory; import com.microsoft.azure.servicebus.primitives.ServiceBusException; import com.microsoft.azure.servicebus.primitives.StringUtil; @@ -18,14 +19,14 @@ private TopicClient() super(StringUtil.getShortRandomString(), null); } - public TopicClient(String amqpConnectionString) throws InterruptedException, ServiceBusException + public TopicClient(ConnectionStringBuilder amqpConnectionStringBuilder) throws InterruptedException, ServiceBusException { this(); - this.sender = ClientFactory.createMessageSenderFromConnectionString(amqpConnectionString); + this.sender = ClientFactory.createMessageSenderFromConnectionStringBuilder(amqpConnectionStringBuilder); this.browser = new MessageBrowser((MessageSender)sender); } - public TopicClient(MessagingFactory factory, String topicPath) throws InterruptedException, ServiceBusException + TopicClient(MessagingFactory factory, String topicPath) throws InterruptedException, ServiceBusException { this(); this.sender = ClientFactory.createMessageSenderFromEntityPath(factory, topicPath); @@ -127,4 +128,9 @@ CompletableFuture initializeAsync() throws Exception { protected CompletableFuture onClose() { return this.sender.closeAsync(); } + + @Override + public String getTopicName() { + return this.getEntityPath(); + } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java index 26b940fc2796..cc621ef6b616 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/QueueClientTests.java @@ -44,12 +44,12 @@ private void createSessionfulQueueClient() throws InterruptedException, ServiceB private void createQueueClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.queueClient = new QueueClient(TestUtils.getNonPartitionedQueueConnectionStringBuilder().toString(), receiveMode); + this.queueClient = new QueueClient(TestUtils.getNonPartitionedQueueConnectionStringBuilder(), receiveMode); } private void createSessionfulQueueClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.sessionfulQueueClient = new QueueClient(TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder().toString(), receiveMode); + this.sessionfulQueueClient = new QueueClient(TestUtils.getNonPartitionedSessionfulQueueConnectionStringBuilder(), receiveMode); } @Test diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SessionTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SessionTests.java index 2786663d6fbc..97ccb7d3b37d 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SessionTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SessionTests.java @@ -209,10 +209,10 @@ public void testRenewSessionLock() throws InterruptedException, ServiceBusExcept String sessionId = getRandomString(); this.session = ClientFactory.acceptSessionFromEntityPath(factory, receiveBuilder.getEntityPath(), sessionId, ReceiveMode.PeekLock); Instant initialValidity = this.session.getLockedUntilUtc(); - this.session.renewLock(); + this.session.renewSessionLock(); Instant renewedValidity = this.session.getLockedUntilUtc(); Assert.assertTrue("RenewSessionLock did not renew session lockeduntil time.", renewedValidity.isAfter(initialValidity)); - this.session.renewLock(); + this.session.renewSessionLock(); Instant renewedValidity2 = this.session.getLockedUntilUtc(); Assert.assertTrue("RenewSessionLock did not renew session lockeduntil time.", renewedValidity2.isAfter(renewedValidity)); } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java index d7c4e96dccd4..254455ba2f17 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/SubscriptionClientTests.java @@ -63,14 +63,14 @@ private void createSessionfulSubscriptionClient() throws InterruptedException, S private void createSubscriptionClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.topicClient = new TopicClient(TestUtils.getNonPartitionedTopicConnectionStringBuilder().toString()); - this.subscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder().toString(), receiveMode); + this.topicClient = new TopicClient(TestUtils.getNonPartitionedTopicConnectionStringBuilder()); + this.subscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder(), receiveMode); } private void createSessionfulSubscriptionClient(ReceiveMode receiveMode) throws InterruptedException, ServiceBusException { - this.sessionfulTopicClient = new TopicClient(TestUtils.getNonPartitionedSessionfulTopicConnectionStringBuilder().toString()); - this.sessionfulSubscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder().toString(), receiveMode); + this.sessionfulTopicClient = new TopicClient(TestUtils.getNonPartitionedSessionfulTopicConnectionStringBuilder()); + this.sessionfulSubscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSessionfulSubscriptionConnectionStringBuilder(), receiveMode); } @Test @@ -215,4 +215,11 @@ public void testSessionPumpRenewLock() throws InterruptedException, ServiceBusEx this.createSessionfulSubscriptionClient(); MessageAndSessionPumpTests.testSessionPumpRenewLock(this.sessionfulTopicClient, this.sessionfulSubscriptionClient); } + + @Test + public void testSubscriptionNameSplitting() throws InterruptedException, ServiceBusException + { + this.subscriptionClient = new SubscriptionClient(TestUtils.getNonPartitionedSubscriptionConnectionStringBuilder(), ReceiveMode.PeekLock); + Assert.assertEquals("Wrong subscription name returned.", TestUtils.getProperty(TestUtils.SUBSCRIPTION_NAME_PROPERTY), this.subscriptionClient.getSubscriptionName()); + } } diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java index 2631067fbb51..356e1402f072 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestCommons.java @@ -394,7 +394,7 @@ public static void testReceiveBySequenceNumberAndComplete(IMessageSender sender, receiver.defer(receivedMessage.getLockToken()); // Now receive by sequence number - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", sequenceNumber, receivedMessage.getSequenceNumber()); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", messageId, receivedMessage.getMessageId()); receiver.complete(receivedMessage.getLockToken()); @@ -402,7 +402,7 @@ public static void testReceiveBySequenceNumberAndComplete(IMessageSender sender, // Try to receive by sequence number again try { - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.fail("Message recieved by sequnce number was not properly completed."); } catch(MessageNotFoundException e) @@ -426,14 +426,14 @@ public static void testReceiveBySequenceNumberAndAbandon(IMessageSender sender, receiver.defer(receivedMessage.getLockToken()); // Now receive by sequence number - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", sequenceNumber, receivedMessage.getSequenceNumber()); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", messageId, receivedMessage.getMessageId()); long deliveryCount = receivedMessage.getDeliveryCount(); receiver.abandon(receivedMessage.getLockToken()); // Try to receive by sequence number again - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("Abandon didn't increase the delivery count for the message received by sequence number.", deliveryCount + 1, receivedMessage.getDeliveryCount()); receiver.complete(receivedMessage.getLockToken()); } @@ -463,7 +463,7 @@ public static void testReceiveBySequenceNumberAndDefer(IMessageSender sender, St receiver.defer(receivedMessage.getLockToken(), customProperties); // Now receive by sequence number - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", sequenceNumber, receivedMessage.getSequenceNumber()); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", messageId, receivedMessage.getMessageId()); Assert.assertEquals("Defer didn't update properties of the message received by sequence number", firstDeferredPhase, receivedMessage.getProperties().get(phaseKey)); @@ -471,7 +471,7 @@ public static void testReceiveBySequenceNumberAndDefer(IMessageSender sender, St receiver.defer(receivedMessage.getLockToken(), customProperties); // Try to receive by sequence number again - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message after deferrring", sequenceNumber, receivedMessage.getSequenceNumber()); Assert.assertEquals("Defer didn't update properties of the message received by sequence number", secondDeferredPhase, receivedMessage.getProperties().get(phaseKey)); receiver.complete(receivedMessage.getLockToken()); @@ -492,7 +492,7 @@ public static void testReceiveBySequenceNumberAndDeadletter(IMessageSender sende receiver.defer(receivedMessage.getLockToken()); // Now receive by sequence number - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", sequenceNumber, receivedMessage.getSequenceNumber()); Assert.assertEquals("ReceiveBySequenceNumber didn't receive the right message.", messageId, receivedMessage.getMessageId()); String deadLetterReason = "java client deadletter test"; @@ -501,7 +501,7 @@ public static void testReceiveBySequenceNumberAndDeadletter(IMessageSender sende // Try to receive by sequence number again try { - receivedMessage = receiver.receive(sequenceNumber); + receivedMessage = receiver.receiveBySequenceNumber(sequenceNumber); Assert.fail("Message received by sequence number was not properly deadlettered"); } catch(MessageNotFoundException e) @@ -510,7 +510,7 @@ public static void testReceiveBySequenceNumberAndDeadletter(IMessageSender sende } } - public static void testGetMessageSessions(IMessageSender sender, IMessageSessionEntity sessionsClient) throws InterruptedException, ServiceBusException + public static void testGetMessageSessions(IMessageSender sender, Object sessionsClient) throws InterruptedException, ServiceBusException { int numSessions = 110; // More than default page size String[] sessionIds = new String[numSessions]; @@ -522,7 +522,12 @@ public static void testGetMessageSessions(IMessageSender sender, IMessageSession sender.send(message); } - Collection sessions = Utils.completeFuture(sessionsClient.getMessageSessionsAsync()); + Collection sessions; + if(sessionsClient instanceof QueueClient) + sessions = Utils.completeFuture(((QueueClient)sessionsClient).getMessageSessionsAsync()); + else + sessions = Utils.completeFuture(((SubscriptionClient)sessionsClient).getMessageSessionsAsync()); + Assert.assertTrue("GetMessageSessions didnot return all sessions", numSessions <= sessions.size()); // There could be sessions left over from other tests IMessageSession anySession = (IMessageSession)sessions.toArray()[0]; @@ -585,7 +590,7 @@ public static void drainAllMessagesFromReceiver(IMessageReceiver receiver, boole { try { - IMessage message = receiver.receive(peekedMessage.getSequenceNumber()); + IMessage message = receiver.receiveBySequenceNumber(peekedMessage.getSequenceNumber()); if(receiver.getReceiveMode() == ReceiveMode.PeekLock) { receiver.complete(message.getLockToken()); @@ -610,17 +615,18 @@ public static void drainAllMessages(ConnectionStringBuilder connectionStringBuil public static void drainAllSessions(ConnectionStringBuilder connectionStringBuilder, boolean isQueue) throws InterruptedException, ServiceBusException { int numParallelSessionDrains = 5; - IMessageSessionEntity sessionsClient; + Collection browsableSessions; if(isQueue) { - sessionsClient = new QueueClient(connectionStringBuilder.toString(), ReceiveMode.ReceiveAndDelete); + QueueClient qc = new QueueClient(connectionStringBuilder, ReceiveMode.ReceiveAndDelete); + browsableSessions = qc.getMessageSessions(); } else { - sessionsClient = new SubscriptionClient(connectionStringBuilder.toString(), ReceiveMode.ReceiveAndDelete); - } + SubscriptionClient sc = new SubscriptionClient(connectionStringBuilder, ReceiveMode.ReceiveAndDelete); + browsableSessions = sc.getMessageSessions(); + } - Collection browsableSessions = sessionsClient.getMessageSessions(); if(browsableSessions != null && browsableSessions.size() > 0) { CompletableFuture[] drainFutures = new CompletableFuture[numParallelSessionDrains]; diff --git a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java index 0c04ae813837..7de27d5957ea 100644 --- a/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java +++ b/azure-servicebus/src/test/java/com/microsoft/azure/servicebus/TestUtils.java @@ -21,7 +21,7 @@ public class TestUtils { //Topic and Subscription private static final String NON_PARTITIONED_TOPIC_NAME_PROPERTY = "non.partitioned.topic.name"; - private static final String SUBSCRIPTION_NAME_PROPERTY = "subscription.name"; + static final String SUBSCRIPTION_NAME_PROPERTY = "subscription.name"; //Sessionful Topic and Subscription private static final String NON_PARTITIONED_SESSIONFUL_TOPIC_NAME_PROPERTY = "session.non.partitioned.topic.name"; @@ -52,7 +52,7 @@ public class TestUtils { } } - private static String getProperty(String propertyName) + static String getProperty(String propertyName) { String defaultValue = ""; return accessProperties.getProperty(propertyName, defaultValue); From 566604623ea3635e7c2aeaf45d337c1cc84c409e Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Yarramneni Date: Mon, 15 May 2017 22:40:17 -0700 Subject: [PATCH 15/17] Adding case insensitivity to splitting subscription path --- .../azure/servicebus/SubscriptionClient.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java index 2de6a384ad0f..c7704ae8ba8d 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/SubscriptionClient.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; import com.microsoft.azure.servicebus.primitives.MessagingFactory; @@ -254,12 +255,21 @@ public void setPrefetchCount(int prefetchCount) throws ServiceBusException { @Override public String getTopicName() { String entityPath = this.getEntityPath(); - return entityPath.substring(0, entityPath.indexOf(SUBSCRIPTIONS_DELIMITER)); + String[] parts = Pattern.compile(SUBSCRIPTIONS_DELIMITER, Pattern.CASE_INSENSITIVE).split(entityPath, 2); + return parts[0]; } @Override public String getSubscriptionName() { String entityPath = this.getEntityPath(); - return entityPath.substring(entityPath.indexOf(SUBSCRIPTIONS_DELIMITER) + SUBSCRIPTIONS_DELIMITER.length()); + String[] parts = Pattern.compile(SUBSCRIPTIONS_DELIMITER, Pattern.CASE_INSENSITIVE).split(entityPath, 2); + if(parts.length == 2) + { + return parts[1]; + } + else + { + throw new RuntimeException("Invalid entity path in the subscription client."); + } } } From 90d48b51a1014fdef4298af26cc829eb49b044ff Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Thu, 25 May 2017 17:49:33 -0700 Subject: [PATCH 16/17] Fixing CBSLink and RequestResponseLink creation concurrency issues. --- .../servicebus/amqp/ConnectionHandler.java | 2 +- .../servicebus/amqp/IAmqpConnection.java | 2 +- .../primitives/CoreMessageReceiver.java | 29 +- .../primitives/CoreMessageSender.java | 26 +- .../primitives/MessagingFactory.java | 281 +++++++++--------- .../MiscRequestResponseOperationHandler.java | 28 +- 6 files changed, 193 insertions(+), 175 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java index 7858d6ad0ede..c062eee10f25 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/ConnectionHandler.java @@ -106,7 +106,7 @@ public void onConnectionRemoteOpen(Event event) TRACE_LOGGER.log(Level.FINE, "Connection.onConnectionRemoteOpen: hostname[" + event.getConnection().getHostname() + ", " + event.getConnection().getRemoteContainer() +"]"); } - this.messagingFactory.onOpenComplete(null); + this.messagingFactory.onOpenComplete(); } @Override diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java index a931739f4458..7dc50b42a8d8 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/amqp/IAmqpConnection.java @@ -9,7 +9,7 @@ public interface IAmqpConnection { - void onOpenComplete(Exception exception); + void onOpenComplete(); void onConnectionError(ErrorCondition error); diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java index f68048bc7d9b..06bcb5c9f949 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java @@ -94,6 +94,7 @@ public class CoreMessageReceiver extends ClientEntity implements IAmqpReceiver, private Instant lastKnownErrorReportedAt; private int nextCreditToFlow; private ScheduledFuture sasTokenRenewTimerFuture; + private CompletableFuture requestResponseLinkCreationFuture; private final Runnable timedOutUpdateStateRequestsDaemon; @@ -230,20 +231,20 @@ public void onEvent() private CompletableFuture createRequestResponseLink() { - synchronized (this.requestResonseLinkCreationLock) - { - if(this.requestResponseLink == null) - { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.receivePath); - CompletableFuture crateAndAssignRequestResponseLink = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); - return crateAndAssignRequestResponseLink; - } - else - { - return CompletableFuture.completedFuture(null); - } - } + synchronized (this.requestResonseLinkCreationLock) { + if(this.requestResponseLinkCreationFuture == null) + { + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.receivePath); + this.requestResponseLinkCreationFuture = + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + }); + } + } + + return this.requestResponseLinkCreationFuture; } private void createReceiveLink() diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java index 1f6711b159c4..2619f2c7c1a8 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java @@ -78,6 +78,7 @@ public class CoreMessageSender extends ClientEntity implements IAmqpSender, IErr private Exception lastKnownLinkError; private Instant lastKnownErrorReportedAt; private ScheduledFuture sasTokenRenewTimerFuture; + private CompletableFuture requestResponseLinkCreationFuture; public static CompletableFuture create( final MessagingFactory factory, @@ -123,18 +124,19 @@ public void onEvent() private CompletableFuture createRequestResponseLink() { synchronized (this.requestResonseLinkCreationLock) { - if(this.requestResponseLink == null) - { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.sendPath); - CompletableFuture crateAndAssignRequestResponseLink = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); - return crateAndAssignRequestResponseLink; - } - else - { - return CompletableFuture.completedFuture(null); - } - } + if(this.requestResponseLinkCreationFuture == null) + { + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.sendPath); + this.requestResponseLinkCreationFuture = + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + }); + } + } + + return this.requestResponseLinkCreationFuture; } private CoreMessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java index e9a21a5e056c..f313f985743d 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java @@ -43,10 +43,9 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I public static final Duration DefaultOperationTimeout = Duration.ofSeconds(30); private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); - private final Object requestResponseLinkCreationLock = new Object(); private final ConnectionStringBuilder builder; private final String hostName; - private final CompletableFuture closeTask; + private final CompletableFuture connetionCloseFuture; private final ConnectionHandler connectionHandler; private final ReactorHandler reactorHandler; private final LinkedList registeredLinks; @@ -58,8 +57,8 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I private Duration operationTimeout; private RetryPolicy retryPolicy; - private CompletableFuture open; - private CompletableFuture openConnection; + private CompletableFuture factoryOpenFuture; + private CompletableFuture cbsLinkCreationFuture; private RequestResponseLink cbsLink; /** @@ -76,11 +75,11 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I this.operationTimeout = builder.getOperationTimeout(); this.retryPolicy = builder.getRetryPolicy(); this.registeredLinks = new LinkedList(); - this.closeTask = new CompletableFuture(); + this.connetionCloseFuture = new CompletableFuture(); this.reactorLock = new Object(); this.connectionHandler = new ConnectionHandler(this); - this.open = new CompletableFuture(); - this.openConnection = new CompletableFuture(); + this.factoryOpenFuture = new CompletableFuture(); + this.cbsLinkCreationFuture = new CompletableFuture(); this.reactorHandler = new ReactorHandler() { @@ -157,9 +156,9 @@ public static CompletableFuture createFromConnectionStringBuil messagingFactory.startReactor(messagingFactory.reactorHandler); } catch (IOException e) { e.printStackTrace(); - messagingFactory.open.completeExceptionally(e); + messagingFactory.factoryOpenFuture.completeExceptionally(e); } - return messagingFactory.open; + return messagingFactory.factoryOpenFuture; } public static CompletableFuture createFromConnectionStringAsync(final String connectionString) @@ -179,70 +178,41 @@ public static MessagingFactory createFromConnectionString(final String connectio } @Override - public void onOpenComplete(Exception exception) + public void onOpenComplete() { - if (exception == null) - { - AsyncUtil.completeFuture(this.open, this); - AsyncUtil.completeFuture(this.openConnection, this.connection); - } - else - { - AsyncUtil.completeFutureExceptionally(this.open, exception); - AsyncUtil.completeFutureExceptionally(this.openConnection, exception); - } + if(!factoryOpenFuture.isDone()) + { + AsyncUtil.completeFuture(this.factoryOpenFuture, this); + } + + // Connection opened. Initiate new cbs link creation + this.createCBSLinkAsync(); } @Override public void onConnectionError(ErrorCondition error) { - if (!this.open.isDone()) + if (!this.factoryOpenFuture.isDone()) { - this.onOpenComplete(ExceptionUtil.toException(error)); + AsyncUtil.completeFutureExceptionally(this.factoryOpenFuture, ExceptionUtil.toException(error)); } else { - final Connection currentConnection = this.connection; - Link[] links = this.registeredLinks.toArray(new Link[0]); - - this.openConnection = new CompletableFuture(); - - for(Link link : links) - { - if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) - { - link.close(); - } - } - - if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) - { - currentConnection.close(); - } - - for(Link link : links) - { - Handler handler = BaseHandler.getHandler(link); - if (handler != null && handler instanceof BaseLinkHandler) - { - BaseLinkHandler linkHandler = (BaseLinkHandler) handler; - linkHandler.processOnClose(link, error); - } - } + this.closeConnection(error, null); } - if (this.getIsClosingOrClosed() && !this.closeTask.isDone()) + if (this.getIsClosingOrClosed() && !this.connetionCloseFuture.isDone()) { - this.closeTask.complete(null); + this.connetionCloseFuture.complete(null); Timer.unregister(this.getClientId()); } } private void onReactorError(Exception cause) { - if (!this.open.isDone()) + if (!this.factoryOpenFuture.isDone()) { - this.onOpenComplete(cause); + AsyncUtil.completeFutureExceptionally(this.factoryOpenFuture, cause); } else { @@ -250,8 +220,6 @@ private void onReactorError(Exception cause) { return; } - - final Connection currentConnection = this.connection; try { @@ -259,81 +227,135 @@ private void onReactorError(Exception cause) } catch (IOException e) { - TRACE_LOGGER.log(Level.SEVERE, ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error")); - + TRACE_LOGGER.log(Level.SEVERE, ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error")); this.onReactorError(cause); - } - - Link[] links = this.registeredLinks.toArray(new Link[0]); - - for(Link link : links) - { - if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) - { - link.close(); - } - } - - if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) - { - currentConnection.close(); } - - for(Link link : links) - { - Handler handler = BaseHandler.getHandler(link); - if (handler != null && handler instanceof BaseLinkHandler) - { - BaseLinkHandler linkHandler = (BaseLinkHandler) handler; - linkHandler.processOnClose(link, cause); - } - } + + this.closeConnection(null, cause); } } + + // One of the parameters must be null + private void closeConnection(ErrorCondition error, Exception cause) + { + // Important to copy the reference of the connection as a call to getConnection might create a new connection while we are still in this method + Connection currentConnection = this.connection; + if(connection != null) + { + Link[] links = this.registeredLinks.toArray(new Link[0]); + + for(Link link : links) + { + if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) + { + link.close(); + } + } + + if(this.cbsLink != null) + { + try { + this.cbsLink.close(); + } catch (ServiceBusException e) { + // Ignore this exception + } + } + + if(this.cbsLinkCreationFuture != null && !this.cbsLinkCreationFuture.isDone()) + { + AsyncUtil.completeFutureExceptionally(this.cbsLinkCreationFuture, new Exception("Connection closed.")); + } + + this.cbsLinkCreationFuture = new CompletableFuture(); + + if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) + { + currentConnection.close(); + } + + for(Link link : links) + { + Handler handler = BaseHandler.getHandler(link); + if (handler != null && handler instanceof BaseLinkHandler) + { + BaseLinkHandler linkHandler = (BaseLinkHandler) handler; + if(error != null) + { + linkHandler.processOnClose(link, error); + } + else + { + linkHandler.processOnClose(link, cause); + } + } + } + } + } @Override protected CompletableFuture onClose() { if (!this.getIsClosed()) { - if (this.connection != null && this.connection.getRemoteState() != EndpointState.CLOSED) - { - try { - this.scheduleOnReactorThread(new DispatchHandler() - { - @Override - public void onEvent() - { - if (MessagingFactory.this.connection != null && MessagingFactory.this.connection.getLocalState() != EndpointState.CLOSED) - { - MessagingFactory.this.connection.close(); - } - } - }); - } catch (IOException e) { - AsyncUtil.completeFutureExceptionally(this.closeTask, e); - } - - Timer.schedule(new Runnable() - { - @Override - public void run() - { - if (!MessagingFactory.this.closeTask.isDone()) - { - MessagingFactory.this.closeTask.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); - } - } - }, - this.operationTimeout, TimerType.OneTimeRun); - } - else if(this.connection == null || this.connection.getRemoteState() == EndpointState.CLOSED) - { - this.closeTask.complete(null); - } + CompletableFuture cbsLinkCloseFuture; + if(this.cbsLink == null) + { + cbsLinkCloseFuture = CompletableFuture.completedFuture(null); + } + else + { + cbsLinkCloseFuture = this.cbsLink.closeAsync(); + } + + cbsLinkCloseFuture.thenRun(() -> { + if(this.cbsLinkCreationFuture != null && !this.cbsLinkCreationFuture.isDone()) + { + AsyncUtil.completeFutureExceptionally(this.cbsLinkCreationFuture, new Exception("Connection closed.")); + } + + if (this.connection != null && this.connection.getRemoteState() != EndpointState.CLOSED) + { + try { + this.scheduleOnReactorThread(new DispatchHandler() + { + @Override + public void onEvent() + { + if (MessagingFactory.this.connection != null && MessagingFactory.this.connection.getLocalState() != EndpointState.CLOSED) + { + MessagingFactory.this.connection.close(); + } + } + }); + } catch (IOException e) { + AsyncUtil.completeFutureExceptionally(this.connetionCloseFuture, e); + } + + Timer.schedule(new Runnable() + { + @Override + public void run() + { + if (!MessagingFactory.this.connetionCloseFuture.isDone()) + { + MessagingFactory.this.connetionCloseFuture.completeExceptionally(new TimeoutException("Closing MessagingFactory timed out.")); + } + } + }, + this.operationTimeout, TimerType.OneTimeRun); + } + else if(this.connection == null || this.connection.getRemoteState() == EndpointState.CLOSED) + { + this.connetionCloseFuture.complete(null); + } + }); + + return this.connetionCloseFuture; + } + else + { + return CompletableFuture.completedFuture(null); } - - return this.closeTask; } private class RunReactor implements Runnable @@ -450,7 +472,7 @@ CompletableFuture> sendSASTokenAndSetRenewTimer(String sasTok final String finalSasToken = sasToken; final boolean finalIsSasTokenGenerated = isSasTokenGenerated; - CompletableFuture sendTokenFuture = this.createCBSLink().thenComposeAsync((v) -> { + CompletableFuture sendTokenFuture = this.cbsLinkCreationFuture.thenComposeAsync((v) -> { return CommonRequestResponseOperations.sendCBSTokenAsync(this.cbsLink, Util.adjustServerTimeout(this.operationTimeout), finalSasToken, ClientConstants.SAS_TOKEN_TYPE, sasTokenAudienceURI); }); return sendTokenFuture.thenApplyAsync((v) -> { @@ -468,20 +490,11 @@ CompletableFuture> sendSASTokenAndSetRenewTimer(String sasTok }); } - private CompletableFuture createCBSLink() + private CompletableFuture createCBSLinkAsync() { - synchronized (this.requestResponseLinkCreationLock) { - if(this.cbsLink == null) - { - String requestResponseLinkPath = RequestResponseLink.getCBSNodeLinkPath(); - CompletableFuture crateAndAssignRequestResponseLink = - RequestResponseLink.createAsync(this, this.getClientId() + "-cbs", requestResponseLinkPath).thenAccept((rrlink) -> {this.cbsLink = rrlink;}); - return crateAndAssignRequestResponseLink; - } - else - { - return CompletableFuture.completedFuture(null); - } - } + String requestResponseLinkPath = RequestResponseLink.getCBSNodeLinkPath(); + CompletableFuture crateAndAssignRequestResponseLink = + RequestResponseLink.createAsync(this, this.getClientId() + "-cbs", requestResponseLinkPath).thenAcceptAsync((rrlink) -> {this.cbsLink = rrlink; this.cbsLinkCreationFuture.complete(null);}); + return crateAndAssignRequestResponseLink; } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java index e4aae24035ee..8b4638f5fdaf 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java @@ -15,6 +15,7 @@ public final class MiscRequestResponseOperationHandler extends ClientEntity private final String entityPath; private final MessagingFactory underlyingFactory; private RequestResponseLink requestResponseLink; + private CompletableFuture requestResponseLinkCreationFuture; private MiscRequestResponseOperationHandler(MessagingFactory factory, String linkName, String entityPath) { @@ -39,19 +40,20 @@ protected CompletableFuture onClose() { private CompletableFuture createRequestResponseLink() { - synchronized (this.requestResonseLinkCreationLock) { - if(this.requestResponseLink == null) - { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.entityPath); - CompletableFuture crateAndAssignRequestResponseLink = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAccept((rrlink) -> {this.requestResponseLink = rrlink;}); - return crateAndAssignRequestResponseLink; - } - else - { - return CompletableFuture.completedFuture(null); - } - } + synchronized (this.requestResonseLinkCreationLock) { + if(this.requestResponseLinkCreationFuture == null) + { + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.entityPath); + this.requestResponseLinkCreationFuture = + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + }); + } + } + + return this.requestResponseLinkCreationFuture; } public CompletableFuture> getMessageSessionsAsync(Date lastUpdatedTime, int skip, int top, String lastSessionId) From 0ef36eb82d67a9027866698d56fdc7767bd33cbe Mon Sep 17 00:00:00 2001 From: Vijaya Gopal Date: Fri, 26 May 2017 17:47:36 -0700 Subject: [PATCH 17/17] Bug fixes. --- .../primitives/CoreMessageReceiver.java | 33 ++++-- .../primitives/CoreMessageSender.java | 41 ++++--- .../primitives/MessagingFactory.java | 33 +++++- .../MiscRequestResponseOperationHandler.java | 105 +++++++++++++++--- 4 files changed, 170 insertions(+), 42 deletions(-) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java index 06bcb5c9f949..9f57412b9201 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageReceiver.java @@ -234,17 +234,30 @@ private CompletableFuture createRequestResponseLink() synchronized (this.requestResonseLinkCreationLock) { if(this.requestResponseLinkCreationFuture == null) { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.receivePath); - this.requestResponseLinkCreationFuture = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> - { - this.requestResponseLink = rrlink; - this.requestResponseLinkCreationFuture.complete(null); - }); + this.requestResponseLinkCreationFuture = new CompletableFuture(); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.receivePath); + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).handleAsync((rrlink, ex) -> + { + if(ex == null) + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + } + else + { + this.requestResponseLinkCreationFuture.completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(ex)); + // Set it to null so next call will retry rr link creation + synchronized (this.requestResonseLinkCreationLock) + { + this.requestResponseLinkCreationFuture = null; + } + } + return null; + }); } - } - - return this.requestResponseLinkCreationFuture; + + return this.requestResponseLinkCreationFuture; + } } private void createReceiveLink() diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java index 2619f2c7c1a8..1269e229cf6a 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/CoreMessageSender.java @@ -122,21 +122,34 @@ public void onEvent() } private CompletableFuture createRequestResponseLink() - { + { synchronized (this.requestResonseLinkCreationLock) { - if(this.requestResponseLinkCreationFuture == null) - { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.sendPath); - this.requestResponseLinkCreationFuture = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> - { - this.requestResponseLink = rrlink; - this.requestResponseLinkCreationFuture.complete(null); - }); - } - } - - return this.requestResponseLinkCreationFuture; + if(this.requestResponseLinkCreationFuture == null) + { + this.requestResponseLinkCreationFuture = new CompletableFuture(); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.sendPath); + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).handleAsync((rrlink, ex) -> + { + if(ex == null) + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + } + else + { + this.requestResponseLinkCreationFuture.completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(ex)); + // Set it to null so next call will retry rr link creation + synchronized (this.requestResonseLinkCreationLock) + { + this.requestResponseLinkCreationFuture = null; + } + } + return null; + }); + } + + return this.requestResponseLinkCreationFuture; + } } private CoreMessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java index f313f985743d..38e9a1a83041 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MessagingFactory.java @@ -43,6 +43,7 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I public static final Duration DefaultOperationTimeout = Duration.ofSeconds(30); private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private static final int MAX_CBS_LINK_CREATION_ATTEMPTS = 3; private final ConnectionStringBuilder builder; private final String hostName; private final CompletableFuture connetionCloseFuture; @@ -60,6 +61,8 @@ public class MessagingFactory extends ClientEntity implements IAmqpConnection, I private CompletableFuture factoryOpenFuture; private CompletableFuture cbsLinkCreationFuture; private RequestResponseLink cbsLink; + private int cbsLinkCreationAttempts = 0; + private Throwable lastCBSLinkCreationException = null; /** * @param reactor parameter reactor is purely for testing purposes and the SDK code should always set it to null @@ -492,9 +495,31 @@ CompletableFuture> sendSASTokenAndSetRenewTimer(String sasTok private CompletableFuture createCBSLinkAsync() { - String requestResponseLinkPath = RequestResponseLink.getCBSNodeLinkPath(); - CompletableFuture crateAndAssignRequestResponseLink = - RequestResponseLink.createAsync(this, this.getClientId() + "-cbs", requestResponseLinkPath).thenAcceptAsync((rrlink) -> {this.cbsLink = rrlink; this.cbsLinkCreationFuture.complete(null);}); - return crateAndAssignRequestResponseLink; + if(++this.cbsLinkCreationAttempts > MAX_CBS_LINK_CREATION_ATTEMPTS ) + { + Throwable completionEx = this.lastCBSLinkCreationException == null ? new Exception("CBS link creation failed multiple times.") : this.lastCBSLinkCreationException; + this.cbsLinkCreationFuture.completeExceptionally(completionEx); + return CompletableFuture.completedFuture(null); + } + else + { + String requestResponseLinkPath = RequestResponseLink.getCBSNodeLinkPath(); + CompletableFuture crateAndAssignRequestResponseLink = + RequestResponseLink.createAsync(this, this.getClientId() + "-cbs", requestResponseLinkPath).handleAsync((cbsLink, ex) -> + { + if(ex == null) + { + this.cbsLink = cbsLink; + this.cbsLinkCreationFuture.complete(null); + } + else + { + this.lastCBSLinkCreationException = ExceptionUtil.extractAsyncCompletionCause(ex); + this.createCBSLinkAsync(); + } + return null; + }); + return crateAndAssignRequestResponseLink; + } } } diff --git a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java index 8b4638f5fdaf..e4a1d1cf2215 100644 --- a/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java +++ b/azure-servicebus/src/main/java/com/microsoft/azure/servicebus/primitives/MiscRequestResponseOperationHandler.java @@ -1,9 +1,14 @@ package com.microsoft.azure.servicebus.primitives; +import java.time.ZonedDateTime; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.qpid.proton.message.Message; @@ -11,11 +16,15 @@ public final class MiscRequestResponseOperationHandler extends ClientEntity { + private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE); + private final Object requestResonseLinkCreationLock = new Object(); private final String entityPath; + private final String sasTokenAudienceURI; private final MessagingFactory underlyingFactory; private RequestResponseLink requestResponseLink; private CompletableFuture requestResponseLinkCreationFuture; + private ScheduledFuture sasTokenRenewTimerFuture; private MiscRequestResponseOperationHandler(MessagingFactory factory, String linkName, String entityPath) { @@ -23,37 +32,105 @@ private MiscRequestResponseOperationHandler(MessagingFactory factory, String lin this.underlyingFactory = factory; this.entityPath = entityPath; + this.sasTokenAudienceURI = String.format(ClientConstants.SAS_TOKEN_AUDIENCE_FORMAT, factory.getHostName(), entityPath); } - public static CompletableFuture create( - MessagingFactory factory, - String entityPath) + public static CompletableFuture create(MessagingFactory factory, String entityPath) { - MiscRequestResponseOperationHandler sessionBrowser = new MiscRequestResponseOperationHandler(factory, StringUtil.getShortRandomString(), entityPath); - return CompletableFuture.completedFuture(sessionBrowser); + CompletableFuture creationFuture = new CompletableFuture(); + MiscRequestResponseOperationHandler requestResponseOperationHandler = new MiscRequestResponseOperationHandler(factory, StringUtil.getShortRandomString(), entityPath); + requestResponseOperationHandler.sendSASTokenAndSetRenewTimer().handleAsync((v, ex) -> { + if(ex == null) + { + creationFuture.complete(requestResponseOperationHandler); + } + else + { + creationFuture.completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(ex)); + } + return null; + }); + + Timer.schedule( + new Runnable() + { + public void run() + { + if (!creationFuture.isDone()) + { + requestResponseOperationHandler.cancelSASTokenRenewTimer(); + Exception operationTimedout = new TimeoutException( + String.format(Locale.US, "Open operation on CBSLink(%s) on Entity(%s) timed out at %s.", requestResponseOperationHandler.getClientId(), requestResponseOperationHandler.entityPath, ZonedDateTime.now().toString())); + if (TRACE_LOGGER.isLoggable(Level.WARNING)) + { + TRACE_LOGGER.log(Level.WARNING, operationTimedout.getMessage()); + } + + creationFuture.completeExceptionally(operationTimedout); + } + } + } + , factory.getOperationTimeout() + , TimerType.OneTimeRun); + return creationFuture; } @Override protected CompletableFuture onClose() { + this.cancelSASTokenRenewTimer(); return this.requestResponseLink == null ? CompletableFuture.completedFuture(null) : this.requestResponseLink.closeAsync(); } + CompletableFuture sendSASTokenAndSetRenewTimer() + { + if(this.getIsClosingOrClosed()) + { + return CompletableFuture.completedFuture(null); + } + else + { + CompletableFuture> sendTokenFuture = this.underlyingFactory.sendSASTokenAndSetRenewTimer(this.sasTokenAudienceURI, () -> this.sendSASTokenAndSetRenewTimer()); + return sendTokenFuture.thenAccept((f) -> {this.sasTokenRenewTimerFuture = f;}); + } + } + + private void cancelSASTokenRenewTimer() + { + if(this.sasTokenRenewTimerFuture != null && !this.sasTokenRenewTimerFuture.isDone()) + { + this.sasTokenRenewTimerFuture.cancel(true); + } + } + private CompletableFuture createRequestResponseLink() { synchronized (this.requestResonseLinkCreationLock) { if(this.requestResponseLinkCreationFuture == null) { - String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.entityPath); - this.requestResponseLinkCreationFuture = - RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).thenAcceptAsync((rrlink) -> - { - this.requestResponseLink = rrlink; - this.requestResponseLinkCreationFuture.complete(null); - }); + this.requestResponseLinkCreationFuture = new CompletableFuture(); + String requestResponseLinkPath = RequestResponseLink.getManagementNodeLinkPath(this.entityPath); + RequestResponseLink.createAsync(this.underlyingFactory, this.getClientId() + "-RequestResponse", requestResponseLinkPath).handleAsync((rrlink, ex) -> + { + if(ex == null) + { + this.requestResponseLink = rrlink; + this.requestResponseLinkCreationFuture.complete(null); + } + else + { + this.requestResponseLinkCreationFuture.completeExceptionally(ExceptionUtil.extractAsyncCompletionCause(ex)); + // Set it to null so next call will retry rr link creation + synchronized (this.requestResonseLinkCreationLock) + { + this.requestResponseLinkCreationFuture = null; + } + } + return null; + }); } + + return this.requestResponseLinkCreationFuture; } - - return this.requestResponseLinkCreationFuture; } public CompletableFuture> getMessageSessionsAsync(Date lastUpdatedTime, int skip, int top, String lastSessionId)