diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationAsyncClient.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationAsyncClient.java index b589f9b62152..870da779d948 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationAsyncClient.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallAutomationAsyncClient.java @@ -125,14 +125,12 @@ Mono> createCallWithResponseInternal(CreateCallOption try { context = context == null ? Context.NONE : context; CreateCallRequestInternal request = getCreateCallRequestInternal(createCallOptions); - if (createCallOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - createCallOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + + createCallOptions.setRepeatabilityHeaders(handleApiIdempotency(createCallOptions.getRepeatabilityHeaders())); return serverCallingInternal.createCallWithResponseAsync(request, - createCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - createCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + createCallOptions.getRepeatabilityHeaders() != null ? createCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + createCallOptions.getRepeatabilityHeaders() != null ? createCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> { @@ -227,10 +225,7 @@ Mono> answerCallWithResponseInternal(AnswerCallOption .setIncomingCallContext(answerCallOptions.getIncomingCallContext()) .setCallbackUri(answerCallOptions.getCallbackUrl()); - if (answerCallOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - answerCallOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + answerCallOptions.setRepeatabilityHeaders(handleApiIdempotency(answerCallOptions.getRepeatabilityHeaders())); if (answerCallOptions.getMediaStreamingConfiguration() != null) { MediaStreamingConfigurationInternal mediaStreamingConfigurationInternal = @@ -241,8 +236,8 @@ Mono> answerCallWithResponseInternal(AnswerCallOption return serverCallingInternal.answerCallWithResponseAsync(request, - answerCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - answerCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + answerCallOptions.getRepeatabilityHeaders() != null ? answerCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + answerCallOptions.getRepeatabilityHeaders() != null ? answerCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> { @@ -296,14 +291,11 @@ Mono> redirectCallWithResponseInternal(RedirectCallOptions redire .setIncomingCallContext(redirectCallOptions.getIncomingCallContext()) .setTarget(CommunicationIdentifierConverter.convert(redirectCallOptions.getTarget())); - if (redirectCallOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - redirectCallOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + redirectCallOptions.setRepeatabilityHeaders(handleApiIdempotency(redirectCallOptions.getRepeatabilityHeaders())); return serverCallingInternal.redirectCallWithResponseAsync(request, - redirectCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - redirectCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + redirectCallOptions.getRepeatabilityHeaders() != null ? redirectCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + redirectCallOptions.getRepeatabilityHeaders() != null ? redirectCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create); } catch (RuntimeException ex) { @@ -348,14 +340,11 @@ Mono> rejectCallWithResponseInternal(RejectCallOptions rejectCall request.setCallRejectReason(CallRejectReasonInternal.fromString(rejectCallOptions.getCallRejectReason().toString())); } - if (rejectCallOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - rejectCallOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + rejectCallOptions.setRepeatabilityHeaders(handleApiIdempotency(rejectCallOptions.getRepeatabilityHeaders())); return serverCallingInternal.rejectCallWithResponseAsync(request, - rejectCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - rejectCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + rejectCallOptions.getRepeatabilityHeaders() != null ? rejectCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + rejectCallOptions.getRepeatabilityHeaders() != null ? rejectCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create); } catch (RuntimeException ex) { @@ -387,4 +376,24 @@ public CallRecordingAsync getCallRecordingAsync() { contentDownloader, httpPipelineInternal, resourceEndpoint); } //endregion + + //region helper functions + /*** + * Make sure repeatability headers of the request are correctly set. + * + * @return a verified RepeatabilityHeaders object. + */ + static RepeatabilityHeaders handleApiIdempotency(RepeatabilityHeaders repeatabilityHeaders) { + // This case means user did not disable idempotency + if (repeatabilityHeaders != null) { + // This means user never set the repeatability headers manually. + if (repeatabilityHeaders.getRepeatabilityRequestId().equals(UUID.fromString("0-0-0-0-0")) + && repeatabilityHeaders.getRepeatabilityFirstSent() == Instant.MIN) { + repeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); + } // Else do nothing, use the repeatability headers that user specified. + } // Else do nothing, since the user disabled idempotency. Leave it as null. + + return repeatabilityHeaders; + } + //endregion } diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallConnectionAsync.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallConnectionAsync.java index e2a07422ef2e..7de915974b71 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallConnectionAsync.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallConnectionAsync.java @@ -42,9 +42,7 @@ import reactor.core.publisher.Mono; import java.net.URISyntaxException; -import java.time.Instant; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; import static com.azure.core.util.FluxUtil.monoError; @@ -142,14 +140,11 @@ Mono> hangUpWithResponseInternal(HangUpOptions hangUpOptions, Con try { context = context == null ? Context.NONE : context; - if (hangUpOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - hangUpOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + hangUpOptions.setRepeatabilityHeaders(handleApiIdempotency(hangUpOptions.getRepeatabilityHeaders())); return (hangUpOptions.getIsForEveryone() ? callConnectionInternal.terminateCallWithResponseAsync(callConnectionId, - hangUpOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - hangUpOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + hangUpOptions.getRepeatabilityHeaders() != null ? hangUpOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + hangUpOptions.getRepeatabilityHeaders() != null ? hangUpOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) : callConnectionInternal.hangupCallWithResponseAsync(callConnectionId, context)) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create); @@ -272,14 +267,11 @@ Mono> transferToParticipantCallWithResponseInternal .setUserToUserInformation(transferToParticipantCallOptions.getUserToUserInformation()) .setOperationContext(transferToParticipantCallOptions.getOperationContext()); - if (transferToParticipantCallOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - transferToParticipantCallOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + transferToParticipantCallOptions.setRepeatabilityHeaders(handleApiIdempotency(transferToParticipantCallOptions.getRepeatabilityHeaders())); return callConnectionInternal.transferToParticipantWithResponseAsync(callConnectionId, request, - transferToParticipantCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - transferToParticipantCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + transferToParticipantCallOptions.getRepeatabilityHeaders() != null ? transferToParticipantCallOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + transferToParticipantCallOptions.getRepeatabilityHeaders() != null ? transferToParticipantCallOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> @@ -332,14 +324,11 @@ Mono> addParticipantsWithResponseInternal(AddPar request.setInvitationTimeoutInSeconds((int) addParticipantsOptions.getInvitationTimeout().getSeconds()); } - if (addParticipantsOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - addParticipantsOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + addParticipantsOptions.setRepeatabilityHeaders(handleApiIdempotency(addParticipantsOptions.getRepeatabilityHeaders())); return callConnectionInternal.addParticipantWithResponseAsync(callConnectionId, request, - addParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - addParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + addParticipantsOptions.getRepeatabilityHeaders() != null ? addParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + addParticipantsOptions.getRepeatabilityHeaders() != null ? addParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> new SimpleResponse<>(response, AddParticipantsResponseConstructorProxy.create(response.getValue()))); @@ -380,18 +369,15 @@ Mono> removeParticipantsWithResponseInternal( List participantModels = removeParticipantsOptions.getParticipants() .stream().map(CommunicationIdentifierConverter::convert).collect(Collectors.toList()); - if (removeParticipantsOptions.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - removeParticipantsOptions.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + removeParticipantsOptions.setRepeatabilityHeaders(handleApiIdempotency(removeParticipantsOptions.getRepeatabilityHeaders())); RemoveParticipantsRequestInternal request = new RemoveParticipantsRequestInternal() .setParticipantsToRemove(participantModels) .setOperationContext(removeParticipantsOptions.getOperationContext()); return callConnectionInternal.removeParticipantsWithResponseAsync(callConnectionId, request, - removeParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityRequestId(), - removeParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + removeParticipantsOptions.getRepeatabilityHeaders() != null ? removeParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + removeParticipantsOptions.getRepeatabilityHeaders() != null ? removeParticipantsOptions.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, context) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> new SimpleResponse<>(response, RemoveParticipantsResponseConstructorProxy.create(response.getValue()))); @@ -411,4 +397,15 @@ public CallMediaAsync getCallMediaAsync() { return new CallMediaAsync(callConnectionId, contentsInternal); } //endregion + + //region helper functions + /*** + * Make sure repeatability headers of the request are correctly set. + * + * @return a verified RepeatabilityHeaders object. + */ + private RepeatabilityHeaders handleApiIdempotency(RepeatabilityHeaders repeatabilityHeaders) { + return CallAutomationAsyncClient.handleApiIdempotency(repeatabilityHeaders); + } + //endregion } diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallRecordingAsync.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallRecordingAsync.java index 38793c54666f..0ce80e540fe2 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallRecordingAsync.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/CallRecordingAsync.java @@ -52,12 +52,10 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.InvalidParameterException; -import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; import static com.azure.core.util.FluxUtil.monoError; @@ -123,18 +121,15 @@ Mono> startRecordingWithResponseInternal(StartRec } StartCallRecordingRequestInternal request = getStartCallRecordingRequest(options); - if (options.getRepeatabilityHeaders() == null) { - RepeatabilityHeaders autoRepeatabilityHeaders = new RepeatabilityHeaders(UUID.randomUUID(), Instant.now()); - options.setRepeatabilityHeaders(autoRepeatabilityHeaders); - } + options.setRepeatabilityHeaders(handleApiIdempotency(options.getRepeatabilityHeaders())); return withContext(contextValue -> { contextValue = context == null ? contextValue : context; return contentsInternal .recordingWithResponseAsync( request, - options.getRepeatabilityHeaders().getRepeatabilityRequestId(), - options.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat(), + options.getRepeatabilityHeaders() != null ? options.getRepeatabilityHeaders().getRepeatabilityRequestId() : null, + options.getRepeatabilityHeaders() != null ? options.getRepeatabilityHeaders().getRepeatabilityFirstSentInHttpDateFormat() : null, contextValue) .onErrorMap(HttpResponseException.class, ErrorConstructorProxy::create) .map(response -> @@ -577,4 +572,15 @@ private URL getUrlToSignRequestWith(String endpoint) { throw logger.logExceptionAsError(new IllegalArgumentException(ex)); } } + + //region helper functions + /*** + * Make sure repeatability headers of the request are correctly set. + * + * @return a verified RepeatabilityHeaders object. + */ + private RepeatabilityHeaders handleApiIdempotency(RepeatabilityHeaders repeatabilityHeaders) { + return CallAutomationAsyncClient.handleApiIdempotency(repeatabilityHeaders); + } + //endregion } diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AddParticipantsOptions.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AddParticipantsOptions.java index 445e27d0dca6..cd43cdbddae8 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AddParticipantsOptions.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AddParticipantsOptions.java @@ -9,7 +9,9 @@ import com.azure.core.annotation.Fluent; import java.time.Duration; +import java.time.Instant; import java.util.List; +import java.util.UUID; /** * The options for adding participants. @@ -50,6 +52,7 @@ public final class AddParticipantsOptions { */ public AddParticipantsOptions(List participants) { this.participants = participants; + this.repeatabilityHeaders = new RepeatabilityHeaders(UUID.fromString("0-0-0-0-0"), Instant.MIN); } /** diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AnswerCallOptions.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AnswerCallOptions.java index 51b771ab826e..5391a8b08441 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AnswerCallOptions.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/AnswerCallOptions.java @@ -5,6 +5,9 @@ import com.azure.core.annotation.Fluent; +import java.time.Instant; +import java.util.UUID; + /** * The options for creating a call. */ @@ -39,6 +42,7 @@ public class AnswerCallOptions { public AnswerCallOptions(String incomingCallContext, String callbackUrl) { this.incomingCallContext = incomingCallContext; this.callbackUrl = callbackUrl; + this.repeatabilityHeaders = new RepeatabilityHeaders(UUID.fromString("0-0-0-0-0"), Instant.MIN); } /** diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/CreateCallOptions.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/CreateCallOptions.java index 08b56230c278..113f330ee7a4 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/CreateCallOptions.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/CreateCallOptions.java @@ -6,7 +6,9 @@ import com.azure.communication.common.CommunicationIdentifier; import com.azure.core.annotation.Fluent; +import java.time.Instant; import java.util.List; +import java.util.UUID; /** * The options for creating a call. @@ -60,6 +62,7 @@ public CreateCallOptions(CommunicationIdentifier source, List participants) { this.participants = participants; + this.repeatabilityHeaders = new RepeatabilityHeaders(UUID.fromString("0-0-0-0-0"), Instant.MIN); } /** diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/RepeatabilityHeaders.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/RepeatabilityHeaders.java index 232d36f69b76..c5f47e807534 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/RepeatabilityHeaders.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/RepeatabilityHeaders.java @@ -7,7 +7,6 @@ import java.time.Instant; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.UUID; @@ -26,7 +25,7 @@ public final class RepeatabilityHeaders { /** * The value should be the date and time at which the request was first created. */ - private final ZonedDateTime repeatabilityFirstSent; + private final Instant repeatabilityFirstSent; /** * Constructor @@ -37,7 +36,7 @@ public final class RepeatabilityHeaders { */ public RepeatabilityHeaders(UUID repeatabilityRequestId, Instant repeatabilityFirstSent) { this.repeatabilityRequestId = repeatabilityRequestId; - this.repeatabilityFirstSent = repeatabilityFirstSent.atZone(ZoneId.of("UTC")); + this.repeatabilityFirstSent = repeatabilityFirstSent; } /** @@ -55,14 +54,14 @@ public UUID getRepeatabilityRequestId() { */ public String getRepeatabilityFirstSentInHttpDateFormat() { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH).withZone(ZoneId.of("GMT")); - return repeatabilityFirstSent.format(formatter); + return repeatabilityFirstSent.atZone(ZoneId.of("UTC")).format(formatter); } /** * Get the repeatabilityFirstSent : The value should be the date and time at which the request was first created. * @return the repeatabilityFirstSent. */ - public ZonedDateTime getRepeatabilityFirstSent() { + public Instant getRepeatabilityFirstSent() { return repeatabilityFirstSent; } } diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/StartRecordingOptions.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/StartRecordingOptions.java index dea9c089213f..9ef7f107bb67 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/StartRecordingOptions.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/StartRecordingOptions.java @@ -6,8 +6,10 @@ import com.azure.communication.common.CommunicationIdentifier; import com.azure.core.annotation.Fluent; +import java.time.Instant; import java.util.List; import java.util.Objects; +import java.util.UUID; /** * The options for creating a call. @@ -38,8 +40,8 @@ public class StartRecordingOptions { */ public StartRecordingOptions(CallLocator callLocator) { Objects.requireNonNull(callLocator, "'callLocator' cannot be null."); - this.callLocator = callLocator; + this.repeatabilityHeaders = new RepeatabilityHeaders(UUID.fromString("0-0-0-0-0"), Instant.MIN); } /** diff --git a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/TransferToParticipantCallOptions.java b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/TransferToParticipantCallOptions.java index abfd5fb1d5ae..49e3a182bb09 100644 --- a/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/TransferToParticipantCallOptions.java +++ b/sdk/communication/azure-communication-callautomation/src/main/java/com/azure/communication/callautomation/models/TransferToParticipantCallOptions.java @@ -7,6 +7,9 @@ import com.azure.communication.common.PhoneNumberIdentifier; import com.azure.core.annotation.Fluent; +import java.time.Instant; +import java.util.UUID; + /** * The options for adding participants. */ @@ -44,6 +47,7 @@ public class TransferToParticipantCallOptions { */ public TransferToParticipantCallOptions(CommunicationIdentifier targetParticipant) { this.targetParticipant = targetParticipant; + this.repeatabilityHeaders = new RepeatabilityHeaders(UUID.fromString("0-0-0-0-0"), Instant.MIN); } /** diff --git a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallAutomationAsyncClientAutomatedLiveTests.java b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallAutomationAsyncClientAutomatedLiveTests.java index 40c561f88d58..999b87a2896a 100644 --- a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallAutomationAsyncClientAutomatedLiveTests.java +++ b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallAutomationAsyncClientAutomatedLiveTests.java @@ -3,10 +3,11 @@ package com.azure.communication.callautomation; +import com.azure.communication.callautomation.models.AnswerCallOptions; import com.azure.communication.callautomation.models.AnswerCallResult; import com.azure.communication.callautomation.models.CreateCallOptions; import com.azure.communication.callautomation.models.CreateCallResult; -import com.azure.communication.callautomation.models.RepeatabilityHeaders; +import com.azure.communication.callautomation.models.HangUpOptions; import com.azure.communication.callautomation.models.events.CallConnected; import com.azure.communication.callautomation.models.events.CallDisconnected; import com.azure.communication.callautomation.models.events.ParticipantsUpdated; @@ -22,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -61,12 +63,8 @@ public void createVOIPCallAndAnswerThenHangupAutomatedTest(HttpClient httpClient // create a call List targets = new ArrayList<>(Collections.singletonList(target)); CreateCallOptions createCallOptions = new CreateCallOptions(caller, targets, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)); + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); Response createCallResultResponse = callAsyncClient.createCallWithResponse(createCallOptions).block(); - RepeatabilityHeaders repeatabilityHeaders = createCallOptions.getRepeatabilityHeaders(); - assertNotNull(repeatabilityHeaders); - assertNotNull(repeatabilityHeaders.getRepeatabilityRequestId()); - assertNotNull(repeatabilityHeaders.getRepeatabilityFirstSent()); assertNotNull(createCallResultResponse); CreateCallResult createCallResult = createCallResultResponse.getValue(); @@ -80,8 +78,9 @@ public void createVOIPCallAndAnswerThenHangupAutomatedTest(HttpClient httpClient assertNotNull(incomingCallContext); // answer the call - AnswerCallResult answerCallResult = callAsyncClient.answerCall(incomingCallContext, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).block(); + AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); + AnswerCallResult answerCallResult = Objects.requireNonNull(callAsyncClient.answerCallWithResponse(answerCallOptions).block()).getValue(); assertNotNull(answerCallResult); assertNotNull(answerCallResult.getCallConnectionAsync()); assertNotNull(answerCallResult.getCallConnectionProperties()); @@ -114,7 +113,7 @@ public void createVOIPCallAndAnswerThenHangupAutomatedTest(HttpClient httpClient } finally { if (!callDestructors.isEmpty()) { try { - callDestructors.forEach(callConnection -> callConnection.hangUp(true).block()); + callDestructors.forEach(callConnection -> callConnection.hangUpWithResponse(new HangUpOptions(true).setRepeatabilityHeaders(null)).block()); } catch (Exception ignored) { // Some call might have been terminated during the test, and it will cause exceptions here. // Do nothing and iterate to next call connection. diff --git a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallConnectionAsyncAutomatedLiveTests.java b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallConnectionAsyncAutomatedLiveTests.java index 7867585c5205..c17c541986cd 100644 --- a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallConnectionAsyncAutomatedLiveTests.java +++ b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallConnectionAsyncAutomatedLiveTests.java @@ -5,9 +5,11 @@ import com.azure.communication.callautomation.models.AddParticipantsOptions; import com.azure.communication.callautomation.models.AddParticipantsResult; +import com.azure.communication.callautomation.models.AnswerCallOptions; import com.azure.communication.callautomation.models.AnswerCallResult; import com.azure.communication.callautomation.models.CreateCallOptions; import com.azure.communication.callautomation.models.CreateCallResult; +import com.azure.communication.callautomation.models.HangUpOptions; import com.azure.communication.callautomation.models.ListParticipantsResult; import com.azure.communication.callautomation.models.RemoveParticipantsResult; import com.azure.communication.callautomation.models.RepeatabilityHeaders; @@ -24,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -67,7 +70,7 @@ public void createVOIPCallAndAnswerThenAddParticipantFinallyRemoveParticipantAut // create a call List targets = new ArrayList<>(Arrays.asList(receiver)); CreateCallOptions createCallOptions = new CreateCallOptions(caller, targets, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)); + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); Response createCallResultResponse = callAsyncClient.createCallWithResponse(createCallOptions).block(); assertNotNull(createCallResultResponse); CreateCallResult createCallResult = createCallResultResponse.getValue(); @@ -81,8 +84,9 @@ public void createVOIPCallAndAnswerThenAddParticipantFinallyRemoveParticipantAut assertNotNull(incomingCallContext); // answer the call - AnswerCallResult answerCallResult = callAsyncClient.answerCall(incomingCallContext, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).block(); + AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); + AnswerCallResult answerCallResult = Objects.requireNonNull(callAsyncClient.answerCallWithResponse(answerCallOptions).block()).getValue(); assertNotNull(answerCallResult); assertNotNull(answerCallResult.getCallConnectionAsync()); assertNotNull(answerCallResult.getCallConnectionProperties()); @@ -112,8 +116,9 @@ public void createVOIPCallAndAnswerThenAddParticipantFinallyRemoveParticipantAut assertNotNull(anotherIncomingCallContext); // answer the call - AnswerCallResult anotherAnswerCallResult = callAsyncClient.answerCall(anotherIncomingCallContext, - DISPATCHER_CALLBACK + String.format("?q=%s", anotherUniqueId)).block(); + answerCallOptions = new AnswerCallOptions(anotherIncomingCallContext, + DISPATCHER_CALLBACK + String.format("?q=%s", anotherUniqueId)).setRepeatabilityHeaders(null); + AnswerCallResult anotherAnswerCallResult = Objects.requireNonNull(callAsyncClient.answerCallWithResponse(answerCallOptions).block()).getValue(); assertNotNull(anotherAnswerCallResult); assertNotNull(anotherAnswerCallResult.getCallConnectionAsync()); assertNotNull(anotherAnswerCallResult.getCallConnectionProperties()); @@ -148,7 +153,7 @@ public void createVOIPCallAndAnswerThenAddParticipantFinallyRemoveParticipantAut } finally { if (!callDestructors.isEmpty()) { try { - callDestructors.forEach(callConnection -> callConnection.hangUp(true).block()); + callDestructors.forEach(callConnection -> callConnection.hangUpWithResponse(new HangUpOptions(true).setRepeatabilityHeaders(null)).block()); } catch (Exception ignored) { // Some call might have been terminated during the test, and it will cause exceptions here. // Do nothing and iterate to next call connection. diff --git a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallMediaAsyncAutomatedLiveTests.java b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallMediaAsyncAutomatedLiveTests.java index d7d37707ad7f..070134a6b6e3 100644 --- a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallMediaAsyncAutomatedLiveTests.java +++ b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallMediaAsyncAutomatedLiveTests.java @@ -3,10 +3,12 @@ package com.azure.communication.callautomation; +import com.azure.communication.callautomation.models.AnswerCallOptions; import com.azure.communication.callautomation.models.AnswerCallResult; import com.azure.communication.callautomation.models.CreateCallOptions; import com.azure.communication.callautomation.models.CreateCallResult; import com.azure.communication.callautomation.models.FileSource; +import com.azure.communication.callautomation.models.HangUpOptions; import com.azure.communication.callautomation.models.events.CallConnected; import com.azure.communication.callautomation.models.events.PlayCompleted; import com.azure.communication.common.CommunicationIdentifier; @@ -21,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -60,7 +63,7 @@ public void playMediaInACallAutomatedTest(HttpClient httpClient) { // create a call List targets = new ArrayList<>(Arrays.asList(receiver)); CreateCallOptions createCallOptions = new CreateCallOptions(caller, targets, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)); + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); Response createCallResultResponse = callAsyncClient.createCallWithResponse(createCallOptions).block(); assertNotNull(createCallResultResponse); CreateCallResult createCallResult = createCallResultResponse.getValue(); @@ -74,8 +77,9 @@ public void playMediaInACallAutomatedTest(HttpClient httpClient) { assertNotNull(incomingCallContext); // answer the call - AnswerCallResult answerCallResult = callAsyncClient.answerCall(incomingCallContext, - DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).block(); + AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, + DISPATCHER_CALLBACK + String.format("?q=%s", uniqueId)).setRepeatabilityHeaders(null); + AnswerCallResult answerCallResult = Objects.requireNonNull(callAsyncClient.answerCallWithResponse(answerCallOptions).block()).getValue(); assertNotNull(answerCallResult); assertNotNull(answerCallResult.getCallConnectionAsync()); assertNotNull(answerCallResult.getCallConnectionProperties()); @@ -96,7 +100,7 @@ public void playMediaInACallAutomatedTest(HttpClient httpClient) { } finally { if (!callDestructors.isEmpty()) { try { - callDestructors.forEach(callConnection -> callConnection.hangUp(true).block()); + callDestructors.forEach(callConnection -> callConnection.hangUpWithResponse(new HangUpOptions(true).setRepeatabilityHeaders(null)).block()); } catch (Exception ignored) { // Some call might have been terminated during the test, and it will cause exceptions here. // Do nothing and iterate to next call connection. diff --git a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallRecordingAutomatedLiveTests.java b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallRecordingAutomatedLiveTests.java index 434b20b9704e..e0293050bfbb 100644 --- a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallRecordingAutomatedLiveTests.java +++ b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/CallRecordingAutomatedLiveTests.java @@ -3,10 +3,13 @@ package com.azure.communication.callautomation; +import com.azure.communication.callautomation.models.AnswerCallOptions; import com.azure.communication.callautomation.models.AnswerCallResult; import com.azure.communication.callautomation.models.CallConnectionProperties; import com.azure.communication.callautomation.models.CallConnectionState; +import com.azure.communication.callautomation.models.CreateCallOptions; import com.azure.communication.callautomation.models.CreateCallResult; +import com.azure.communication.callautomation.models.HangUpOptions; import com.azure.communication.callautomation.models.RecordingChannel; import com.azure.communication.callautomation.models.RecordingContent; import com.azure.communication.callautomation.models.RecordingFormat; @@ -59,9 +62,9 @@ public void createACSCallAndUnmixedAudioTest(HttpClient httpClient) { String uniqueId = serviceBusWithNewCall(source, target); // create call and assert response - CreateCallResult createCallResult = client.createCall( - source, Arrays.asList(target), String.format("%s?q=%s", DISPATCHER_CALLBACK, uniqueId) - ); + CreateCallOptions createCallOptions = new CreateCallOptions(source, Arrays.asList(target), String.format("%s?q=%s", DISPATCHER_CALLBACK, uniqueId)) + .setRepeatabilityHeaders(null); + CreateCallResult createCallResult = client.createCallWithResponse(createCallOptions, null).getValue(); callConnectionId = createCallResult.getCallConnectionProperties().getCallConnectionId(); assertNotNull(callConnectionId); @@ -70,7 +73,9 @@ public void createACSCallAndUnmixedAudioTest(HttpClient httpClient) { assertNotNull(incomingCallContext); // answer the call - AnswerCallResult answerCallResult = client.answerCall(incomingCallContext, DISPATCHER_CALLBACK); + AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, DISPATCHER_CALLBACK) + .setRepeatabilityHeaders(null); + AnswerCallResult answerCallResult = client.answerCallWithResponse(answerCallOptions, null).getValue(); assertNotNull(answerCallResult); // wait for callConnected @@ -88,6 +93,7 @@ public void createACSCallAndUnmixedAudioTest(HttpClient httpClient) { .setRecordingContent(RecordingContent.AUDIO) .setRecordingFormat(RecordingFormat.WAV) .setRecordingStateCallbackUrl(DISPATCHER_CALLBACK) + .setRepeatabilityHeaders(null) ); assertNotNull(recordingStateResult.getRecordingId()); @@ -136,9 +142,9 @@ public void createACSCallUnmixedAudioAffinityTest(HttpClient httpClient) { String uniqueId = serviceBusWithNewCall(source, target); // create call and assert response - CreateCallResult createCallResult = client.createCall( - source, Arrays.asList(target), String.format("%s?q=%s", DISPATCHER_CALLBACK, uniqueId) - ); + CreateCallOptions createCallOptions = new CreateCallOptions(source, Arrays.asList(target), String.format("%s?q=%s", DISPATCHER_CALLBACK, uniqueId)) + .setRepeatabilityHeaders(null); + CreateCallResult createCallResult = client.createCallWithResponse(createCallOptions, null).getValue(); callConnectionId = createCallResult.getCallConnectionProperties().getCallConnectionId(); assertNotNull(callConnectionId); @@ -147,7 +153,9 @@ public void createACSCallUnmixedAudioAffinityTest(HttpClient httpClient) { assertNotNull(incomingCallContext); // answer the call - AnswerCallResult answerCallResult = client.answerCall(incomingCallContext, DISPATCHER_CALLBACK); + AnswerCallOptions answerCallOptions = new AnswerCallOptions(incomingCallContext, DISPATCHER_CALLBACK) + .setRepeatabilityHeaders(null); + AnswerCallResult answerCallResult = client.answerCallWithResponse(answerCallOptions, null).getValue(); assertNotNull(answerCallResult); // wait for callConnected @@ -171,6 +179,7 @@ public void createACSCallUnmixedAudioAffinityTest(HttpClient httpClient) { add(target); } }) + .setRepeatabilityHeaders(null) ); assertNotNull(recordingStateResult.getRecordingId()); @@ -181,7 +190,7 @@ public void createACSCallUnmixedAudioAffinityTest(HttpClient httpClient) { // hangup if (!callConnectionId.isEmpty()) { CallConnection callConnection = client.getCallConnection(callConnectionId); - callConnection.hangUp(true); + callConnection.hangUpWithResponse(new HangUpOptions(true).setRepeatabilityHeaders(null), null); CallDisconnected callDisconnectedEvent = waitForEvent(CallDisconnected.class, callConnectionId, Duration.ofSeconds(10)); assertNotNull(callDisconnectedEvent); } diff --git a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/RepeatabilityHeadersUnitTests.java b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/RepeatabilityHeadersUnitTests.java index 097dcb4432cd..a6f2b13ee49e 100644 --- a/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/RepeatabilityHeadersUnitTests.java +++ b/sdk/communication/azure-communication-callautomation/src/test/java/com/azure/communication/callautomation/RepeatabilityHeadersUnitTests.java @@ -3,6 +3,7 @@ package com.azure.communication.callautomation; +import com.azure.communication.callautomation.models.HangUpOptions; import com.azure.communication.callautomation.models.RepeatabilityHeaders; import org.junit.jupiter.api.Test; @@ -11,6 +12,9 @@ import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; public class RepeatabilityHeadersUnitTests { @@ -28,4 +32,28 @@ public void repeatabilityHeadersDateValidation() { assertThrows(DateTimeException.class, () -> new RepeatabilityHeaders(UUID.randomUUID(), Instant.MAX.plusSeconds(1))); assertThrows(DateTimeException.class, () -> new RepeatabilityHeaders(UUID.randomUUID(), Instant.MIN.minusSeconds(1))); } + + @Test + public void handleApiIdempotencyHelperFunctionUnitTest() { + HangUpOptions hangUpOptions = new HangUpOptions(true); + + // Case 1: default repeatability headers, it should be altered by handleApiIdempotency. + RepeatabilityHeaders headers = CallAutomationAsyncClient.handleApiIdempotency(hangUpOptions.getRepeatabilityHeaders()); + assertNotEquals(UUID.fromString("0-0-0-0-0"), headers.getRepeatabilityRequestId()); + assertNotEquals(Instant.MIN, headers.getRepeatabilityFirstSent()); + + // Case 2: user defined repeatability headers, it should not be altered by handleApiIdempotency. + UUID uuid = UUID.randomUUID(); + Instant instant = Instant.now(); + hangUpOptions.setRepeatabilityHeaders(new RepeatabilityHeaders(uuid, instant)); + headers = CallAutomationAsyncClient.handleApiIdempotency(hangUpOptions.getRepeatabilityHeaders()); + assertEquals(uuid, headers.getRepeatabilityRequestId()); + assertEquals(instant, headers.getRepeatabilityFirstSent()); + + // Case 3: user disabled repeatability headers. + hangUpOptions = new HangUpOptions(true); + hangUpOptions.setRepeatabilityHeaders(null); + headers = CallAutomationAsyncClient.handleApiIdempotency(hangUpOptions.getRepeatabilityHeaders()); + assertNull(headers); + } }