Skip to content

Commit

Permalink
[Communication] - SMS - Refactor SMS SDK and add *withResponse methods (
Browse files Browse the repository at this point in the history
#19666)

* Refactor SMS Clients and Tests

* Updating readme and refactoring builder

* Fix readme

* Fix readme

* Address review comments

* Address review comments

Co-authored-by: Minnie Liu <peiliu@microsoft.com>
  • Loading branch information
minnieliu and Minnie Liu authored Mar 6, 2021
1 parent 59319d2 commit 536abd6
Show file tree
Hide file tree
Showing 46 changed files with 813 additions and 842 deletions.
10 changes: 5 additions & 5 deletions sdk/communication/azure-communication-sms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Release History
## 1.0.0-beta.5 (Unreleased)
###Added
### Added
- Support for creating SmsClient with TokenCredential.
- Added support for 1:N SMS messaging.
- Added support for tagging SMS messages.
- Send method series in SmsClient are idempotent under retry policy.
- Added `SmsOptions`

### Breaking Change
- Updated `public Mono<SendSmsResponse> sendMessage(PhoneNumberIdentifier from, PhoneNumberIdentifier to, String message)` to `public Mono<SendSmsResponse> sendMessage(PhoneNumberIdentifier from,List<PhoneNumberIdentifier> to, String message)`
- Replaced `SendSmsResponse` with `SmsSendResult`

- Updated `public Mono<SendSmsResponse> sendMessage(PhoneNumberIdentifier from, PhoneNumberIdentifier to, String message)` to `public Mono<SendSmsResponse> send(String from, String to, String message)`.
- Updated `public Mono<Response<SendSmsResponse>> sendMessageWithResponse(PhoneNumberIdentifier from,List<PhoneNumberIdentifier> to, String message, SendSmsOptions smsOptions, Context context)` to `Mono<Response<SmsSendResult>> sendWithResponse(String from, String to, String message, SmsSendOptions options, Context context)`.
- Replaced `SendSmsResponse` with `SmsSendResult`.

## 1.0.0-beta.4 (Skipped)
### Added
Expand All @@ -21,7 +21,7 @@
- Support directly passing connection string to the SmsClientBuilder using connectionString().

### Breaking Change
- Removed credential(CommunicationClientCredential credential) and replaced with
- Removed credential(CommunicationClientCredential credential) and replaced with
accessKey(String accessKey) within CommunicationIdentityClientBuilder.

## 1.0.0-beta.2 (2020-10-06)
Expand Down
134 changes: 79 additions & 55 deletions sdk/communication/azure-communication-sms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,102 +21,126 @@ Azure Communication SMS is used to send simple text messages.
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-sms</artifactId>
<version>1.0.0-beta.4</version>
<version>1.0.0-beta.4</version>
</dependency>
```

## Key concepts

There are two different forms of authentication to use the Azure Communication SMS Service.
## Authenticate the client

### Azure Active Directory Token Authentication
A `DefaultAzureCredential` object must be passed to the `SmsClientBuilder` via the credential() function. Endpoint and httpClient must also be set via the endpoint() and httpClient() functions respectively.

The `DefaultAzureCredential` object must be passed to the `SmsClientBuilder` via
the credential() funtion. Endpoint and httpClient must also be set
via the endpoint() and httpClient() functions respectively.

`AZURE_CLIENT_SECRET`, `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` environment variables
`AZURE_CLIENT_SECRET`, `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` environment variables
are needed to create a DefaultAzureCredential object.

To create a SmsClient
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L22-L35 -->
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L50-L60 -->
```java
// You can find your endpoint and access key from your resource in the Azure Portal
String endpoint = "https://<RESOURCE_NAME>.communication.azure.com";
//Enter your azureKeyCredential
AzureKeyCredential azureKeyCredential = new AzureKeyCredential("SECRET");
// Instantiate the http client

// Create an HttpClient builder of your choice and customize it
HttpClient httpClient = new NettyAsyncHttpClientBuilder().build();
// Create a new SmsClientBuilder to instantiate an SmsClient
SmsClientBuilder smsClientBuilder = new SmsClientBuilder();
// Set the endpoint, access key, and the HttpClient
smsClientBuilder.endpoint(endpoint)
.credential(azureKeyCredential)
.httpClient(httpClient);
// Build a new SmsClient
SmsClient smsClient = smsClientBuilder.buildClient();

SmsClient smsClient = new SmsClientBuilder()
.endpoint(endpoint)
.credential(new DefaultAzureCredentialBuilder().build())
.httpClient(httpClient)
.buildClient();
```

Alternatively, you can provide the entire connection string using the connectionString() function instead of providing the endpoint and access key.
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L39-L46 -->
### Access Key Authentication
SMS uses HMAC authentication with the resource access key.
The access key must be provided to the `SmsClientBuilder` via the credential() function. Endpoint and httpClient must also be set via the endpoint() and httpClient() functions respectively.

<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L18-L29 -->
```java
// You can find your endpoint and access key from your resource in the Azure Portal
String endpoint = "https://<resource-name>.communication.azure.com";
AzureKeyCredential azureKeyCredential = new AzureKeyCredential("<access-key>");

// Create an HttpClient builder of your choice and customize it
HttpClient httpClient = new NettyAsyncHttpClientBuilder().build();

SmsClient smsClient = new SmsClientBuilder()
.endpoint(endpoint)
.credential(azureKeyCredential)
.httpClient(httpClient)
.buildClient();
```

Alternatively, you can provide the entire connection string using the connectionString() function instead of providing the endpoint and access key.
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L35-L44 -->
```java
// You can find your connection string from your resource in the Azure Portal
String connectionString = "<connection_string>";
String connectionString = "https://<resource-name>.communication.azure.com/;<access-key>";

// Create an HttpClient builder of your choice and customize it
HttpClient httpClient = new NettyAsyncHttpClientBuilder().build();

SmsClient smsClient = new SmsClientBuilder()
.connectionString(connectionString)
.httpClient(httpClient)
.buildClient();
```

## Key concepts

There are two different forms of authentication to use the Azure Communication SMS Service.

## Examples

### Sending a message to a single recipient
Use the `send` function to send a new message to a phone number.
Once you send the message, you'll receive a response where you can access several
properties such as the message id with the `messageResponseItem.getMessageId()` function.
### Send a 1:1 SMS Message
Use the `send` or `sendWithResponse` function to send a SMS message to a single phone number.

<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L50-L60 -->
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L68-L75 -->
```java
//Send an sms to only one phone number
SmsSendOptions options = new SmsSendOptions();
options.setDeliveryReportEnabled(true);
options.setTag("Tag"); /* Optional */
// Send the message to a list of phone Numbers and check the response for a messages ids
SmsSendResult response = smsClient.send(
SmsSendResult sendResult = smsClient.send(
"<from-phone-number>",
"<to-phone-number>",
"your message",
options /* Optional */);
System.out.println("MessageId: " + response.getMessageId());
"Hi");

System.out.println("Message Id: " + sendResult.getMessageId());
System.out.println("Recipient Number: " + sendResult.getTo());
System.out.println("Send Result Successful:" + sendResult.isSuccessful());
```
### Sending a message to multiple recipients
Use the `send` function to send a new message to a list of phone numbers.
Once you send the message, you'll receive a PagedIterable response where you can access several
properties such as the message id with the `messageResponseItem.getMessageId()` function.
### Send a 1:N SMS Message
To send a SMS message to a list of recipients, call the `send` or `sendWithResponse` function with a list of recipient phone numbers. You may also add pass in an options object to specify whether the delivery report should be enabled and set custom tags.

<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L64-L76 -->
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L81-L96 -->
```java
//Send an sms to multiple phone numbers
SmsSendOptions options = new SmsSendOptions();
options.setDeliveryReportEnabled(true);
options.setTag("Tag"); /* Optional */
// Send the message to a list of phone Numbers and check the response for a messages ids
Iterable<SmsSendResult> responseMultiplePhones = smsClient.send(
options.setTag("Tag");

Iterable<SmsSendResult> sendResults = smsClient.sendWithResponse(
"<from-phone-number>",
new ArrayList<String>(Arrays.asList("<to-phone-number1>", "<to-phone-number2>")),
"your message",
Arrays.asList("<to-phone-number1>", "<to-phone-number2>"),
"Hi",
options /* Optional */,
null);
for (SmsSendResult messageResponseItem : responseMultiplePhones) {
System.out.println("MessageId sent to " + messageResponseItem.getTo() + ": " + messageResponseItem.getMessageId());
```
Context.NONE).getValue();

for (SmsSendResult result : sendResults) {
System.out.println("Message Id: " + result.getMessageId());
System.out.println("Recipient Number: " + result.getTo());
System.out.println("Send Result Successful:" + result.isSuccessful());
}
```

## Troubleshooting

In progress.
All SMS service operations will throw an exception on failure.
<!-- embedme src/samples/java/com/azure/communication/sms/samples/quickstart/ReadmeSamples.java#L104-L112 -->
```java
try {
SmsSendResult sendResult = smsClient.send(
"<from-phone-number>",
"<to-phone-number>",
"Hi"
);
} catch (RuntimeException ex) {
System.out.println(ex.getMessage());
}
```

## Next steps

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.azure.communication.sms;

import com.azure.communication.sms.implementation.AzureCommunicationSMSServiceImpl;
import com.azure.communication.sms.implementation.SmsImpl;
import com.azure.communication.sms.implementation.models.SmsSendResponseItem;
import com.azure.communication.sms.implementation.models.SendMessageRequest;
import com.azure.communication.sms.implementation.models.SmsRecipient;
Expand All @@ -13,9 +14,12 @@
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.http.rest.Response;
import com.azure.core.http.rest.SimpleResponse;
import com.azure.core.util.Context;
import com.azure.core.util.logging.ClientLogger;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import reactor.core.publisher.Mono;
import static com.azure.core.util.FluxUtil.monoError;
Expand All @@ -26,16 +30,15 @@
*/
@ServiceClient(builder = SmsClientBuilder.class, isAsync = true)
public final class SmsAsyncClient {
private final AzureCommunicationSMSServiceImpl smsServiceClient;
private final SmsImpl smsClient;
private final ClientLogger logger = new ClientLogger(SmsAsyncClient.class);

SmsAsyncClient(AzureCommunicationSMSServiceImpl smsServiceClient) {
this.smsServiceClient = smsServiceClient;
smsClient = smsServiceClient.getSms();
}

/**
* Sends an SMS message from a phone number that belongs to the authenticated account.
* Phone number has to be in the format 000 - 00 - 00
*
* @param from Number that is sending the message.
* @param to The recipient's phone number.
Expand All @@ -44,7 +47,9 @@ public final class SmsAsyncClient {
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<SmsSendResult> send(String from, String to, String message) {
return send(from, to, message, null);
return sendWithResponse(from, to, message, null, null).flatMap(response -> {
return Mono.just(response.getValue());
});
}

/**
Expand All @@ -53,30 +58,31 @@ public Mono<SmsSendResult> send(String from, String to, String message) {
* @param from Number that is sending the message.
* @param to The recipient's phone number.
* @param message message to send to recipient.
* @param smsOptions set options on the SMS request, like enable delivery report, which sends a report
* @param options set options on the SMS request, like enable delivery report, which sends a report
* for this message to the Azure Resource Event Grid.
* @return response for a successful send Sms request.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<SmsSendResult> send(String from, String to, String message, SmsSendOptions smsOptions) {
List<String> recipients = new ArrayList<String>();
recipients.add(to);
SendMessageRequest request = createSendMessageRequest(from, recipients, message, smsOptions);
public Mono<Response<SmsSendResult>> sendWithResponse(String from, String to, String message, SmsSendOptions options) {
return sendWithResponse(from, to, message, options, null);
}

Mono<Response<SmsSendResult>> sendWithResponse(String from, String to, String message, SmsSendOptions options, Context context) {
try {
Objects.requireNonNull(from, "'from' cannot be null.");
Objects.requireNonNull(to, "'to' cannot be null.");
Mono<Response<SmsSendResponse>> responseMono = withContext(context -> this.smsServiceClient.getSms().sendWithResponseAsync(request, context));
Response<SmsSendResponse> response = responseMono.block();
SmsSendResponse smsSendResponse = response.getValue();

List<SmsSendResult> result = convertSmsResults(smsSendResponse.getValue());
if (result.size() == 1) {
return Mono.just(result.get(0));
} else {
return monoError(logger, new NullPointerException("no response"));
}
} catch (NullPointerException ex) {
return monoError(logger, ex);
List<String> recipients = Arrays.asList(to);
SendMessageRequest request = createSendMessageRequest(from, recipients, message, options);
return withContext(contextValue -> {
if (context != null) {
contextValue = context;
}
return smsClient.sendWithResponseAsync(request, contextValue)
.flatMap((Response<SmsSendResponse> response) -> {
List<SmsSendResult> smsSendResults = convertSmsSendResults(response.getValue().getValue());
return Mono.just(new SimpleResponse<SmsSendResult>(response, smsSendResults.get(0)));
});
});
} catch (RuntimeException ex) {
return monoError(logger, ex);
}
Expand All @@ -93,7 +99,10 @@ public Mono<SmsSendResult> send(String from, String to, String message, SmsSendO
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Iterable<SmsSendResult>> send(String from, Iterable<String> to, String message) {
return send(from, to, message, null);
return sendWithResponse(from, to, message, null)
.flatMap((Response<Iterable<SmsSendResult>> response) -> {
return Mono.just(response.getValue());
});
}

/**
Expand All @@ -102,38 +111,44 @@ public Mono<Iterable<SmsSendResult>> send(String from, Iterable<String> to, Stri
* @param from Number that is sending the message.
* @param to A list of the recipient's phone numbers.
* @param message message to send to recipient.
* @param smsOptions set options on the SMS request, like enable delivery report, which sends a report
* @param options set options on the SMS request, like enable delivery report, which sends a report
* for this message to the Azure Resource Event Grid.
* @return response for a successful send Sms request.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Iterable<SmsSendResult>> send(String from, Iterable<String> to, String message, SmsSendOptions smsOptions) {
SendMessageRequest request = createSendMessageRequest(from, to, message, smsOptions);
public Mono<Response<Iterable<SmsSendResult>>> sendWithResponse(String from, Iterable<String> to, String message, SmsSendOptions options) {
return sendWithResponse(from, to, message, options, null);
}

Mono<Response<Iterable<SmsSendResult>>> sendWithResponse(String from, Iterable<String> to, String message, SmsSendOptions options, Context context) {
try {
Objects.requireNonNull(from, "'from' cannot be null.");
Objects.requireNonNull(to, "'to' cannot be null.");
Mono<Response<SmsSendResponse>> responseMono = withContext(context -> this.smsServiceClient.getSms().sendWithResponseAsync(request, context));
Response<SmsSendResponse> response = responseMono.block();
SmsSendResponse smsSendResponse = response.getValue();
List<SmsSendResult> result = convertSmsResults(smsSendResponse.getValue());
return Mono.just(result);
} catch (NullPointerException ex) {
return monoError(logger, ex);
SendMessageRequest request = createSendMessageRequest(from, to, message, options);
return withContext(contextValue -> {
if (context != null) {
contextValue = context;
}
return this.smsClient.sendWithResponseAsync(request, contextValue)
.flatMap((Response<SmsSendResponse> response) -> {
Iterable<SmsSendResult> smsSendResults = convertSmsSendResults(response.getValue().getValue());
return Mono.just(new SimpleResponse<Iterable<SmsSendResult>>(response, smsSendResults));
});
});
} catch (RuntimeException ex) {
return monoError(logger, ex);
}
}

private List<SmsSendResult> convertSmsResults(Iterable<SmsSendResponseItem> resultsIterable) {
List <SmsSendResult> iterableWrapper = new ArrayList<>();
for (SmsSendResponseItem item : resultsIterable
) {
private List<SmsSendResult> convertSmsSendResults(Iterable<SmsSendResponseItem> resultsIterable) {
List<SmsSendResult> iterableWrapper = new ArrayList<>();
for (SmsSendResponseItem item : resultsIterable) {
iterableWrapper.add(new SmsSendResult(item));
}
return iterableWrapper;
}

private SendMessageRequest createSendMessageRequest(String from, Iterable<String> smsRecipient, String message, SmsSendOptions smsOptions) {
private SendMessageRequest createSendMessageRequest(String from, Iterable<String> smsRecipient, String message, SmsSendOptions options) {
SendMessageRequest request = new SendMessageRequest();
List<SmsRecipient> recipients = new ArrayList<SmsRecipient>();
for (String s : smsRecipient) {
Expand All @@ -142,7 +157,7 @@ private SendMessageRequest createSendMessageRequest(String from, Iterable<String
request.setFrom(from)
.setSmsRecipients(recipients)
.setMessage(message)
.setSmsSendOptions(smsOptions);
.setSmsSendOptions(options);
return request;
}
}
Loading

0 comments on commit 536abd6

Please sign in to comment.