com.microsoft.rest.AzureClient.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.rest.AzureClient.java

Source

/**
 *
 * 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.rest;

import com.microsoft.rest.credentials.ServiceClientCredentials;
import com.microsoft.rest.serializer.AzureJacksonUtils;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.ResponseBody;
import retrofit.Call;
import retrofit.Response;
import retrofit.Retrofit;
import retrofit.http.GET;
import retrofit.http.Url;

import java.io.IOException;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
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 class AzureClient extends AzureServiceClient {
    /**
     * The interval time between two long running operation polls. Default is
     * used if null.
     */
    private Integer longRunningOperationRetryTimeout;
    /**
     * The credentials to use for authentication for long running operations.
     */
    private ServiceClientCredentials credentials;
    /**
     * The executor for asynchronous requests.
     */
    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    /**
     * Initializes an instance of this class.
     */
    public AzureClient() {
        super();
    }

    /**
     * Initializes an instance of this class with customized client metadata.
     *
     * @param client customized http client.
     * @param retrofitBuilder customized retrofit builder
     */
    public AzureClient(OkHttpClient client, Retrofit.Builder retrofitBuilder) {
        super(client, retrofitBuilder);
    }

    /**
     * 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 response  the initial response from the PUT or PATCH operation.
     * @param <T>       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
     */
    public <T> ServiceResponse<T> getPutOrPatchResult(Response<ResponseBody> response, Type resourceType)
            throws CloudException, InterruptedException, IOException {
        if (response == null) {
            throw new CloudException("response is null.");
        }

        int statusCode = response.code();
        ResponseBody responseBody;
        if (response.isSuccess()) {
            responseBody = response.body();
        } else {
            responseBody = response.errorBody();
        }
        if (statusCode != 200 && statusCode != 201 && statusCode != 202) {
            CloudException exception = new CloudException(statusCode + " is not a valid polling status code");
            exception.setResponse(response);
            if (responseBody != null) {
                exception.setBody(
                        (CloudError) new AzureJacksonUtils().deserialize(responseBody.string(), CloudError.class));
            }
            throw exception;
        }

        PollingState<T> pollingState = new PollingState<>(response, this.getLongRunningOperationRetryTimeout(),
                resourceType);
        String url = response.raw().request().urlString();

        // Check provisioning state
        while (!AzureAsyncOperation.getTerminalStatuses().contains(pollingState.getStatus())) {
            Thread.sleep(pollingState.getDelayInMilliseconds());

            if (pollingState.getAzureAsyncOperationHeaderLink() != null
                    && !pollingState.getAzureAsyncOperationHeaderLink().isEmpty()) {
                updateStateFromAzureAsyncOperationHeader(pollingState);
            } else if (pollingState.getLocationHeaderLink() != null
                    && !pollingState.getLocationHeaderLink().isEmpty()) {
                updateStateFromLocationHeaderOnPut(pollingState);
            } else {
                updateStateFromGetResourceOperation(pollingState, url);
            }
        }

        if (AzureAsyncOperation.SUCCESS_STATUS.equals(pollingState.getStatus())
                && pollingState.getResource() == null) {
            updateStateFromGetResourceOperation(pollingState, url);
        }

        if (AzureAsyncOperation.getFailedStatuses().contains(pollingState.getStatus())) {
            throw new CloudException("Async operation failed");
        }

        return new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse());
    }

    /**
     * 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 response  the initial response from the PUT or PATCH operation.
     * @param resourceType the type of the resource
     * @param headerType the type of the response header
     * @param <T>       the return type of the caller
     * @param <THeader> 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 <T, THeader> ServiceResponseWithHeaders<T, THeader> getPutOrPatchResultWithHeaders(
            Response<ResponseBody> response, Type resourceType, Class<THeader> headerType)
            throws CloudException, InterruptedException, IOException {
        ServiceResponse<T> bodyResponse = getPutOrPatchResult(response, resourceType);
        return new ServiceResponseWithHeaders<>(bodyResponse.getBody(),
                new AzureJacksonUtils().<THeader>deserialize(
                        AzureJacksonUtils.serialize(bodyResponse.getResponse().headers()), headerType),
                bodyResponse.getResponse());
    }

    /**
     * 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 response  the initial response from the PUT or PATCH operation.
     * @param <T>       the return type of the caller.
     * @param resourceType the type of the resource.
     * @param callback  the user callback to call when operation terminates.
     * @return          the task describing the asynchronous polling.
     */
    public <T> AsyncPollingTask<T> getPutOrPatchResultAsync(Response<ResponseBody> response, Type resourceType,
            ServiceCallback<T> callback) {
        if (response == null) {
            callback.failure(new ServiceException("response is null."));
            return null;
        }

        int statusCode = response.code();
        ResponseBody responseBody;
        if (response.isSuccess()) {
            responseBody = response.body();
        } else {
            responseBody = response.errorBody();
        }
        if (statusCode != 200 && statusCode != 201 && statusCode != 202) {
            CloudException exception = new CloudException(statusCode + " is not a valid polling status code");
            exception.setResponse(response);
            try {
                if (responseBody != null) {
                    exception.setBody((CloudError) new AzureJacksonUtils().deserialize(responseBody.string(),
                            CloudError.class));
                }
            } catch (Exception e) {
                /* ignore serialization errors on top of service errors */ }
            callback.failure(exception);
            return null;
        }

        PollingState<T> pollingState;
        try {
            pollingState = new PollingState<>(response, this.getLongRunningOperationRetryTimeout(), resourceType);
        } catch (IOException e) {
            callback.failure(e);
            return null;
        }
        String url = response.raw().request().urlString();

        // Task runner will take it from here
        PutPatchPollingTask<T> task = new PutPatchPollingTask<>(pollingState, url, callback);
        executor.schedule(task, pollingState.getDelayInMilliseconds(), TimeUnit.MILLISECONDS);
        return task;
    }

    /**
     * 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 response  the initial response from the PUT or PATCH operation.
     * @param <T>       the return type of the caller
     * @param <THeader> the type of the response header
     * @param resourceType the type of the resource.
     * @param headerType the type of the response header
     * @param callback  the user callback to call when operation terminates.
     * @return          the task describing the asynchronous polling.
     */
    public <T, THeader> AsyncPollingTask<T> getPutOrPatchResultWithHeadersAsync(Response<ResponseBody> response,
            Type resourceType, final Class<THeader> headerType, final ServiceCallback<T> callback) {
        return this.getPutOrPatchResultAsync(response, resourceType, new ServiceCallback<T>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<T> result) {
                try {
                    callback.success(new ServiceResponseWithHeaders<>(result.getBody(),
                            new AzureJacksonUtils().<THeader>deserialize(
                                    AzureJacksonUtils.serialize(result.getResponse().headers()), headerType),
                            result.getResponse()));
                } catch (IOException e) {
                    failure(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 response  the initial response from the POST or DELETE operation.
     * @param <T>       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
     */
    public <T> ServiceResponse<T> getPostOrDeleteResult(Response<ResponseBody> response, Type resourceType)
            throws CloudException, InterruptedException, IOException {
        if (response == null) {
            throw new CloudException("response is null.");
        }

        int statusCode = response.code();
        ResponseBody responseBody;
        if (response.isSuccess()) {
            responseBody = response.body();
        } else {
            responseBody = response.errorBody();
        }
        if (statusCode != 200 && statusCode != 202 && statusCode != 204) {
            CloudException exception = new CloudException(statusCode + " is not a valid polling status code");
            exception.setResponse(response);
            if (responseBody != null) {
                exception.setBody(
                        (CloudError) new AzureJacksonUtils().deserialize(responseBody.string(), CloudError.class));
            }
            throw exception;
        }

        PollingState<T> pollingState = new PollingState<>(response, this.getLongRunningOperationRetryTimeout(),
                resourceType);

        // Check provisioning state
        while (!AzureAsyncOperation.getTerminalStatuses().contains(pollingState.getStatus())) {
            Thread.sleep(pollingState.getDelayInMilliseconds());

            if (pollingState.getAzureAsyncOperationHeaderLink() != null
                    && !pollingState.getAzureAsyncOperationHeaderLink().isEmpty()) {
                updateStateFromAzureAsyncOperationHeader(pollingState);
            } else if (pollingState.getLocationHeaderLink() != null
                    && !pollingState.getLocationHeaderLink().isEmpty()) {
                updateStateFromLocationHeaderOnPostOrDelete(pollingState);
            } else {
                CloudException exception = new CloudException("No header in response");
                exception.setResponse(response);
                throw exception;
            }
        }

        // Check if operation failed
        if (AzureAsyncOperation.getFailedStatuses().contains(pollingState.getStatus())) {
            throw new CloudException("Async operation failed");
        }

        return new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse());
    }

    /**
     * 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 response  the initial response from the POST or DELETE operation.
     * @param resourceType the type of the resource
     * @param headerType the type of the response header
     * @param <T>       the return type of the caller
     * @param <THeader> 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 <T, THeader> ServiceResponseWithHeaders<T, THeader> getPostOrDeleteResultWithHeaders(
            Response<ResponseBody> response, Type resourceType, Class<THeader> headerType)
            throws CloudException, InterruptedException, IOException {
        ServiceResponse<T> bodyResponse = getPostOrDeleteResult(response, resourceType);
        return new ServiceResponseWithHeaders<>(bodyResponse.getBody(),
                new AzureJacksonUtils().<THeader>deserialize(
                        AzureJacksonUtils.serialize(bodyResponse.getResponse().headers()), headerType),
                bodyResponse.getResponse());
    }

    /**
     * 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 response  the initial response from the POST or DELETE operation.
     * @param <T>       the return type of the caller.
     * @param resourceType the type of the resource.
     * @param callback  the user callback to call when operation terminates.
     * @return          the task describing the asynchronous polling.
     */
    public <T> AsyncPollingTask<T> getPostOrDeleteResultAsync(Response<ResponseBody> response, Type resourceType,
            ServiceCallback<T> callback) {
        if (response == null) {
            callback.failure(new ServiceException("response is null."));
            return null;
        }

        int statusCode = response.code();
        ResponseBody responseBody;
        if (response.isSuccess()) {
            responseBody = response.body();
        } else {
            responseBody = response.errorBody();
        }
        if (statusCode != 200 && statusCode != 201 && statusCode != 202) {
            CloudException exception = new CloudException(statusCode + " is not a valid polling status code");
            exception.setResponse(response);
            try {
                if (responseBody != null) {
                    exception.setBody((CloudError) new AzureJacksonUtils().deserialize(responseBody.string(),
                            CloudError.class));
                }
            } catch (Exception e) {
                /* ignore serialization errors on top of service errors */ }
            callback.failure(exception);
            return null;
        }

        PollingState<T> pollingState;
        try {
            pollingState = new PollingState<>(response, this.getLongRunningOperationRetryTimeout(), resourceType);
        } catch (IOException e) {
            callback.failure(e);
            return null;
        }

        // Task runner will take it from here
        PostDeletePollingTask<T> task = new PostDeletePollingTask<>(pollingState, callback);
        executor.schedule(task, pollingState.getDelayInMilliseconds(), TimeUnit.MILLISECONDS);
        return task;
    }

    /**
     * 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 response  the initial response from the POST or DELETE operation.
     * @param <T>       the return type of the caller
     * @param <THeader> the type of the response header
     * @param resourceType the type of the resource.
     * @param headerType the type of the response header
     * @param callback  the user callback to call when operation terminates.
     * @return          the task describing the asynchronous polling.
     */
    public <T, THeader> AsyncPollingTask<T> getPostOrDeleteResultWithHeadersAsync(Response<ResponseBody> response,
            Type resourceType, final Class<THeader> headerType, final ServiceCallback<T> callback) {
        return this.getPostOrDeleteResultAsync(response, resourceType, new ServiceCallback<T>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<T> result) {
                try {
                    callback.success(new ServiceResponseWithHeaders<>(result.getBody(),
                            new AzureJacksonUtils().<THeader>deserialize(
                                    AzureJacksonUtils.serialize(result.getResponse().headers()), headerType),
                            result.getResponse()));
                } catch (IOException e) {
                    failure(e);
                }
            }
        });
    }

    /**
     * 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 <T> the return type of the caller.
     * @throws CloudException REST exception
     * @throws IOException thrown by deserialization
     */
    private <T> void updateStateFromLocationHeaderOnPut(PollingState<T> pollingState)
            throws CloudException, IOException {
        Response<ResponseBody> response = poll(pollingState.getLocationHeaderLink());
        int statusCode = response.code();
        if (statusCode == 202) {
            pollingState.setResponse(response);
            pollingState.setStatus(AzureAsyncOperation.IN_PROGRESS_STATUS);
        } else if (statusCode == 200 || statusCode == 201) {
            pollingState.updateFromResponseOnPutPatch(response);
        }
    }

    /**
     * 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 callback  the user callback to call when operation terminates.
     * @param <T> the return type of the caller.
     * @return the task describing the asynchronous polling.
     */
    private <T> Call<ResponseBody> updateStateFromLocationHeaderOnPutAsync(final PollingState<T> pollingState,
            final ServiceCallback<T> callback) {
        return pollAsync(pollingState.getLocationHeaderLink(), new ServiceCallback<ResponseBody>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<ResponseBody> result) {
                try {
                    int statusCode = result.getResponse().code();
                    if (statusCode == 202) {
                        pollingState.setResponse(result.getResponse());
                        pollingState.setStatus(AzureAsyncOperation.IN_PROGRESS_STATUS);
                    } else if (statusCode == 200 || statusCode == 201) {
                        pollingState.updateFromResponseOnPutPatch(result.<ResponseBody>getResponse());
                    }
                    callback.success(new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                } catch (Throwable t) {
                    failure(t);
                }
            }
        });
    }

    /**
     * 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 <T> the return type of the caller.
     * @throws CloudException service exception
     * @throws IOException thrown by deserialization
     */
    private <T> void updateStateFromLocationHeaderOnPostOrDelete(PollingState<T> pollingState)
            throws CloudException, IOException {
        Response<ResponseBody> response = poll(pollingState.getLocationHeaderLink());
        int statusCode = response.code();
        if (statusCode == 202) {
            pollingState.setResponse(response);
            pollingState.setStatus(AzureAsyncOperation.IN_PROGRESS_STATUS);
        } else if (statusCode == 200 || statusCode == 201 || statusCode == 204) {
            pollingState.updateFromResponseOnDeletePost(response);
        }
    }

    /**
     * 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 callback  the user callback to call when operation terminates.
     * @param <T> the return type of the caller.
     * @return the task describing the asynchronous polling.
     */
    private <T> Call<ResponseBody> updateStateFromLocationHeaderOnPostOrDeleteAsync(
            final PollingState<T> pollingState, final ServiceCallback<T> callback) {
        return pollAsync(pollingState.getLocationHeaderLink(), new ServiceCallback<ResponseBody>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<ResponseBody> result) {
                try {
                    int statusCode = result.getResponse().code();
                    if (statusCode == 202) {
                        pollingState.setResponse(result.getResponse());
                        pollingState.setStatus(AzureAsyncOperation.IN_PROGRESS_STATUS);
                    } else if (statusCode == 200 || statusCode == 201 || statusCode == 204) {
                        pollingState.updateFromResponseOnDeletePost(result.getResponse());
                    }
                    callback.success(new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                } catch (Throwable t) {
                    failure(t);
                }
            }
        });
    }

    /**
     * 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 <T> the return type of the caller.
     * @throws CloudException service exception
     * @throws IOException thrown by deserialization
     */
    private <T> void updateStateFromGetResourceOperation(PollingState<T> pollingState, String url)
            throws CloudException, IOException {
        Response<ResponseBody> response = poll(url);
        pollingState.updateFromResponseOnPutPatch(response);
    }

    /**
     * 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 callback  the user callback to call when operation terminates.
     * @param <T> the return type of the caller.
     * @return the task describing the asynchronous polling.
     */
    private <T> Call<ResponseBody> updateStateFromGetResourceOperationAsync(final PollingState<T> pollingState,
            String url, final ServiceCallback<T> callback) {
        return pollAsync(url, new ServiceCallback<ResponseBody>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<ResponseBody> result) {
                try {
                    pollingState.updateFromResponseOnPutPatch(result.getResponse());
                    callback.success(new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                } catch (Throwable t) {
                    failure(t);
                }
            }
        });
    }

    /**
     * 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 <T> the return type of the caller.
     * @throws CloudException service exception
     * @throws IOException thrown by deserialization
     */
    private <T> void updateStateFromAzureAsyncOperationHeader(PollingState<T> pollingState)
            throws CloudException, IOException {
        Response<ResponseBody> response = poll(pollingState.getAzureAsyncOperationHeaderLink());

        AzureAsyncOperation body = null;
        if (response.body() != null) {
            body = new AzureJacksonUtils().deserialize(response.body().string(), AzureAsyncOperation.class);
        }

        if (body == null || body.getStatus() == null) {
            CloudException exception = new CloudException("no body");
            exception.setResponse(response);
            if (response.errorBody() != null) {
                exception.setBody((CloudError) new AzureJacksonUtils().deserialize(response.errorBody().string(),
                        CloudError.class));
            }
            throw exception;
        }

        pollingState.setStatus(body.getStatus());
        pollingState.setResponse(response);
        pollingState.setResource(null);
    }

    /**
     * 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 callback  the user callback to call when operation terminates.
     * @param <T> the return type of the caller.
     * @return the task describing the asynchronous polling.
     */
    private <T> Call<ResponseBody> updateStateFromAzureAsyncOperationHeaderAsync(final PollingState<T> pollingState,
            final ServiceCallback<T> callback) {
        return pollAsync(pollingState.getAzureAsyncOperationHeaderLink(), new ServiceCallback<ResponseBody>() {
            @Override
            public void failure(Throwable t) {
                callback.failure(t);
            }

            @Override
            public void success(ServiceResponse<ResponseBody> result) {
                try {
                    AzureAsyncOperation body = null;
                    if (result.getBody() != null) {
                        body = new AzureJacksonUtils().deserialize(result.getBody().string(),
                                AzureAsyncOperation.class);
                    }
                    if (body == null || body.getStatus() == null) {
                        CloudException exception = new CloudException("no body");
                        exception.setResponse(result.getResponse());
                        if (result.getResponse().errorBody() != null) {
                            exception.setBody((CloudError) new AzureJacksonUtils()
                                    .deserialize(result.getResponse().errorBody().string(), CloudError.class));
                        }
                        failure(exception);
                    } else {
                        pollingState.setStatus(body.getStatus());
                        pollingState.setResponse(result.getResponse());
                        pollingState.setResource(null);
                        callback.success(
                                new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                    }
                } catch (IOException ex) {
                    failure(ex);
                }
            }
        });
    }

    /**
     * Polls from the URL provided.
     *
     * @param url the URL to poll from.
     * @return the raw response.
     * @throws CloudException REST exception
     * @throws IOException thrown by deserialization
     */
    private Response<ResponseBody> poll(String url) throws CloudException, IOException {
        URL endpoint;
        endpoint = new URL(url);
        int port = endpoint.getPort();
        if (port == -1) {
            port = endpoint.getDefaultPort();
        }
        AsyncService service = this.retrofitBuilder
                .baseUrl(endpoint.getProtocol() + "://" + endpoint.getHost() + ":" + port).build()
                .create(AsyncService.class);
        Response<ResponseBody> response = service.get(endpoint.getFile()).execute();
        int statusCode = response.code();
        if (statusCode != 200 && statusCode != 201 && statusCode != 202 && statusCode != 204) {
            CloudException exception = new CloudException(statusCode + " is not a valid polling status code");
            exception.setResponse(response);
            if (response.body() != null) {
                exception.setBody((CloudError) new AzureJacksonUtils().deserialize(response.body().string(),
                        CloudError.class));
            } else if (response.errorBody() != null) {
                exception.setBody((CloudError) new AzureJacksonUtils().deserialize(response.errorBody().string(),
                        CloudError.class));
            }
            throw exception;
        }
        return response;
    }

    /**
     * Polls asynchronously from the URL provided.
     *
     * @param url the URL to poll from.
     * @param callback  the user callback to call when operation terminates.
     * @return the {@link Call} object from Retrofit.
     */
    private Call<ResponseBody> pollAsync(String url, final ServiceCallback<ResponseBody> callback) {
        URL endpoint;
        try {
            endpoint = new URL(url);
        } catch (MalformedURLException e) {
            callback.failure(e);
            return null;
        }
        int port = endpoint.getPort();
        if (port == -1) {
            port = endpoint.getDefaultPort();
        }
        AsyncService service = this.retrofitBuilder
                .baseUrl(endpoint.getProtocol() + "://" + endpoint.getHost() + ":" + port).build()
                .create(AsyncService.class);
        Call<ResponseBody> call = service.get(endpoint.getFile());
        call.enqueue(new ServiceResponseCallback<ResponseBody>(callback) {
            @Override
            public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
                try {
                    int statusCode = response.code();
                    if (statusCode != 200 && statusCode != 201 && statusCode != 202 && statusCode != 204) {
                        CloudException exception = new CloudException(
                                statusCode + " is not a valid polling status code");
                        exception.setResponse(response);
                        if (response.body() != null) {
                            exception.setBody((CloudError) new AzureJacksonUtils()
                                    .deserialize(response.body().string(), CloudError.class));
                        } else if (response.errorBody() != null) {
                            exception.setBody((CloudError) new AzureJacksonUtils()
                                    .deserialize(response.errorBody().string(), CloudError.class));
                        }
                        callback.failure(exception);
                        return;
                    }
                    callback.success(new ServiceResponse<>(response.body(), response));
                } catch (IOException ex) {
                    callback.failure(ex);
                }
            }
        });
        return call;
    }

    /**
     * Gets the interval time between two long running operation polls.
     *
     * @return the time in milliseconds.
     */
    public Integer getLongRunningOperationRetryTimeout() {
        return longRunningOperationRetryTimeout;
    }

    /**
     * Sets the interval time between two long running operation polls.
     *
     * @param longRunningOperationRetryTimeout the time in milliseconds.
     */
    public void setLongRunningOperationRetryTimeout(Integer longRunningOperationRetryTimeout) {
        this.longRunningOperationRetryTimeout = longRunningOperationRetryTimeout;
    }

    /**
     * Gets the credentials used for authentication.
     *
     * @return the credentials.
     */
    public ServiceClientCredentials getCredentials() {
        return credentials;
    }

    /**
     * Sets the credentials used for authentication.
     *
     * @param credentials the credentials.
     */
    public void setCredentials(ServiceClientCredentials credentials) {
        this.credentials = credentials;
    }

    /**
     * The Retrofit service used for polling.
     */
    private interface AsyncService {
        @GET
        Call<ResponseBody> get(@Url String url);
    }

    /**
     * The task runner that describes the state of an asynchronous long running
     * operation.
     *
     * @param <T> the return type of the caller.
     */
    abstract class AsyncPollingTask<T> implements Runnable {
        /** The {@link Call} object from Retrofit. */
        protected Call<ResponseBody> call;
        /** The polling state for the current operation. */
        protected PollingState<T> pollingState;
        /** The callback used for asynchronous polling. */
        protected ServiceCallback<T> pollingCallback;
        /** The client callback to call when polling finishes. */
        protected ServiceCallback<T> clientCallback;

        /**
         * Gets the {@link Call} object from Retrofit so that the client can
         * cancel or perform other operations on the polling.
         *
         * @return the {@link Call} object.
         */
        public Call<ResponseBody> getRestCall() {
            return this.call;
        }
    }

    /**
     * The task runner that handles PUT or PATCH operations.
     *
     * @param <T> the return type of the caller.
     */
    class PutPatchPollingTask<T> extends AsyncPollingTask<T> {
        /** The URL to poll from. */
        private String url;

        /**
         * Creates an instance of Polling task for PUT or PATCH operations.
         *
         * @param pollingState the current polling state.
         * @param url the URL to poll from.
         * @param clientCallback the client callback to call when a terminal status is hit.
         */
        public PutPatchPollingTask(final PollingState<T> pollingState, final String url,
                final ServiceCallback<T> clientCallback) {
            this.create(pollingState, url, clientCallback);
        }

        /**
         * Creates an instance of Polling task for PUT or PATCH operations.
         *
         * @param pollingState the current polling state.
         * @param url the URL to poll from.
         * @param clientCallback the client callback to call when a terminal status is hit.
         * @return a PutPatchPollingTask instance
         */
        private PutPatchPollingTask<T> create(final PollingState<T> pollingState, final String url,
                final ServiceCallback<T> clientCallback) {
            this.call = null;
            this.pollingState = pollingState;
            this.url = url;
            this.clientCallback = clientCallback;
            this.pollingCallback = new ServiceCallback<T>() {
                @Override
                public void failure(Throwable t) {
                    clientCallback.failure(t);
                }

                @Override
                public void success(ServiceResponse<T> result) {
                    PutPatchPollingTask<T> task = new PutPatchPollingTask<>(pollingState, url, clientCallback);
                    executor.schedule(task, pollingState.getDelayInMilliseconds(), TimeUnit.MILLISECONDS);
                }
            };
            return this;
        }

        @Override
        public void run() {
            // Check provisioning state
            if (!AzureAsyncOperation.getTerminalStatuses().contains(pollingState.getStatus())) {
                if (pollingState.getAzureAsyncOperationHeaderLink() != null
                        && !pollingState.getAzureAsyncOperationHeaderLink().isEmpty()) {
                    this.call = updateStateFromAzureAsyncOperationHeaderAsync(pollingState, pollingCallback);
                } else if (pollingState.getLocationHeaderLink() != null
                        && !pollingState.getLocationHeaderLink().isEmpty()) {
                    this.call = updateStateFromLocationHeaderOnPutAsync(pollingState, pollingCallback);
                } else {
                    this.call = updateStateFromGetResourceOperationAsync(pollingState, url, pollingCallback);
                }
            } else {
                if (AzureAsyncOperation.SUCCESS_STATUS.equals(pollingState.getStatus())
                        && pollingState.getResource() == null) {
                    call = updateStateFromGetResourceOperationAsync(pollingState, url, clientCallback);
                } else if (AzureAsyncOperation.getFailedStatuses().contains(pollingState.getStatus())) {
                    clientCallback.failure(new ServiceException("Async operation failed"));
                } else {
                    clientCallback
                            .success(new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                }
            }
        }
    }

    /**
     * The task runner that handles POST or DELETE operations.
     *
     * @param <T> the return type of the caller.
     */
    class PostDeletePollingTask<T> extends AsyncPollingTask<T> {
        /**
         * Creates an instance of Polling task for POST or DELETE operations.
         *
         * @param pollingState the current polling state.
         * @param clientCallback the client callback to call when a terminal status is hit.
         */
        public PostDeletePollingTask(final PollingState<T> pollingState, final ServiceCallback<T> clientCallback) {
            this.create(pollingState, clientCallback);
        }

        /**
         * Creates an instance of Polling task for POST or DELETE operations.
         *
         * @param pollingState the current polling state.
         * @param clientCallback the client callback to call when a terminal status is hit.
         * @return a PostDeletePollingTask instance.
         */
        private PostDeletePollingTask<T> create(final PollingState<T> pollingState,
                final ServiceCallback<T> clientCallback) {
            this.call = null;
            this.pollingState = pollingState;
            this.pollingCallback = new ServiceCallback<T>() {
                @Override
                public void failure(Throwable t) {
                    clientCallback.failure(t);
                }

                @Override
                public void success(ServiceResponse<T> result) {
                    PostDeletePollingTask<T> task = new PostDeletePollingTask<>(pollingState, clientCallback);
                    executor.schedule(task, pollingState.getDelayInMilliseconds(), TimeUnit.MILLISECONDS);
                }
            };
            return this;
        }

        @Override
        public void run() {
            if (!AzureAsyncOperation.getTerminalStatuses().contains(pollingState.getStatus())) {
                if (pollingState.getAzureAsyncOperationHeaderLink() != null
                        && !pollingState.getAzureAsyncOperationHeaderLink().isEmpty()) {
                    updateStateFromAzureAsyncOperationHeaderAsync(pollingState, pollingCallback);
                } else if (pollingState.getLocationHeaderLink() != null
                        && !pollingState.getLocationHeaderLink().isEmpty()) {
                    updateStateFromLocationHeaderOnPostOrDeleteAsync(pollingState, pollingCallback);
                } else {
                    pollingCallback.failure(new ServiceException("No header in response"));
                }
            } else {
                // Check if operation failed
                if (AzureAsyncOperation.getFailedStatuses().contains(pollingState.getStatus())) {
                    clientCallback.failure(new ServiceException("Async operation failed"));
                } else {
                    clientCallback
                            .success(new ServiceResponse<>(pollingState.getResource(), pollingState.getResponse()));
                }
            }
        }
    }
}