Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Communication] - SMS - Refactor SMS SDK and add *withResponse methods #19666

Merged
merged 6 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions sdk/communication/azure-communication-sms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +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)`
- Updated `public Mono<SendSmsResponse> send(PhoneNumberIdentifier from, PhoneNumberIdentifier to, String message)` to `public Mono<SendSmsResponse> send(PhoneNumberIdentifier from, String to, String message)`
- Replaced `SendSmsResponse` with `SmsSendResult`


## 1.0.0-beta.4 (Skipped)
### Added
- Added Azure Active Directory authentication support
Expand All @@ -21,7 +20,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok using the DefaultAzureCredential as our example, but this statement makes me think we can only use DefaultAzureCredential. The .net readme has "SMS clients can also be authenticated using a valid token credential." So maybe something more like that?


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be before instead of after the auth section


## 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