Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See License.txt in the project root for license information.
/** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for * license information. */
package com.microsoft.azure; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.joda.JodaModule; import com.microsoft.rest.protocol.SerializerAdapter; import com.microsoft.rest.serializer.Base64UrlSerializer; import com.microsoft.rest.serializer.ByteArraySerializer; import com.microsoft.rest.serializer.DateTimeRfc1123Serializer; import com.microsoft.rest.serializer.DateTimeSerializer; import com.microsoft.rest.serializer.HeadersSerializer; import okhttp3.ResponseBody; import retrofit2.Response; import java.io.IOException; import java.lang.reflect.Type;
An instance of this class defines polling status of a long running operation.
Type parameters:
  • <T> – the type of the resource the operation returns.
/** * An instance of this class defines polling status of a long running operation. * * @param <T> the type of the resource the operation returns. */
public class PollingState<T> {
The HTTP method used to initiate the long running operation.
/** The HTTP method used to initiate the long running operation. **/
private String initialHttpMethod;
The polling status.
/** The polling status. */
private String status;
The HTTP status code.
/** The HTTP status code. */
private int statusCode = DEFAULT_STATUS_CODE;
The link in 'Azure-AsyncOperation' header.
/** The link in 'Azure-AsyncOperation' header. */
private String azureAsyncOperationHeaderLink;
The link in 'Location' Header.
/** The link in 'Location' Header. */
private String locationHeaderLink;
The default timeout interval between two polling operations.
/** The default timeout interval between two polling operations. */
private int defaultRetryTimeout;
The timeout interval between two polling operation.
/** The timeout interval between two polling operation. **/
private int retryTimeout;
The resource uri on which PUT or PATCH operation is applied.
/** The resource uri on which PUT or PATCH operation is applied. **/
private String putOrPatchResourceUri;
The logging context.
/** The logging context. **/
private String loggingContext;
indicate how to retrieve the final state of LRO.
/** indicate how to retrieve the final state of LRO. **/
private LongRunningFinalState finalStateVia; // Non-serializable properties //
The logging context header name.
/** The logging context header name. **/
@JsonIgnore private static final String LOGGING_HEADER = "x-ms-logging-context";
The statusCode that is used when no statusCode has been set.
/** The statusCode that is used when no statusCode has been set. */
@JsonIgnore private static final int DEFAULT_STATUS_CODE = 0;
The Retrofit response object.
/** The Retrofit response object. */
@JsonIgnore private Response<ResponseBody> response;
The response resource object.
/** The response resource object. */
@JsonIgnore private T resource;
The type of the response resource object.
/** The type of the response resource object. */
@JsonIgnore private Type resourceType;
The error during the polling operations.
/** The error during the polling operations. */
@JsonIgnore private CloudError error;
The adapter for a custom serializer.
/** The adapter for a custom serializer. */
@JsonIgnore private SerializerAdapter<?> serializerAdapter;
Default constructor.
/** * Default constructor. */
PollingState() { }
Creates a polling state.
Params:
  • response – the response from Retrofit REST call that initiate the long running operation.
  • lroOptions – long running operation options.
  • defaultRetryTimeout – the long running operation retry timeout.
  • resourceType – the type of the resource the long running operation returns
  • serializerAdapter – the adapter for the Jackson object mapper
Type parameters:
  • <T> – the result type
Throws:
Returns:the polling state
/** * Creates a polling state. * * @param response the response from Retrofit REST call that initiate the long running operation. * @param lroOptions long running operation options. * @param defaultRetryTimeout the long running operation retry timeout. * @param resourceType the type of the resource the long running operation returns * @param serializerAdapter the adapter for the Jackson object mapper * @param <T> the result type * @return the polling state * @throws IOException thrown by deserialization */
public static <T> PollingState<T> create(Response<ResponseBody> response, LongRunningOperationOptions lroOptions, int defaultRetryTimeout, Type resourceType, SerializerAdapter<?> serializerAdapter) throws IOException { PollingState<T> pollingState = new PollingState<>(); pollingState.initialHttpMethod = response.raw().request().method(); pollingState.defaultRetryTimeout = defaultRetryTimeout; pollingState.withResponse(response); pollingState.resourceType = resourceType; pollingState.serializerAdapter = serializerAdapter; pollingState.loggingContext = response.raw().request().header(LOGGING_HEADER); pollingState.finalStateVia = lroOptions.finalStateVia(); String responseContent = null; PollingResource resource = null; if (response.body() != null) { responseContent = response.body().string(); response.body().close(); } if (responseContent != null && !responseContent.isEmpty()) { pollingState.resource = serializerAdapter.deserialize(responseContent, resourceType); resource = serializerAdapter.deserialize(responseContent, PollingResource.class); } final int statusCode = pollingState.response.code(); if (resource != null && resource.properties != null && resource.properties.provisioningState != null) { pollingState.withStatus(resource.properties.provisioningState, statusCode); } else { switch (statusCode) { case 202: pollingState.withStatus(AzureAsyncOperation.IN_PROGRESS_STATUS, statusCode); break; case 204: case 201: case 200: pollingState.withStatus(AzureAsyncOperation.SUCCESS_STATUS, statusCode); break; default: pollingState.withStatus(AzureAsyncOperation.FAILED_STATUS, statusCode); } } return pollingState; }
Creates PollingState from the json string.
Params:
  • serializedPollingState – polling state as json string
Type parameters:
  • <ResultT> – the result that the poll operation produces
Returns:the polling state
/** * Creates PollingState from the json string. * * @param serializedPollingState polling state as json string * @param <ResultT> the result that the poll operation produces * @return the polling state */
public static <ResultT> PollingState<ResultT> createFromJSONString(String serializedPollingState) { ObjectMapper mapper = initMapper(new ObjectMapper()); PollingState<ResultT> pollingState; try { pollingState = mapper.readValue(serializedPollingState, PollingState.class); } catch (IOException exception) { throw new RuntimeException(exception); } return pollingState; }
Creates PollingState from another polling state.
Params:
  • other – other polling state
  • result – the final result of the LRO
Type parameters:
  • <ResultT> – the result that the poll operation produces
Returns:the polling state
/** * Creates PollingState from another polling state. * * @param other other polling state * @param result the final result of the LRO * @param <ResultT> the result that the poll operation produces * @return the polling state */
public static <ResultT> PollingState<ResultT> createFromPollingState(PollingState<?> other, ResultT result) { PollingState<ResultT> pollingState = new PollingState<>(); pollingState.resource = result; pollingState.initialHttpMethod = other.initialHttpMethod(); pollingState.status = other.status(); pollingState.statusCode = other.statusCode(); pollingState.azureAsyncOperationHeaderLink = other.azureAsyncOperationHeaderLink(); pollingState.locationHeaderLink = other.locationHeaderLink(); pollingState.putOrPatchResourceUri = other.putOrPatchResourceUri(); pollingState.defaultRetryTimeout = other.defaultRetryTimeout; pollingState.retryTimeout = other.retryTimeout; pollingState.loggingContext = other.loggingContext; pollingState.finalStateVia = other.finalStateVia; return pollingState; }
Returns:the polling state in json string format
/** * @return the polling state in json string format */
public String serialize() { ObjectMapper mapper = initMapper(new ObjectMapper()); try { return mapper.writeValueAsString(this); } catch (JsonProcessingException exception) { throw new RuntimeException(exception); } }
Gets the resource.
Returns:the resource.
/** * Gets the resource. * * @return the resource. */
public T resource() { return resource; }
Gets the operation response.
Returns:the operation response.
/** * Gets the operation response. * * @return the operation response. */
public Response<ResponseBody> response() { return this.response; }
Gets the polling status.
Returns:the polling status.
/** * Gets the polling status. * * @return the polling status. */
public String status() { return status; }
Gets the polling HTTP status code.
Returns:the polling HTTP status code.
/** * Gets the polling HTTP status code. * * @return the polling HTTP status code. */
public int statusCode() { return statusCode; }
Gets the value captured from Azure-AsyncOperation header.
Returns:the link in the header.
/** * Gets the value captured from Azure-AsyncOperation header. * * @return the link in the header. */
public String azureAsyncOperationHeaderLink() { if (azureAsyncOperationHeaderLink != null && !azureAsyncOperationHeaderLink.isEmpty()) { return azureAsyncOperationHeaderLink; } return null; }
Gets the value captured from Location header.
Returns:the link in the header.
/** * Gets the value captured from Location header. * * @return the link in the header. */
public String locationHeaderLink() { if (locationHeaderLink != null && !locationHeaderLink.isEmpty()) { return locationHeaderLink; } return null; }
Updates the polling state from a PUT or PATCH operation.
Params:
  • response – the response from Retrofit REST call
Throws:
/** * Updates the polling state from a PUT or PATCH operation. * * @param response the response from Retrofit REST call * @throws CloudException thrown if the response is invalid * @throws IOException thrown by deserialization */
void updateFromResponseOnPutPatch(Response<ResponseBody> response) throws CloudException, IOException { String responseContent = null; if (response.body() != null) { responseContent = response.body().string(); response.body().close(); } if (responseContent == null || responseContent.isEmpty()) { throw new CloudException("polling response does not contain a valid body", response); } PollingResource resource = serializerAdapter.deserialize(responseContent, PollingResource.class); final int statusCode = response.code(); if (resource != null && resource.properties != null && resource.properties.provisioningState != null) { this.withStatus(resource.properties.provisioningState, statusCode); } else { this.withStatus(AzureAsyncOperation.SUCCESS_STATUS, statusCode); } CloudError error = new CloudError(); this.withErrorBody(error); error.withCode(this.status()); error.withMessage("Long running operation failed"); this.withResponse(response); this.withResource(serializerAdapter.<T>deserialize(responseContent, resourceType)); }
Updates the polling state from a DELETE or POST operation.
Params:
  • response – the response from Retrofit REST call
Throws:
/** * Updates the polling state from a DELETE or POST operation. * * @param response the response from Retrofit REST call * @throws IOException thrown by deserialization */
void updateFromResponseOnDeletePost(Response<ResponseBody> response) throws IOException { this.withResponse(response); String responseContent = null; if (response.body() != null) { responseContent = response.body().string(); response.body().close(); } this.withResource(serializerAdapter.<T>deserialize(responseContent, resourceType)); withStatus(AzureAsyncOperation.SUCCESS_STATUS, response.code()); }
Gets long running operation delay in milliseconds.
Returns:the delay in milliseconds.
/** * Gets long running operation delay in milliseconds. * * @return the delay in milliseconds. */
int delayInMilliseconds() { if (this.retryTimeout >= 0) { return this.retryTimeout; } if (this.defaultRetryTimeout >= 0) { return this.defaultRetryTimeout * 1000; } return AzureAsyncOperation.DEFAULT_DELAY * 1000; }
Returns:the uri of the resource on which the LRO PUT or PATCH applied.
/** * @return the uri of the resource on which the LRO PUT or PATCH applied. */
String putOrPatchResourceUri() { return this.putOrPatchResourceUri; }
Returns:true if the status this state hold represents terminal status.
/** * @return true if the status this state hold represents terminal status. */
boolean isStatusTerminal() { for (String terminalStatus : AzureAsyncOperation.terminalStatuses()) { if (terminalStatus.equalsIgnoreCase(this.status())) { return true; } } return false; }
Returns:true if the status this state hold is represents failed status.
/** * @return true if the status this state hold is represents failed status. */
boolean isStatusFailed() { for (String failedStatus : AzureAsyncOperation.failedStatuses()) { if (failedStatus.equalsIgnoreCase(this.status())) { return true; } } return false; }
Returns:true if the status this state represents is succeeded status.
/** * @return true if the status this state represents is succeeded status. */
boolean isStatusSucceeded() { return AzureAsyncOperation.SUCCESS_STATUS.equalsIgnoreCase(this.status()); } boolean resourcePending() { boolean resourcePending = statusCode() != 204 && isStatusSucceeded() && resource() == null && resourceType() != Void.class && locationHeaderLink() != null; if (resourcePending) { // Keep current behaviour for backward-compact return true; } else { return this.finalStateVia() == LongRunningFinalState.LOCATION; } }
Gets the logging context.
Returns:the logging context
/** * Gets the logging context. * * @return the logging context */
String loggingContext() { return loggingContext; }
Sets the polling status.
Params:
  • status – the polling status.
Throws:
/** * Sets the polling status. * * @param status the polling status. * @throws IllegalArgumentException thrown if status is null. */
PollingState<T> withStatus(String status) throws IllegalArgumentException { return withStatus(status, DEFAULT_STATUS_CODE); }
Sets the polling status.
Params:
  • status – the polling status.
  • statusCode – the HTTP status code
Throws:
/** * Sets the polling status. * * @param status the polling status. * @param statusCode the HTTP status code * @throws IllegalArgumentException thrown if status is null. */
PollingState<T> withStatus(String status, int statusCode) throws IllegalArgumentException { if (status == null) { throw new IllegalArgumentException("Status is null."); } this.status = status; this.statusCode = statusCode; return this; }
Sets the last operation response.
Params:
  • response – the last operation response.
/** * Sets the last operation response. * * @param response the last operation response. */
PollingState<T> withResponse(Response<ResponseBody> response) { this.response = response; withPollingUrlFromResponse(response); withPollingRetryTimeoutFromResponse(response); return this; } PollingState<T> withPollingUrlFromResponse(Response<ResponseBody> response) { if (response != null) { String asyncHeader = response.headers().get("Azure-AsyncOperation"); String locationHeader = response.headers().get("Location"); if (asyncHeader != null) { this.azureAsyncOperationHeaderLink = asyncHeader; } if (locationHeader != null) { this.locationHeaderLink = locationHeader; } } return this; } PollingState<T> withPollingRetryTimeoutFromResponse(Response<ResponseBody> response) { if (this.response != null && response.headers().get("Retry-After") != null) { retryTimeout = Integer.parseInt(response.headers().get("Retry-After")) * 1000; return this; } this.retryTimeout = -1; return this; } PollingState<T> withPutOrPatchResourceUri(final String uri) { this.putOrPatchResourceUri = uri; return this; }
Sets the resource.
Params:
  • resource – the resource.
/** * Sets the resource. * * @param resource the resource. */
PollingState<T> withResource(T resource) { this.resource = resource; return this; }
Returns:the resource type
/** * @return the resource type */
Type resourceType() { return resourceType; }
Returns:describes how to retrieve the final result of long running operation.
/** * @return describes how to retrieve the final result of long running operation. */
LongRunningFinalState finalStateVia() { if (this.initialHttpMethod().equalsIgnoreCase("POST") && resourceType() != Void.class) { // FinalStateVia is supported only for POST LRO at the moment. // if (this.locationHeaderLink() != null && this.azureAsyncOperationHeaderLink() != null) { // Consider final-state-via option only if both headers are provided on the wire otherwise // there is nothing to disambiguate. // if (this.finalStateVia == LongRunningFinalState.LOCATION) { // A POST LRO can be tracked only using Location or AsyncOperation Header. // If AsyncOperationHeader is present then anyway polling will be performed using // it and there is no point in making one additional call to retrieve state using // async operation uri anyway. Hence we consider only LongRunningFinalState.LOCATION. // return LongRunningFinalState.LOCATION; } } } return LongRunningFinalState.DEFAULT; }
Sets resource type. param resourceType the resource type
/** * Sets resource type. * * param resourceType the resource type */
PollingState<T> withResourceType(Type resourceType) { this.resourceType = resourceType; return this; }
Gets CloudError from current instance.
Returns:the cloud error.
/** * Gets {@link CloudError} from current instance. * * @return the cloud error. */
CloudError errorBody() { return error; }
Sets CloudError from current instance.
Params:
  • error – the cloud error.
/** * Sets {@link CloudError} from current instance. * * @param error the cloud error. */
PollingState<T> withErrorBody(CloudError error) { this.error = error; return this; }
Sets the serializer adapter.
Params:
  • serializerAdapter – the serializer adapter.
/** * Sets the serializer adapter. * * @param serializerAdapter the serializer adapter. */
PollingState<T> withSerializerAdapter(SerializerAdapter<?> serializerAdapter) { this.serializerAdapter = serializerAdapter; return this; }
Returns:the http method used to initiate the long running operation.
/** * @return the http method used to initiate the long running operation. */
String initialHttpMethod() { return this.initialHttpMethod; }
If status is in failed state then throw CloudException.
/** * If status is in failed state then throw CloudException. */
void throwCloudExceptionIfInFailedState() { if (this.isStatusFailed()) { if (this.errorBody() != null) { throw new CloudException("Async operation failed with provisioning state: " + this.status(), this.response(), this.errorBody()); } else { throw new CloudException("Async operation failed with provisioning state: " + this.status(), this.response()); } } }
Initializes an object mapper.
Params:
  • mapper – the mapper to initialize
Returns:the initialized mapper
/** * Initializes an object mapper. * * @param mapper the mapper to initialize * @return the initialized mapper */
private static ObjectMapper initMapper(ObjectMapper mapper) { mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true) .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) .setSerializationInclusion(JsonInclude.Include.NON_NULL) .registerModule(new JodaModule()) .registerModule(ByteArraySerializer.getModule()) .registerModule(Base64UrlSerializer.getModule()) .registerModule(DateTimeSerializer.getModule()) .registerModule(DateTimeRfc1123Serializer.getModule()) .registerModule(HeadersSerializer.getModule()); mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker() .withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withSetterVisibility(JsonAutoDetect.Visibility.NONE) .withGetterVisibility(JsonAutoDetect.Visibility.NONE) .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)); return mapper; }
An instance of this class describes the status of a long running operation and is returned from server each time.
/** * An instance of this class describes the status of a long running operation * and is returned from server each time. */
private static class PollingResource {
Inner properties object.
/** Inner properties object. */
@JsonProperty(value = "properties") private Properties properties;
Inner properties class.
/** * Inner properties class. */
private static class Properties {
The provisioning state of the resource.
/** The provisioning state of the resource. */
@JsonProperty(value = "provisioningState") private String provisioningState; } } }