Skip to content

Commit

Permalink
Adding other status (#4091)
Browse files Browse the repository at this point in the history
Adding option to provide custom status  Issue #3930
  • Loading branch information
hemanttanwar authored and sima-zhu committed Jun 27, 2019
1 parent 2f8e6f5 commit 8b36281
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public final class PollResponse<T> {
private final T value;
private final Duration retryAfter;
private final Map<Object, Object> properties;
private final String otherStatus;

/**
* An enum to represent all possible states that a long-running operation may find itself in.
Expand All @@ -49,7 +50,14 @@ public enum OperationStatus {

/** Represents that this long-running operation is cancelled by user, however this is still
* considered as complete long-running operation.*/
USER_CANCELLED
USER_CANCELLED,

/**
* When long-running operation status could not be represented by any status in {@link OperationStatus}, this status represents
* a custom status Azure service could be in. This custom status is not considered as complete long-running operation.
* It must have valid value for {@code otherStatus} as {@link String}.
*/
OTHER
}

/**
Expand All @@ -66,11 +74,27 @@ public enum OperationStatus {
* @throws NullPointerException If {@code status} is {@code null}.
*/
public PollResponse(OperationStatus status, T value, Duration retryAfter, Map<Object, Object> properties) {
this(status, null, value, retryAfter, properties);
}

/*
* Creates a new {@link PollResponse} with status, value and retryAfter.
*
* @param status Mandatory operation status as defined in {@link OperationStatus}.
* @param otherStatus string representation of custom status. It must be not null and non empty. The status will be defaulted to {@link OperationStatus#OTHER}
* @param value The value as a result of poll operation. This can be any custom user-defined object. Null is also valid.
* @param retryAfter Represents the delay the service has requested until the next polling operation is performed.
* A {@code null}, zero or negative value will be taken to mean that the {@link Poller} should determine on its own when the next poll operation is to occur.
* @param properties A map of properties provided by the service that will be made available into the next poll operation.
* @throws NullPointerException If {@code status} is {@code null}.
*/
private PollResponse(OperationStatus status, String otherStatus, T value, Duration retryAfter, Map<Object, Object> properties) {
Objects.requireNonNull(status, "The status input parameter cannot be null.");
this.status = status;
this.value = value;
this.retryAfter = retryAfter;
this.properties = properties;
this.otherStatus = otherStatus;
}

/**
Expand Down Expand Up @@ -103,6 +127,49 @@ public PollResponse(OperationStatus status, T value) {
this(status, value, null);
}

/**
* Creates a new {@link PollResponse} with status and value.
*
*<p><strong>Code Sample Creating PollResponse Object</strong></p>
* {@codesnippet com.azure.core.util.polling.pollresponse.custom.status.retryAfter}
*
* @param otherStatus string representation of custom status. It must be not null and non empty. The status will be defaulted to {@link OperationStatus#OTHER}
* @param value The value as a result of poll operation. This can be any custom user-defined object. Null is also valid.
* @param retryAfter Represents the delay the service has requested until the next polling operation is performed.
* A {@code null}, zero or negative value will be taken to mean that the {@link Poller} should determine on its own when the next poll operation is to occur.
* @throws NullPointerException If {@code status} is {@code null}.
* @throws IllegalArgumentException if otherStatus is null or empty when status is {@link OperationStatus#OTHER}.
*/
public PollResponse(String otherStatus, T value, Duration retryAfter) {
this(OperationStatus.OTHER, otherStatus, value, retryAfter, null);
if (Objects.isNull(otherStatus) || otherStatus.trim().length() == 0) {
throw new IllegalArgumentException("The otherStatus can not be empty or null.");
}
}

/**
* Creates a new {@link PollResponse} with custom status and value.
*
*<p><strong>Code Sample Creating PollResponse Object</strong></p>
* {@codesnippet com.azure.core.util.polling.pollresponse.custom.status}
*
* @param otherStatus string representation of custom status. It must be not null and non empty. The status will be defaulted to {@link OperationStatus#OTHER}
* @param value The value as a result of poll operation. This can be any custom user-defined object. Null is also valid.
* @throws NullPointerException If {@code status} is {@code null}.
* @throws IllegalArgumentException if otherStatus is null or empty when status is {@link OperationStatus#OTHER}.
*/
public PollResponse(String otherStatus, T value) {
this(otherStatus, value, null);
}

/**
* Used to retrieve value for custom status string when status is {@link OperationStatus#OTHER}.
* @return custom other status string when status is {@link OperationStatus#OTHER}.
*/
public String getOtherStatus() {
return this.otherStatus;
}

/**
* Represents the status of the long-running operation at the time the last polling operation finished successfully.
* @return A {@link OperationStatus} representing the result of the poll operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,33 @@

public final class PollResponseJavaDocCodeSnippets<T> {

/**
*
* @param otherStatus v
* @param value v
*/
public void initialise(String otherStatus, T value) {
// BEGIN: com.azure.core.util.polling.pollresponse.custom.status
// Lets say we want to crete poll response with status as IN_PROGRESS
PollResponse<String> inProgressPollResponse
= new PollResponse<>("CUSTOM_OTHER_STATUS", "my custom response");
// END: com.azure.core.util.polling.pollresponse.custom.status
}

/**
*
* @param otherStatus v
* @param value v
* @param retryAfterDuration v
*/
public void initialise(String otherStatus, T value, Duration retryAfterDuration) {
// BEGIN: com.azure.core.util.polling.pollresponse.custom.status.retryAfter
// Lets say we want to crete poll response with status as IN_PROGRESS
PollResponse<String> inProgressPollResponse
= new PollResponse<>("CUSTOM_OTHER_STATUS", "my custom response", Duration.ofMillis(5000));
// END: com.azure.core.util.polling.pollresponse.custom.status.retryAfter
}

/**
* initialise
* @param status v
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@

import org.junit.Assert;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.junit.Assert.assertTrue;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Function;

public class PollerTests {
Expand Down Expand Up @@ -46,6 +49,102 @@ public Mono<PollResponse<CreateCertificateResponse>> apply(PollResponse<CreateCe
};
}

private Function<PollResponse<CreateCertificateResponse>, Mono<PollResponse<CreateCertificateResponse>>> createPollOperation(

final List<PollResponse<CreateCertificateResponse>> intermediateOtherPollResponseList,
final PollResponse<CreateCertificateResponse> finalPollResponse,
long sendFinalResponseInMillis
) {
return new Function<PollResponse<CreateCertificateResponse>, Mono<PollResponse<CreateCertificateResponse>>>() {
// Will return success after this time.
LocalDateTime timeToReturnFinalResponse = LocalDateTime.now().plus(Duration.ofMillis(sendFinalResponseInMillis));
@Override
public Mono<PollResponse<CreateCertificateResponse>> apply(PollResponse<CreateCertificateResponse> prePollResponse) {
++count;
if (LocalDateTime.now().isBefore(timeToReturnFinalResponse)) {
int indexForIntermediateResponse = prePollResponse.getValue() == null || prePollResponse.getValue().intermediateResponseIndex >= intermediateOtherPollResponseList.size() ? 0 : prePollResponse.getValue().intermediateResponseIndex;
PollResponse<CreateCertificateResponse> intermediatePollResponse = intermediateOtherPollResponseList.get(indexForIntermediateResponse);
debug(" Service poll function called ", " returning intermediate response status, otherstatus, value " + intermediatePollResponse.getStatus().toString() + "," + intermediatePollResponse.getOtherStatus() + "," + intermediatePollResponse.getValue().response);
intermediatePollResponse.getValue().intermediateResponseIndex = indexForIntermediateResponse + 1;
return Mono.just(intermediatePollResponse);
} else {
debug(" Service poll function called ", " returning final response " + finalPollResponse.getValue().response);
return Mono.just(finalPollResponse);
}
}
};
}

/* Test where SDK Client is subscribed all responses.
* This scenario is setup where source will generate few in-progress response followed by few OTHER responses and finally successfully completed response.
* The sdk client will only subscribe for a specific OTHER response and final successful response.
**/
@Test
public void subscribeToSpecificOtherOperationStatusTest() throws Exception {
PollResponse<CreateCertificateResponse> successPollResponse = new PollResponse<>(OperationStatus.SUCCESSFULLY_COMPLETED, new CreateCertificateResponse("Created : Cert A"));
PollResponse<CreateCertificateResponse> inProgressPollResponse = new PollResponse<>(OperationStatus.IN_PROGRESS, new CreateCertificateResponse("Starting : Cert A"));
PollResponse<CreateCertificateResponse> other1PollResponse = new PollResponse<>("OTHER_1", new CreateCertificateResponse("Starting : Cert A"));
PollResponse<CreateCertificateResponse> other2PollResponse = new PollResponse<>("OTHER_2", new CreateCertificateResponse("Starting : Cert A"));

ArrayList<PollResponse<CreateCertificateResponse>> inProgressPollResponseList = new ArrayList<>();
inProgressPollResponseList.add(inProgressPollResponse);
inProgressPollResponseList.add(inProgressPollResponse);
inProgressPollResponseList.add(other1PollResponse);
inProgressPollResponseList.add(other2PollResponse);
long totalTimeoutInMillis = 1000 * 2;
Duration pollInterval = Duration.ofMillis(totalTimeoutInMillis / 20);

Function<PollResponse<CreateCertificateResponse>, Mono<PollResponse<CreateCertificateResponse>>> pollOperation =
createPollOperation(inProgressPollResponseList,
successPollResponse, totalTimeoutInMillis - pollInterval.toMillis());

Poller<CreateCertificateResponse> createCertPoller = new Poller<>(pollInterval, pollOperation);
Flux<PollResponse<CreateCertificateResponse>> fluxPollResp = createCertPoller.getObserver();
fluxPollResp.subscribe(pr -> {
debug("0 Got Observer() Response " + pr.getStatus().toString() + " " + pr.getOtherStatus() + " " + pr.getValue().response);
});

createCertPoller.getObserver().subscribe(x -> {
debug("1 Got Observer() Response " + x.getStatus().toString() + " " + x.getStatus() + " " + x.getValue().response);
});

// get Specific Event Observer
List<String> observeOtherStates = new ArrayList<>();
observeOtherStates.add("OTHER_1");
observeOtherStates.add("OTHER_2");
List<OperationStatus> observeOperationStates = new ArrayList<>();
observeOperationStates.add(OperationStatus.SUCCESSFULLY_COMPLETED);
Flux<PollResponse<CreateCertificateResponse>> fluxPollRespFiltered = fluxPollResp.filterWhen(tPollResponse -> matchesState(tPollResponse, observeOperationStates, observeOtherStates));
fluxPollResp.subscribe(pr -> {
debug("1 Got Observer() Response " + pr.getStatus().toString() + " " + pr.getOtherStatus() + " " + pr.getValue().response);
});
fluxPollRespFiltered.subscribe(pr -> {
debug("2 Got Observer(SUCCESSFULLY_COMPLETED, OTHER_1,2) Response " + pr.getStatus().toString() + " " + pr.getOtherStatus() + " " + pr.getValue().response);
});

Thread.sleep(totalTimeoutInMillis + 3 * pollInterval.toMillis());
Assert.assertTrue(createCertPoller.block().getStatus() == OperationStatus.SUCCESSFULLY_COMPLETED);
Assert.assertTrue(createCertPoller.getStatus() == OperationStatus.SUCCESSFULLY_COMPLETED);
Assert.assertTrue(createCertPoller.isAutoPollingEnabled());
}

private Mono<Boolean> matchesState(PollResponse<CreateCertificateResponse> currentPollResponse, List<OperationStatus> observeOperationStates, List<String> observeOtherStates) {
List<OperationStatus> operationStates = observeOperationStates != null ? observeOperationStates : new ArrayList<>();
if (currentPollResponse.getStatus() == OperationStatus.OTHER
&& currentPollResponse.getStatus() != null
&& observeOtherStates != null) {
if (observeOtherStates.contains(currentPollResponse.getOtherStatus())) {
return Mono.just(true);
}
} else {
if (operationStates.contains(currentPollResponse.getStatus())) {
return Mono.just(true);
}
}
return Mono.just(false);
}


/* Test where SDK Client is subscribed all responses.
* This scenario is setup where source will generate successful response returned
* after few in-progress response. But the sdk client will stop polling in between
Expand Down Expand Up @@ -327,6 +426,7 @@ private void debug(String... messages) {
public class CreateCertificateResponse {
String response;
HttpResponseException error;
int intermediateResponseIndex;

public CreateCertificateResponse(String respone) {
this.response = respone;
Expand Down

0 comments on commit 8b36281

Please sign in to comment.