com.microsoft.azure.AzureClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of azure-client-runtime Show documentation
Show all versions of azure-client-runtime Show documentation
This package contains the basic runtime for AutoRest generated Azure Java clients.
The newest version!
/**
* 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.microsoft.rest.ServiceResponse;
import com.microsoft.rest.ServiceResponseWithHeaders;
import okhttp3.ResponseBody;
import retrofit2.Response;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Url;
import rx.Observable;
import rx.Single;
import rx.exceptions.Exceptions;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* An instance of this class defines a ServiceClient that handles polling and
* retrying for long running operations when accessing Azure resources.
*/
public final class AzureClient extends AzureServiceClient {
/**
* The interval time between two long running operation polls. Default is 30 seconds.
*/
private int longRunningOperationRetryTimeout = -1;
/**
* The user agent from the service client that owns this Azure Client.
*/
private final String serviceClientUserAgent;
/**
* Initializes an instance of this class with customized client metadata.
*
* @param serviceClient the caller client that initiates the asynchronous request.
*/
public AzureClient(AzureServiceClient serviceClient) {
super(serviceClient.restClient());
this.serviceClientUserAgent = serviceClient.userAgent();
}
/**
* Gets the interval time between two long running operation polls.
*
* @return the time in seconds.
*/
public Integer longRunningOperationRetryTimeout() {
return longRunningOperationRetryTimeout;
}
/**
* Sets the interval time between two long running operation polls. Default is 30 seconds.
* Set to any negative value to let AzureClient ignore this setting.
*
* @param longRunningOperationRetryTimeout the time in seconds. Set to any negative value to let AzureClient ignore this setting.
*/
public void setLongRunningOperationRetryTimeout(int longRunningOperationRetryTimeout) {
if (longRunningOperationRetryTimeout < 0) {
throw new IllegalArgumentException("Invalid timeout for long running operations : " + longRunningOperationRetryTimeout);
}
this.longRunningOperationRetryTimeout = longRunningOperationRetryTimeout;
}
/**
* Handles an initial response from a PUT or PATCH operation response by polling
* the status of the operation until the long running operation terminates.
*
* @param observable the initial observable from the PUT or PATCH operation.
* @param the return type of the caller
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the terminal response for the operation.
* @throws CloudException REST exception
* @throws InterruptedException interrupted exception
* @throws IOException thrown by deserialization
*/
private ServiceResponse getPutOrPatchResult(Observable> observable, Type resourceType) throws CloudException, InterruptedException, IOException {
Observable> asyncObservable = getPutOrPatchResultAsync(observable, resourceType);
return asyncObservable.toBlocking().last();
}
/**
* Handles an initial response from a PUT or PATCH operation response by polling
* the status of the operation until the long running operation terminates.
*
* @param observable the initial observable from the PUT or PATCH operation.
* @param resourceType the java.lang.reflect.Type of the resource.
* @param headerType the type of the response header
* @param the return type of the caller
* @param the type of the response header
* @return the terminal response for the operation.
* @throws CloudException REST exception
* @throws InterruptedException interrupted exception
* @throws IOException thrown by deserialization
*/
public ServiceResponseWithHeaders getPutOrPatchResultWithHeaders(Observable> observable, Type resourceType, Class headerType) throws CloudException, InterruptedException, IOException {
ServiceResponse bodyResponse = getPutOrPatchResult(observable, resourceType);
return new ServiceResponseWithHeaders<>(
bodyResponse.body(),
restClient().serializerAdapter().deserialize(restClient().serializerAdapter().serialize(bodyResponse.response().headers()), headerType),
bodyResponse.response()
);
}
/**
* Handles an initial response from a PUT or PATCH operation response by polling the status of the operation
* asynchronously, once the operation finishes emits the final response.
*
* @param observable the initial observable from the PUT or PATCH operation.
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the return type of the caller.
* @return the observable of which a subscription will lead to a final response.
*/
public Observable> getPutOrPatchResultAsync(Observable> observable, final Type resourceType) {
return this.beginPutOrPatchAsync(observable, resourceType)
.toObservable()
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState pollingState) {
if (pollingState.isStatusTerminal()) {
return Observable.just(pollingState);
} else {
// initial delay
return Observable.just(pollingState).delaySubscription(pollingState.delayInMilliseconds(), TimeUnit.MILLISECONDS, Schedulers.immediate());
}
}
})
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState pollingState) {
return pollPutOrPatchAsync(pollingState, resourceType);
}
})
.last()
.map(new Func1, ServiceResponse>() {
@Override
public ServiceResponse call(PollingState pollingState) {
return new ServiceResponse<>(pollingState.resource(), pollingState.response());
}
});
}
/**
* Given an observable representing a deferred PUT or PATCH action, this method returns {@link Single} object,
* when subscribed to it, the deferred action will be performed and emits the polling state containing information
* to track the progress of the action.
*
* Note: this method does not implicitly introduce concurrency, by default the deferred action will be executed
* in scheduler (if any) set for the provided observable.
*
* @param observable an observable representing a deferred PUT or PATCH operation.
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the type of the resource
* @return the observable of which a subscription will lead PUT or PATCH action.
*/
public Single> beginPutOrPatchAsync(Observable> observable, final Type resourceType) {
return observable.map(new Func1, PollingState>() {
@Override
public PollingState call(Response response) {
RuntimeException exception = createExceptionFromResponse(response, 200, 201, 202);
if (exception != null) {
throw exception;
}
try {
final PollingState pollingState = PollingState.create(response, LongRunningOperationOptions.DEFAULT, longRunningOperationRetryTimeout(), resourceType, restClient().serializerAdapter());
pollingState.withPollingUrlFromResponse(response);
pollingState.withPollingRetryTimeoutFromResponse(response);
pollingState.withPutOrPatchResourceUri(response.raw().request().url().toString());
return pollingState;
} catch (IOException ioException) {
throw Exceptions.propagate(ioException);
}
}
}).toSingle();
}
/**
* Given a polling state representing state of a PUT or PATCH operation, this method returns {@link Single} object,
* when subscribed to it, a single poll will be performed and emits the latest polling state. A poll will be
* performed only if the current polling state is not in terminal state.
*
* Note: this method does not implicitly introduce concurrency, by default the deferred action will be executed
* in scheduler (if any) set for the provided observable.
*
* @param pollingState the current polling state
* @param the type of the resource
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the observable of which a subscription will lead single polling action.
*/
private Single> pollPutOrPatchSingleAsync(final PollingState pollingState, final Type resourceType) {
pollingState.withResourceType(resourceType);
pollingState.withSerializerAdapter(restClient().serializerAdapter());
if (pollingState.isStatusTerminal()) {
if (pollingState.isStatusSucceeded() && pollingState.resource() == null) {
return updateStateFromGetResourceOperationAsync(pollingState, pollingState.putOrPatchResourceUri()).toSingle();
}
return Single.just(pollingState);
}
return putOrPatchPollingDispatcher(pollingState, pollingState.putOrPatchResourceUri())
.map(new Func1, PollingState>() {
@Override
public PollingState call(PollingState tPollingState) {
tPollingState.throwCloudExceptionIfInFailedState();
return tPollingState;
}
})
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState tPollingState) {
if (pollingState.isStatusSucceeded() && pollingState.resource() == null) {
return updateStateFromGetResourceOperationAsync(pollingState, pollingState.putOrPatchResourceUri());
}
return Observable.just(tPollingState);
}
})
.toSingle();
}
/**
* Given a polling state representing state of a PUT or PATCH operation, this method returns {@link Observable} object,
* when subscribed to it, a series of polling will be performed and emits each polling state to downstream.
* Polling will completes when the operation finish with success, failure or exception.
*
* Note: this method implicitly runs the polling on rx IO scheduler.
*
* @param pollingState the current polling state
* @param the type of the resource
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the observable of which a subscription will lead multiple polling action.
*/
private Observable> pollPutOrPatchAsync(final PollingState pollingState, final Type resourceType) {
pollingState.withResourceType(resourceType);
pollingState.withSerializerAdapter(restClient().serializerAdapter());
final int retryCount = 5;
return Observable.just(true)
.flatMap(new Func1>>() {
@Override
public Observable> call(Boolean aBoolean) {
return pollPutOrPatchSingleAsync(pollingState, resourceType).toObservable();
}
}).repeatWhen(new Func1, Observable>() {
@Override
public Observable call(Observable observable) {
return observable.flatMap(new Func1>() {
@Override
public Observable call(Void aVoid) {
return Observable.timer(pollingState.delayInMilliseconds(),
TimeUnit.MILLISECONDS, Schedulers.immediate());
}
});
}
}).retryWhen(new Func1, Observable>() {
@Override
public Observable call(Observable observable) {
return observable.zipWith(Observable.range(1, retryCount), new Func2() {
@Override
public Integer call(Throwable throwable, Integer integer) {
if (throwable instanceof CloudException || integer == retryCount) {
throw Exceptions.propagate(throwable);
}
return integer;
}
});
}
}).takeUntil(new Func1, Boolean>() {
@Override
public Boolean call(PollingState tPollingState) {
return pollingState.isStatusTerminal();
}
});
}
/**
* Handles an initial response from a PUT or PATCH operation response by polling
* the status of the operation asynchronously, calling the user provided callback
* when the operation terminates.
*
* @param observable the initial response from the PUT or PATCH operation.
* @param the return type of the caller
* @param the type of the response header
* @param resourceType the java.lang.reflect.Type of the resource.
* @param headerType the type of the response header
* @return the task describing the asynchronous polling.
*/
public Observable> getPutOrPatchResultWithHeadersAsync(Observable> observable, Type resourceType, final Class headerType) {
Observable> bodyResponse = getPutOrPatchResultAsync(observable, resourceType);
return bodyResponse
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(ServiceResponse serviceResponse) {
try {
return Observable
.just(new ServiceResponseWithHeaders<>(serviceResponse.body(),
restClient().serializerAdapter().deserialize(restClient().serializerAdapter().serialize(serviceResponse.response().headers()), headerType),
serviceResponse.response()));
} catch (IOException e) {
return Observable.error(e);
}
}
});
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation until the long running operation terminates.
*
* @param observable the initial observable from the POST or DELETE operation.
* @param the return type of the caller
* @param resourceType the type of the resource
* @return the terminal response for the operation.
* @throws CloudException REST exception
* @throws InterruptedException interrupted exception
* @throws IOException thrown by deserialization
*/
private ServiceResponse getPostOrDeleteResult(Observable> observable, Type resourceType) throws CloudException, InterruptedException, IOException {
Observable> asyncObservable = getPostOrDeleteResultAsync(observable, resourceType);
return asyncObservable.toBlocking().last();
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation until the long running operation terminates.
*
* @param observable the initial observable from the POST or DELETE operation.
* @param resourceType the java.lang.reflect.Type of the resource.
* @param headerType the type of the response header
* @param the return type of the caller
* @param the type of the response header
* @return the terminal response for the operation.
* @throws CloudException REST exception
* @throws InterruptedException interrupted exception
* @throws IOException thrown by deserialization
*/
public ServiceResponseWithHeaders getPostOrDeleteResultWithHeaders(Observable> observable, Type resourceType, Class headerType) throws CloudException, InterruptedException, IOException {
ServiceResponse bodyResponse = getPostOrDeleteResult(observable, resourceType);
return new ServiceResponseWithHeaders<>(
bodyResponse.body(),
restClient().serializerAdapter().deserialize(restClient().serializerAdapter().serialize(bodyResponse.response().headers()), headerType),
bodyResponse.response()
);
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation asynchronously, calling the user provided callback
* when the operation terminates.
*
* @param observable the initial response from the POST or DELETE operation.
* @param the return type of the caller.
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the task describing the asynchronous polling.
*/
public Observable> getPostOrDeleteResultAsync(Observable> observable, final Type resourceType) {
return this.getPostOrDeleteResultAsync(observable, LongRunningOperationOptions.DEFAULT, resourceType);
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation asynchronously, calling the user provided callback
* when the operation terminates.
*
* @param observable the initial response from the POST or DELETE operation.
* @param lroOptions long running operation options.
* @param the return type of the caller.
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the task describing the asynchronous polling.
*/
public Observable> getPostOrDeleteResultAsync(Observable> observable, final LongRunningOperationOptions lroOptions, final Type resourceType) {
return this.beginPostOrDeleteAsync(observable, lroOptions, resourceType)
.toObservable()
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState pollingState) {
if (pollingState.isStatusTerminal()) {
return Observable.just(pollingState);
} else {
// initial delay
return Observable.just(pollingState).delaySubscription(pollingState.delayInMilliseconds(), TimeUnit.MILLISECONDS, Schedulers.immediate());
}
}
})
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState pollingState) {
return pollPostOrDeleteAsync(pollingState, resourceType);
}
})
.last()
.map(new Func1, ServiceResponse>() {
@Override
public ServiceResponse call(PollingState pollingState) {
return new ServiceResponse<>(pollingState.resource(), pollingState.response());
}
});
}
/**
* Given an observable representing a deferred POST or DELETE action, this method returns {@link Single} object,
* when subscribed to it, the deferred action will be performed and emits the polling state containing information
* to track the progress of the action.
*
* @param observable an observable representing a deferred POST or DELETE operation.
* @param lroOptions long running operation options.
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the type of the resource
* @return the observable of which a subscription will lead POST or DELETE action.
*/
public Single> beginPostOrDeleteAsync(Observable> observable, final LongRunningOperationOptions lroOptions, final Type resourceType) {
return observable.map(new Func1, PollingState>() {
@Override
public PollingState call(Response response) {
RuntimeException exception = createExceptionFromResponse(response, 200, 202, 204);
if (exception != null) {
throw exception;
}
try {
final PollingState pollingState = PollingState.create(response, lroOptions, longRunningOperationRetryTimeout(), resourceType, restClient().serializerAdapter());
pollingState.withPollingUrlFromResponse(response);
pollingState.withPollingRetryTimeoutFromResponse(response);
return pollingState;
} catch (IOException ioException) {
throw Exceptions.propagate(ioException);
}
}
}).toSingle();
}
/**
* Given a polling state representing state of a POST or DELETE operation, this method returns {@link Single} object,
* when subscribed to it, a single poll will be performed and emits the latest polling state. A poll will be
* performed only if the current polling state is not in terminal state.
*
* @param pollingState the current polling state
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the type of the resource
* @return the observable of which a subscription will lead single polling action.
*/
private Single> pollPostOrDeleteSingleAsync(final PollingState pollingState, final Type resourceType) {
pollingState.withResourceType(resourceType);
pollingState.withSerializerAdapter(restClient().serializerAdapter());
if (pollingState.isStatusTerminal()) {
if (pollingState.resourcePending()) {
return updateStateFromLocationHeaderOnPostOrDeleteAsync(pollingState).toSingle();
}
return Single.just(pollingState);
}
return postOrDeletePollingDispatcher(pollingState)
.map(new Func1, PollingState>() {
@Override
public PollingState call(PollingState tPollingState) {
tPollingState.throwCloudExceptionIfInFailedState();
return tPollingState;
}
})
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(PollingState tPollingState) {
if (pollingState.resourcePending()) {
return updateStateFromLocationHeaderOnPostOrDeleteAsync(pollingState);
}
return Observable.just(pollingState);
}
})
.toSingle();
}
/**
* Given a polling state representing state of a POST or DELETE operation, this method returns {@link Observable} object,
* when subscribed to it, a series of polling will be performed and emits each polling state to downstream.
* Polling will completes when the operation finish with success, failure or exception.
*
* @param pollingState the current polling state
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the type of the resource
* @return the observable of which a subscription will lead multiple polling action.
*/
private Observable> pollPostOrDeleteAsync(final PollingState pollingState, final Type resourceType) {
pollingState.withResourceType(resourceType);
pollingState.withSerializerAdapter(restClient().serializerAdapter());
final int retryCount = 5;
return Observable.just(true)
.flatMap(new Func1>>() {
@Override
public Observable> call(Boolean aBoolean) {
return pollPostOrDeleteSingleAsync(pollingState, resourceType).toObservable();
}
}).repeatWhen(new Func1, Observable>() {
@Override
public Observable call(Observable observable) {
return observable.flatMap(new Func1>() {
@Override
public Observable call(Void aVoid) {
return Observable.timer(pollingState.delayInMilliseconds(),
TimeUnit.MILLISECONDS, Schedulers.immediate());
}
});
}
}).retryWhen(new Func1, Observable>() {
@Override
public Observable call(Observable observable) {
return observable.zipWith(Observable.range(1, retryCount), new Func2() {
@Override
public Integer call(Throwable throwable, Integer integer) {
if (throwable instanceof CloudException || integer == retryCount) {
throw Exceptions.propagate(throwable);
}
return integer;
}
});
}
}).takeUntil(new Func1, Boolean>() {
@Override
public Boolean call(PollingState tPollingState) {
return pollingState.isStatusTerminal();
}
});
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation asynchronously, calling the user provided callback
* when the operation terminates.
*
* @param observable the initial observable from the POST or DELETE operation.
* @param the return type of the caller
* @param the type of the response header
* @param resourceType the java.lang.reflect.Type of the resource.
* @param headerType the type of the response header
* @return the task describing the asynchronous polling.
*/
public Observable> getPostOrDeleteResultWithHeadersAsync(Observable> observable, Type resourceType, final Class headerType) {
Observable> bodyResponse = getPostOrDeleteResultAsync(observable, resourceType);
return bodyResponse
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(ServiceResponse serviceResponse) {
try {
return Observable
.just(new ServiceResponseWithHeaders<>(serviceResponse.body(),
restClient().serializerAdapter().deserialize(restClient().serializerAdapter().serialize(serviceResponse.response().headers()), headerType),
serviceResponse.response()));
} catch (IOException e) {
return Observable.error(e);
}
}
});
}
/**
* Handles an initial response from a POST or DELETE operation response by polling
* the status of the operation asynchronously, calling the user provided callback
* when the operation terminates.
*
* @param observable the initial observable from the POST or DELETE operation.
* @param lroOptions long running operation options.
* @param the return type of the caller
* @param the type of the response header
* @param resourceType the java.lang.reflect.Type of the resource.
* @param headerType the type of the response header
* @return the task describing the asynchronous polling.
*/
public Observable> getPostOrDeleteResultWithHeadersAsync(Observable> observable, final LongRunningOperationOptions lroOptions, Type resourceType, final Class headerType) {
Observable> bodyResponse = getPostOrDeleteResultAsync(observable, lroOptions, resourceType);
return bodyResponse
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(ServiceResponse serviceResponse) {
try {
return Observable
.just(new ServiceResponseWithHeaders<>(serviceResponse.body(),
restClient().serializerAdapter().deserialize(restClient().serializerAdapter().serialize(serviceResponse.response().headers()), headerType),
serviceResponse.response()));
} catch (IOException e) {
return Observable.error(e);
}
}
});
}
/**
* Given a polling state representing state of a LRO operation, this method returns {@link Single} object,
* when subscribed to it, a single poll will be performed and emits the latest polling state. A poll will be
* performed only if the current polling state is not in terminal state.
*
* Note: this method does not implicitly introduce concurrency, by default the deferred action will be executed
* in scheduler (if any) set for the provided observable.
*
* @param pollingState the current polling state
* @param the type of the resource
* @param resourceType the java.lang.reflect.Type of the resource.
* @return the observable of which a subscription will lead single polling action.
*/
public Single> pollSingleAsync(final PollingState pollingState, final Type resourceType) {
if (pollingState.initialHttpMethod().equalsIgnoreCase("PUT")
|| pollingState.initialHttpMethod().equalsIgnoreCase("PATCH")) {
return this.pollPutOrPatchSingleAsync(pollingState, resourceType);
}
if (pollingState.initialHttpMethod().equalsIgnoreCase("POST")
|| pollingState.initialHttpMethod().equalsIgnoreCase("DELETE")) {
return this.pollPostOrDeleteSingleAsync(pollingState, resourceType);
}
throw new IllegalArgumentException("PollingState contains unsupported http method:" + pollingState.initialHttpMethod());
}
/**
* Given a polling state representing state of an LRO operation, this method returns {@link Observable} object,
* when subscribed to it, a series of polling will be performed and emits each polling state to downstream.
* Polling will completes when the operation finish with success, failure or exception.
*
* @param pollingState the current polling state
* @param resourceType the java.lang.reflect.Type of the resource.
* @param the type of the resource
* @return the observable of which a subscription will lead multiple polling action.
*/
public Observable> pollAsync(final PollingState pollingState, final Type resourceType) {
if (pollingState.initialHttpMethod().equalsIgnoreCase("PUT")
|| pollingState.initialHttpMethod().equalsIgnoreCase("PATCH")) {
return this.pollPutOrPatchAsync(pollingState, resourceType);
}
if (pollingState.initialHttpMethod().equalsIgnoreCase("POST")
|| pollingState.initialHttpMethod().equalsIgnoreCase("DELETE")) {
return this.pollPostOrDeleteAsync(pollingState, resourceType);
}
throw new IllegalArgumentException("PollingState contains unsupported http method:" + pollingState.initialHttpMethod());
}
/**
* Polls from the location header and updates the polling state with the
* polling response for a PUT operation.
*
* @param pollingState the polling state for the current operation.
* @param the return type of the caller.
*/
private Observable> updateStateFromLocationHeaderOnPutAsync(final PollingState pollingState) {
return pollAsync(pollingState.locationHeaderLink(), pollingState.loggingContext())
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
int statusCode = response.code();
if (statusCode == 202) {
pollingState.withResponse(response);
pollingState.withStatus(AzureAsyncOperation.IN_PROGRESS_STATUS, statusCode);
} else if (statusCode == 200 || statusCode == 201) {
try {
pollingState.updateFromResponseOnPutPatch(response);
} catch (CloudException | IOException e) {
return Observable.error(e);
}
}
return Observable.just(pollingState);
}
});
}
/**
* Polls from the location header and updates the polling state with the
* polling response for a POST or DELETE operation.
*
* @param pollingState the polling state for the current operation.
* @param the return type of the caller.
*/
private Observable> updateStateFromLocationHeaderOnPostOrDeleteAsync(final PollingState pollingState) {
return pollAsync(pollingState.locationHeaderLink(), pollingState.loggingContext())
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
int statusCode = response.code();
if (statusCode == 202) {
pollingState.withResponse(response);
pollingState.withStatus(AzureAsyncOperation.IN_PROGRESS_STATUS, statusCode);
} else if (statusCode == 200 || statusCode == 201 || statusCode == 204) {
try {
pollingState.updateFromResponseOnDeletePost(response);
} catch (IOException e) {
return Observable.error(e);
}
}
return Observable.just(pollingState);
}
});
}
/**
* Polls from the provided URL and updates the polling state with the
* polling response.
*
* @param pollingState the polling state for the current operation.
* @param url the url to poll from
* @param the return type of the caller.
*/
private Observable> updateStateFromGetResourceOperationAsync(final PollingState pollingState, String url) {
return pollAsync(url, pollingState.loggingContext())
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
try {
pollingState.updateFromResponseOnPutPatch(response);
return Observable.just(pollingState);
} catch (CloudException | IOException e) {
return Observable.error(e);
}
}
});
}
/**
* Polls from the 'Azure-AsyncOperation' header and updates the polling
* state with the polling response.
*
* @param pollingState the polling state for the current operation.
* @param the return type of the caller.
*/
private Observable> updateStateFromAzureAsyncOperationHeaderOnPutAsync(final PollingState pollingState) {
return pollAsync(pollingState.azureAsyncOperationHeaderLink(), pollingState.loggingContext())
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
final AzureAsyncOperation asyncOperation;
try {
asyncOperation = AzureAsyncOperation.fromResponse(restClient().serializerAdapter(), response);
} catch (CloudException exception) {
return Observable.error(exception);
}
pollingState.withStatus(asyncOperation.status());
pollingState.withErrorBody(asyncOperation.getError());
pollingState.withResponse(response);
pollingState.withResource(null);
return Observable.just(pollingState);
}
});
}
/**
* Polls from the 'Azure-AsyncOperation' header and updates the polling
* state with the polling response.
*
* @param pollingState the polling state for the current operation.
* @param the return type of the caller.
*/
private Observable> updateStateFromAzureAsyncOperationHeaderOnPostOrDeleteAsync(final PollingState pollingState) {
return pollAsync(pollingState.azureAsyncOperationHeaderLink(), pollingState.loggingContext())
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
final AzureAsyncOperation asyncOperation;
try {
asyncOperation = AzureAsyncOperation.fromResponse(restClient().serializerAdapter(), response);
} catch (CloudException exception) {
return Observable.error(exception);
}
pollingState.withStatus(asyncOperation.status());
pollingState.withErrorBody(asyncOperation.getError());
pollingState.withResponse(response);
try {
T resource = restClient().serializerAdapter().deserialize(asyncOperation.rawString(), pollingState.resourceType());
pollingState.withResource(resource);
} catch (IOException e) {
// Ignore and let resource be null
}
return Observable.just(pollingState);
}
});
}
/**
* Polls from the URL provided.
*
* @param url the URL to poll from.
* @return the raw response.
*/
private Observable> pollAsync(String url, String loggingContext) {
URL endpoint;
try {
endpoint = new URL(url);
} catch (MalformedURLException e) {
return Observable.error(e);
}
AsyncService service = restClient().retrofit().create(AsyncService.class);
if (loggingContext != null && !loggingContext.endsWith(" (poll)")) {
loggingContext += " (poll)";
}
return service.get(endpoint.getFile(), serviceClientUserAgent, loggingContext)
.flatMap(new Func1, Observable>>() {
@Override
public Observable> call(Response response) {
RuntimeException exception = createExceptionFromResponse(response, 200, 201, 202, 204);
if (exception != null) {
return Observable.error(exception);
} else {
return Observable.just(response);
}
}
});
}
private RuntimeException createExceptionFromResponse(Response response, Integer... allowedStatusCodes) {
int statusCode = response.code();
ResponseBody responseBody;
if (response.isSuccessful()) {
responseBody = response.body();
} else {
responseBody = response.errorBody();
}
if (!Arrays.asList(allowedStatusCodes).contains(statusCode)) {
CloudException exception;
try {
String bodyString = responseBody.string();
CloudError errorBody = restClient().serializerAdapter().deserialize(bodyString, CloudError.class);
if (errorBody != null) {
exception = new CloudException(errorBody.message(), response, errorBody);
} else {
exception = new CloudException("Unknown error with status code " + statusCode + " and body " + bodyString, response, null);
}
return exception;
} catch (IOException e) {
/* ignore serialization errors on top of service errors */
return new RuntimeException("Unknown error with status code " + statusCode, e);
}
}
return null;
}
private Observable> putOrPatchPollingDispatcher(PollingState pollingState, String url) {
if (pollingState.azureAsyncOperationHeaderLink() != null) {
return updateStateFromAzureAsyncOperationHeaderOnPutAsync(pollingState);
} else if (pollingState.locationHeaderLink() != null) {
return updateStateFromLocationHeaderOnPutAsync(pollingState);
} else {
return updateStateFromGetResourceOperationAsync(pollingState, url);
}
}
private Observable> postOrDeletePollingDispatcher(PollingState pollingState) {
if (pollingState.azureAsyncOperationHeaderLink() != null) {
return updateStateFromAzureAsyncOperationHeaderOnPostOrDeleteAsync(pollingState);
} else if (pollingState.locationHeaderLink() != null) {
return updateStateFromLocationHeaderOnPostOrDeleteAsync(pollingState);
} else {
CloudException exception = new CloudException("Response does not contain an Azure-AsyncOperation or Location header.", pollingState.response(), pollingState.errorBody());
return Observable.error(exception);
}
}
/**
* The Retrofit service used for polling.
*/
private interface AsyncService {
@GET
Observable> get(@Url String url, @Header("User-Agent") String userAgent, @Header("x-ms-logging-context") String loggingHeader);
}
}