Skip to content

Commit

Permalink
Set service version query param for polling strategies (Azure#34216)
Browse files Browse the repository at this point in the history
* Set service version query param for polling strategies

* minor refactoring

* address pr comments

* update sync polling strategies

* overloads

* javadoc
  • Loading branch information
srnagar authored Mar 29, 2023
1 parent 45ad8ac commit ffdee7e
Show file tree
Hide file tree
Showing 13 changed files with 848 additions and 370 deletions.
3 changes: 3 additions & 0 deletions sdk/core/azure-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## 1.38.0-beta.1 (Unreleased)

### Features Added
- Added new constructor overload to `DefaultPollingStrategy`, `OperationResourcePollingStrategy`, `LocationPollingStrategy`
and their sync counterparts that allows setting a service version as query parameter of request URLs for polling and getting the final
result of a long-running operation.

### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.Objects;

/**
* The default polling strategy to use with Azure data plane services. The default polling strategy will attempt 3
Expand Down Expand Up @@ -76,10 +77,26 @@ public DefaultPollingStrategy(HttpPipeline httpPipeline, JsonSerializer serializ
* @throws NullPointerException If {@code httpPipeline} is null.
*/
public DefaultPollingStrategy(HttpPipeline httpPipeline, String endpoint, JsonSerializer serializer, Context context) {
this(new PollingStrategyOptions(httpPipeline)
.setEndpoint(endpoint)
.setSerializer(serializer)
.setContext(context));
}

/**
* Creates a chained polling strategy with 3 known polling strategies, {@link OperationResourcePollingStrategy},
* {@link LocationPollingStrategy}, and {@link StatusCheckPollingStrategy}, in this order, with a custom
* serializer.
*
* @param pollingStrategyOptions options to configure this polling strategy.
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
*/
public DefaultPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
this.chainedPollingStrategy = new ChainedPollingStrategy<>(Arrays.asList(
new OperationResourcePollingStrategy<>(httpPipeline, endpoint, serializer, null, context),
new LocationPollingStrategy<>(httpPipeline, endpoint, serializer, context),
new StatusCheckPollingStrategy<>(serializer)));
new OperationResourcePollingStrategy<>(null, pollingStrategyOptions),
new LocationPollingStrategy<>(pollingStrategyOptions),
new StatusCheckPollingStrategy<>(pollingStrategyOptions.getSerializer())));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.azure.core.util.Context;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.FluxUtil;
import com.azure.core.util.UrlBuilder;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.polling.implementation.PollingConstants;
import com.azure.core.util.polling.implementation.PollingUtils;
Expand Down Expand Up @@ -48,6 +49,7 @@ public class LocationPollingStrategy<T, U> implements PollingStrategy<T, U> {
private final HttpPipeline httpPipeline;
private final ObjectSerializer serializer;
private final Context context;
private final String serviceVersion;

/**
* Creates an instance of the location polling strategy using a JSON serializer.
Expand Down Expand Up @@ -92,10 +94,25 @@ public LocationPollingStrategy(HttpPipeline httpPipeline, ObjectSerializer seria
* @throws NullPointerException If {@code httpPipeline} is null.
*/
public LocationPollingStrategy(HttpPipeline httpPipeline, String endpoint, ObjectSerializer serializer, Context context) {
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
this.endpoint = endpoint;
this.serializer = (serializer == null) ? DEFAULT_SERIALIZER : serializer;
this.context = context == null ? Context.NONE : context;
this(new PollingStrategyOptions(httpPipeline)
.setEndpoint(endpoint)
.setSerializer(serializer)
.setContext(context));
}

/**
* Creates an instance of the location polling strategy.
*
* @param pollingStrategyOptions options to configure this polling strategy.
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
*/
public LocationPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
this.httpPipeline = pollingStrategyOptions.getHttpPipeline();
this.endpoint = pollingStrategyOptions.getEndpoint();
this.serializer = (pollingStrategyOptions.getSerializer() == null) ? DEFAULT_SERIALIZER : pollingStrategyOptions.getSerializer();
this.serviceVersion = pollingStrategyOptions.getServiceVersion();
this.context = pollingStrategyOptions.getContext() == null ? Context.NONE : pollingStrategyOptions.getContext();
}

@Override
Expand Down Expand Up @@ -142,7 +159,10 @@ public Mono<PollResponse<T>> onInitialResponse(Response<?> response, PollingCont

@Override
public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReference<T> pollResponseType) {
HttpRequest request = new HttpRequest(HttpMethod.GET, pollingContext.getData(PollingConstants.LOCATION));
String url = pollingContext.getData(PollingConstants.LOCATION);
url = setServiceVersionQueryParam(url);

HttpRequest request = new HttpRequest(HttpMethod.GET, url);
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
CoreUtils.mergeContexts(context1, this.context)))
.flatMap(response -> {
Expand All @@ -169,6 +189,15 @@ public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReferenc
});
}

private String setServiceVersionQueryParam(String url) {
if (!CoreUtils.isNullOrEmpty(this.serviceVersion)) {
UrlBuilder urlBuilder = UrlBuilder.parse(url);
urlBuilder.setQueryParameter("api-version", this.serviceVersion);
url = urlBuilder.toString();
}
return url;
}

@Override
public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
if (pollingContext.getLatestResponse().getStatus() == LongRunningOperationStatus.FAILED) {
Expand All @@ -192,6 +221,8 @@ public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resu
String latestResponseBody = pollingContext.getData(PollingConstants.POLL_RESPONSE_BODY);
return PollingUtils.deserializeResponse(BinaryData.fromString(latestResponseBody), serializer, resultType);
} else {
finalGetUrl = setServiceVersionQueryParam(finalGetUrl);

HttpRequest request = new HttpRequest(HttpMethod.GET, finalGetUrl);
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
CoreUtils.mergeContexts(context1, this.context)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.azure.core.util.Context;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.FluxUtil;
import com.azure.core.util.UrlBuilder;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.polling.implementation.PollResult;
import com.azure.core.util.polling.implementation.PollingConstants;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class OperationResourcePollingStrategy<T, U> implements PollingStrategy<T
private final String endpoint;
private final HttpHeaderName operationLocationHeaderName;
private final Context context;
private final String serviceVersion;

/**
* Creates an instance of the operation resource polling strategy using a JSON serializer and "Operation-Location"
Expand All @@ -60,7 +62,7 @@ public class OperationResourcePollingStrategy<T, U> implements PollingStrategy<T
* @param httpPipeline an instance of {@link HttpPipeline} to send requests with
*/
public OperationResourcePollingStrategy(HttpPipeline httpPipeline) {
this(httpPipeline, null, new DefaultJsonSerializer(), DEFAULT_OPERATION_LOCATION_HEADER, Context.NONE);
this(DEFAULT_OPERATION_LOCATION_HEADER, new PollingStrategyOptions(httpPipeline));
}

/**
Expand Down Expand Up @@ -97,19 +99,30 @@ public OperationResourcePollingStrategy(HttpPipeline httpPipeline, ObjectSeriali
*/
public OperationResourcePollingStrategy(HttpPipeline httpPipeline, String endpoint, ObjectSerializer serializer,
String operationLocationHeaderName, Context context) {
this(httpPipeline, endpoint, serializer,
operationLocationHeaderName == null ? null : HttpHeaderName.fromString(operationLocationHeaderName),
context);
this(operationLocationHeaderName == null ? null : HttpHeaderName.fromString(operationLocationHeaderName),
new PollingStrategyOptions(httpPipeline)
.setEndpoint(endpoint)
.setSerializer(serializer)
.setContext(context));
}

private OperationResourcePollingStrategy(HttpPipeline httpPipeline, String endpoint,
ObjectSerializer serializer, HttpHeaderName operationLocationHeaderName, Context context) {
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
this.endpoint = endpoint;
this.serializer = serializer != null ? serializer : new DefaultJsonSerializer();
/**
* Creates an instance of the operation resource polling strategy.
*
* @param operationLocationHeaderName a custom header for polling the long-running operation.
* @param pollingStrategyOptions options to configure this polling strategy.
* @throws NullPointerException if {@code pollingStrategyOptions} is null.
*/
public OperationResourcePollingStrategy(HttpHeaderName operationLocationHeaderName, PollingStrategyOptions pollingStrategyOptions) {
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
this.httpPipeline = pollingStrategyOptions.getHttpPipeline();
this.endpoint = pollingStrategyOptions.getEndpoint();
this.serializer = pollingStrategyOptions.getSerializer() != null ? pollingStrategyOptions.getSerializer() : new DefaultJsonSerializer();
this.operationLocationHeaderName = (operationLocationHeaderName == null)
? DEFAULT_OPERATION_LOCATION_HEADER : operationLocationHeaderName;
this.context = context == null ? Context.NONE : context;

this.serviceVersion = pollingStrategyOptions.getServiceVersion();
this.context = pollingStrategyOptions.getContext() == null ? Context.NONE : pollingStrategyOptions.getContext();
}

@Override
Expand Down Expand Up @@ -160,8 +173,11 @@ public Mono<PollResponse<T>> onInitialResponse(Response<?> response, PollingCont

@Override
public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReference<T> pollResponseType) {
HttpRequest request = new HttpRequest(HttpMethod.GET, pollingContext.getData(operationLocationHeaderName
.getCaseSensitiveName()));
String url = pollingContext.getData(operationLocationHeaderName
.getCaseSensitiveName());

url = setServiceVersionQueryParam(url);
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
CoreUtils.mergeContexts(context1, this.context))).flatMap(response -> response.getBodyAsByteArray()
.map(BinaryData::fromBytes)
Expand All @@ -183,6 +199,15 @@ public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReferenc
})));
}

private String setServiceVersionQueryParam(String url) {
if (!CoreUtils.isNullOrEmpty(this.serviceVersion)) {
UrlBuilder urlBuilder = UrlBuilder.parse(url);
urlBuilder.setQueryParameter("api-version", this.serviceVersion);
url = urlBuilder.toString();
}
return url;
}

@Override
public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
if (pollingContext.getLatestResponse().getStatus() == LongRunningOperationStatus.FAILED) {
Expand All @@ -207,6 +232,7 @@ public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resu
String latestResponseBody = pollingContext.getData(PollingConstants.POLL_RESPONSE_BODY);
return PollingUtils.deserializeResponse(BinaryData.fromString(latestResponseBody), serializer, resultType);
} else {
finalGetUrl = setServiceVersionQueryParam(finalGetUrl);
HttpRequest request = new HttpRequest(HttpMethod.GET, finalGetUrl);
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
CoreUtils.mergeContexts(context1, this.context)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.util.polling;

import com.azure.core.annotation.Fluent;
import com.azure.core.http.HttpPipeline;
import com.azure.core.util.Context;
import com.azure.core.util.serializer.ObjectSerializer;

import java.util.Objects;

/**
* Options to configure polling strategy.
*/
@Fluent
public final class PollingStrategyOptions {

private final HttpPipeline httpPipeline;
private String endpoint;
private ObjectSerializer serializer;
private Context context;
private String serviceVersion;

/**
* The {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
*
* @param httpPipeline {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
* @throws NullPointerException if {@code httpPipeline} is null.
*/
public PollingStrategyOptions(HttpPipeline httpPipeline) {
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
}

/**
* Returns {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
*
* @return {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
*/
public HttpPipeline getHttpPipeline() {
return this.httpPipeline;
}

/**
* Returns the endpoint that will be used as prefix if the service response returns a relative path for getting the
* long-running operation status and final result.
*
* @return the endpoint that will be used as prefix if the service response returns a relative path for getting the
* long-running operation status and final result.
*/
public String getEndpoint() {
return endpoint;
}

/**
* Sets the endpoint that will be used as prefix if the service response returns a relative path for getting the
* long-running operation status and final result.
*
* @param endpoint the endpoint that will be used as prefix if the service response returns a relative path for getting the
* long-running operation status and final result.
* @return the updated {@link PollingStrategyOptions} instance.
*/
public PollingStrategyOptions setEndpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}

/**
* Returns the serializer to use for serializing and deserializing the request and response.
*
* @return the serializer to use for serializing and deserializing the request and response.
*/
public ObjectSerializer getSerializer() {
return serializer;
}

/**
* Set the serializer to use for serializing and deserializing the request and response.
*
* @param serializer the serializer to use for serializing and deserializing the request and response.
* @return the updated {@link PollingStrategyOptions} instance.
*/
public PollingStrategyOptions setSerializer(ObjectSerializer serializer) {
this.serializer = serializer;
return this;
}

/**
* Returns the context to use for sending the request using the {@link #getHttpPipeline()}.
*
* @return the context to use for sending the request using the {@link #getHttpPipeline()}.
*/
public Context getContext() {
return context;
}

/**
* Sets the context to use for sending the request using the {@link #getHttpPipeline()}.
*
* @param context the context to use for sending the request using the {@link #getHttpPipeline()}.
* @return the updated {@link PollingStrategyOptions} instance.
*/
public PollingStrategyOptions setContext(Context context) {
this.context = context;
return this;
}

/**
* Returns the service version that will be added as query param to each polling
* request and final result request URL. If the request URL already contains a service version, it will be replaced
* by the service version set in this constructor.
*
* @return the service version to use for polling and getting the final result.
*/
public String getServiceVersion() {
return serviceVersion;
}

/**
* Sets the service version that will be added as query param to each polling
* request and final result request URL. If the request URL already contains a service version, it will be replaced
* by the service version set in this constructor.
*
* @param serviceVersion the service version to use for polling and getting the final result.
* @return the updated {@link PollingStrategyOptions} instance.
*/
public PollingStrategyOptions setServiceVersion(String serviceVersion) {
this.serviceVersion = serviceVersion;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.azure.core.util.serializer.TypeReference;

import java.util.Arrays;
import java.util.Objects;

/**
* The default synchronous polling strategy to use with Azure data plane services. The default polling strategy will
Expand Down Expand Up @@ -83,6 +84,22 @@ public SyncDefaultPollingStrategy(HttpPipeline httpPipeline, String endpoint, Js
new SyncStatusCheckPollingStrategy<>(serializer)));
}

/**
* Creates a chained polling strategy with 3 known polling strategies, {@link SyncOperationResourcePollingStrategy},
* {@link SyncLocationPollingStrategy}, and {@link SyncStatusCheckPollingStrategy}, in this order, with a custom
* serializer.
*
* @param pollingStrategyOptions options to configure this polling strategy.
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
*/
public SyncDefaultPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
this.chainedPollingStrategy = new SyncChainedPollingStrategy<>(Arrays.asList(
new SyncOperationResourcePollingStrategy<>(null, pollingStrategyOptions),
new SyncLocationPollingStrategy<>(pollingStrategyOptions),
new SyncStatusCheckPollingStrategy<>(pollingStrategyOptions.getSerializer())));
}

@Override
public U getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
return chainedPollingStrategy.getResult(pollingContext, resultType);
Expand Down
Loading

0 comments on commit ffdee7e

Please sign in to comment.