Skip to content

Commit ffdee7e

Browse files
authored
Set service version query param for polling strategies (Azure#34216)
* Set service version query param for polling strategies * minor refactoring * address pr comments * update sync polling strategies * overloads * javadoc
1 parent 45ad8ac commit ffdee7e

13 files changed

+848
-370
lines changed

sdk/core/azure-core/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## 1.38.0-beta.1 (Unreleased)
44

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

710
### Breaking Changes
811

sdk/core/azure-core/src/main/java/com/azure/core/util/polling/DefaultPollingStrategy.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import reactor.core.publisher.Mono;
1313

1414
import java.util.Arrays;
15+
import java.util.Objects;
1516

1617
/**
1718
* The default polling strategy to use with Azure data plane services. The default polling strategy will attempt 3
@@ -76,10 +77,26 @@ public DefaultPollingStrategy(HttpPipeline httpPipeline, JsonSerializer serializ
7677
* @throws NullPointerException If {@code httpPipeline} is null.
7778
*/
7879
public DefaultPollingStrategy(HttpPipeline httpPipeline, String endpoint, JsonSerializer serializer, Context context) {
80+
this(new PollingStrategyOptions(httpPipeline)
81+
.setEndpoint(endpoint)
82+
.setSerializer(serializer)
83+
.setContext(context));
84+
}
85+
86+
/**
87+
* Creates a chained polling strategy with 3 known polling strategies, {@link OperationResourcePollingStrategy},
88+
* {@link LocationPollingStrategy}, and {@link StatusCheckPollingStrategy}, in this order, with a custom
89+
* serializer.
90+
*
91+
* @param pollingStrategyOptions options to configure this polling strategy.
92+
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
93+
*/
94+
public DefaultPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
95+
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
7996
this.chainedPollingStrategy = new ChainedPollingStrategy<>(Arrays.asList(
80-
new OperationResourcePollingStrategy<>(httpPipeline, endpoint, serializer, null, context),
81-
new LocationPollingStrategy<>(httpPipeline, endpoint, serializer, context),
82-
new StatusCheckPollingStrategy<>(serializer)));
97+
new OperationResourcePollingStrategy<>(null, pollingStrategyOptions),
98+
new LocationPollingStrategy<>(pollingStrategyOptions),
99+
new StatusCheckPollingStrategy<>(pollingStrategyOptions.getSerializer())));
83100
}
84101

85102
@Override

sdk/core/azure-core/src/main/java/com/azure/core/util/polling/LocationPollingStrategy.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.azure.core.util.Context;
1818
import com.azure.core.util.CoreUtils;
1919
import com.azure.core.util.FluxUtil;
20+
import com.azure.core.util.UrlBuilder;
2021
import com.azure.core.util.logging.ClientLogger;
2122
import com.azure.core.util.polling.implementation.PollingConstants;
2223
import com.azure.core.util.polling.implementation.PollingUtils;
@@ -48,6 +49,7 @@ public class LocationPollingStrategy<T, U> implements PollingStrategy<T, U> {
4849
private final HttpPipeline httpPipeline;
4950
private final ObjectSerializer serializer;
5051
private final Context context;
52+
private final String serviceVersion;
5153

5254
/**
5355
* Creates an instance of the location polling strategy using a JSON serializer.
@@ -92,10 +94,25 @@ public LocationPollingStrategy(HttpPipeline httpPipeline, ObjectSerializer seria
9294
* @throws NullPointerException If {@code httpPipeline} is null.
9395
*/
9496
public LocationPollingStrategy(HttpPipeline httpPipeline, String endpoint, ObjectSerializer serializer, Context context) {
95-
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
96-
this.endpoint = endpoint;
97-
this.serializer = (serializer == null) ? DEFAULT_SERIALIZER : serializer;
98-
this.context = context == null ? Context.NONE : context;
97+
this(new PollingStrategyOptions(httpPipeline)
98+
.setEndpoint(endpoint)
99+
.setSerializer(serializer)
100+
.setContext(context));
101+
}
102+
103+
/**
104+
* Creates an instance of the location polling strategy.
105+
*
106+
* @param pollingStrategyOptions options to configure this polling strategy.
107+
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
108+
*/
109+
public LocationPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
110+
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
111+
this.httpPipeline = pollingStrategyOptions.getHttpPipeline();
112+
this.endpoint = pollingStrategyOptions.getEndpoint();
113+
this.serializer = (pollingStrategyOptions.getSerializer() == null) ? DEFAULT_SERIALIZER : pollingStrategyOptions.getSerializer();
114+
this.serviceVersion = pollingStrategyOptions.getServiceVersion();
115+
this.context = pollingStrategyOptions.getContext() == null ? Context.NONE : pollingStrategyOptions.getContext();
99116
}
100117

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

143160
@Override
144161
public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReference<T> pollResponseType) {
145-
HttpRequest request = new HttpRequest(HttpMethod.GET, pollingContext.getData(PollingConstants.LOCATION));
162+
String url = pollingContext.getData(PollingConstants.LOCATION);
163+
url = setServiceVersionQueryParam(url);
164+
165+
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
146166
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
147167
CoreUtils.mergeContexts(context1, this.context)))
148168
.flatMap(response -> {
@@ -169,6 +189,15 @@ public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReferenc
169189
});
170190
}
171191

192+
private String setServiceVersionQueryParam(String url) {
193+
if (!CoreUtils.isNullOrEmpty(this.serviceVersion)) {
194+
UrlBuilder urlBuilder = UrlBuilder.parse(url);
195+
urlBuilder.setQueryParameter("api-version", this.serviceVersion);
196+
url = urlBuilder.toString();
197+
}
198+
return url;
199+
}
200+
172201
@Override
173202
public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
174203
if (pollingContext.getLatestResponse().getStatus() == LongRunningOperationStatus.FAILED) {
@@ -192,6 +221,8 @@ public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resu
192221
String latestResponseBody = pollingContext.getData(PollingConstants.POLL_RESPONSE_BODY);
193222
return PollingUtils.deserializeResponse(BinaryData.fromString(latestResponseBody), serializer, resultType);
194223
} else {
224+
finalGetUrl = setServiceVersionQueryParam(finalGetUrl);
225+
195226
HttpRequest request = new HttpRequest(HttpMethod.GET, finalGetUrl);
196227
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
197228
CoreUtils.mergeContexts(context1, this.context)))

sdk/core/azure-core/src/main/java/com/azure/core/util/polling/OperationResourcePollingStrategy.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.azure.core.util.Context;
1818
import com.azure.core.util.CoreUtils;
1919
import com.azure.core.util.FluxUtil;
20+
import com.azure.core.util.UrlBuilder;
2021
import com.azure.core.util.logging.ClientLogger;
2122
import com.azure.core.util.polling.implementation.PollResult;
2223
import com.azure.core.util.polling.implementation.PollingConstants;
@@ -52,6 +53,7 @@ public class OperationResourcePollingStrategy<T, U> implements PollingStrategy<T
5253
private final String endpoint;
5354
private final HttpHeaderName operationLocationHeaderName;
5455
private final Context context;
56+
private final String serviceVersion;
5557

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

6668
/**
@@ -97,19 +99,30 @@ public OperationResourcePollingStrategy(HttpPipeline httpPipeline, ObjectSeriali
9799
*/
98100
public OperationResourcePollingStrategy(HttpPipeline httpPipeline, String endpoint, ObjectSerializer serializer,
99101
String operationLocationHeaderName, Context context) {
100-
this(httpPipeline, endpoint, serializer,
101-
operationLocationHeaderName == null ? null : HttpHeaderName.fromString(operationLocationHeaderName),
102-
context);
102+
this(operationLocationHeaderName == null ? null : HttpHeaderName.fromString(operationLocationHeaderName),
103+
new PollingStrategyOptions(httpPipeline)
104+
.setEndpoint(endpoint)
105+
.setSerializer(serializer)
106+
.setContext(context));
103107
}
104108

105-
private OperationResourcePollingStrategy(HttpPipeline httpPipeline, String endpoint,
106-
ObjectSerializer serializer, HttpHeaderName operationLocationHeaderName, Context context) {
107-
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
108-
this.endpoint = endpoint;
109-
this.serializer = serializer != null ? serializer : new DefaultJsonSerializer();
109+
/**
110+
* Creates an instance of the operation resource polling strategy.
111+
*
112+
* @param operationLocationHeaderName a custom header for polling the long-running operation.
113+
* @param pollingStrategyOptions options to configure this polling strategy.
114+
* @throws NullPointerException if {@code pollingStrategyOptions} is null.
115+
*/
116+
public OperationResourcePollingStrategy(HttpHeaderName operationLocationHeaderName, PollingStrategyOptions pollingStrategyOptions) {
117+
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
118+
this.httpPipeline = pollingStrategyOptions.getHttpPipeline();
119+
this.endpoint = pollingStrategyOptions.getEndpoint();
120+
this.serializer = pollingStrategyOptions.getSerializer() != null ? pollingStrategyOptions.getSerializer() : new DefaultJsonSerializer();
110121
this.operationLocationHeaderName = (operationLocationHeaderName == null)
111122
? DEFAULT_OPERATION_LOCATION_HEADER : operationLocationHeaderName;
112-
this.context = context == null ? Context.NONE : context;
123+
124+
this.serviceVersion = pollingStrategyOptions.getServiceVersion();
125+
this.context = pollingStrategyOptions.getContext() == null ? Context.NONE : pollingStrategyOptions.getContext();
113126
}
114127

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

161174
@Override
162175
public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReference<T> pollResponseType) {
163-
HttpRequest request = new HttpRequest(HttpMethod.GET, pollingContext.getData(operationLocationHeaderName
164-
.getCaseSensitiveName()));
176+
String url = pollingContext.getData(operationLocationHeaderName
177+
.getCaseSensitiveName());
178+
179+
url = setServiceVersionQueryParam(url);
180+
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
165181
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
166182
CoreUtils.mergeContexts(context1, this.context))).flatMap(response -> response.getBodyAsByteArray()
167183
.map(BinaryData::fromBytes)
@@ -183,6 +199,15 @@ public Mono<PollResponse<T>> poll(PollingContext<T> pollingContext, TypeReferenc
183199
})));
184200
}
185201

202+
private String setServiceVersionQueryParam(String url) {
203+
if (!CoreUtils.isNullOrEmpty(this.serviceVersion)) {
204+
UrlBuilder urlBuilder = UrlBuilder.parse(url);
205+
urlBuilder.setQueryParameter("api-version", this.serviceVersion);
206+
url = urlBuilder.toString();
207+
}
208+
return url;
209+
}
210+
186211
@Override
187212
public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
188213
if (pollingContext.getLatestResponse().getStatus() == LongRunningOperationStatus.FAILED) {
@@ -207,6 +232,7 @@ public Mono<U> getResult(PollingContext<T> pollingContext, TypeReference<U> resu
207232
String latestResponseBody = pollingContext.getData(PollingConstants.POLL_RESPONSE_BODY);
208233
return PollingUtils.deserializeResponse(BinaryData.fromString(latestResponseBody), serializer, resultType);
209234
} else {
235+
finalGetUrl = setServiceVersionQueryParam(finalGetUrl);
210236
HttpRequest request = new HttpRequest(HttpMethod.GET, finalGetUrl);
211237
return FluxUtil.withContext(context1 -> httpPipeline.send(request,
212238
CoreUtils.mergeContexts(context1, this.context)))
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.core.util.polling;
5+
6+
import com.azure.core.annotation.Fluent;
7+
import com.azure.core.http.HttpPipeline;
8+
import com.azure.core.util.Context;
9+
import com.azure.core.util.serializer.ObjectSerializer;
10+
11+
import java.util.Objects;
12+
13+
/**
14+
* Options to configure polling strategy.
15+
*/
16+
@Fluent
17+
public final class PollingStrategyOptions {
18+
19+
private final HttpPipeline httpPipeline;
20+
private String endpoint;
21+
private ObjectSerializer serializer;
22+
private Context context;
23+
private String serviceVersion;
24+
25+
/**
26+
* The {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
27+
*
28+
* @param httpPipeline {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
29+
* @throws NullPointerException if {@code httpPipeline} is null.
30+
*/
31+
public PollingStrategyOptions(HttpPipeline httpPipeline) {
32+
this.httpPipeline = Objects.requireNonNull(httpPipeline, "'httpPipeline' cannot be null");
33+
}
34+
35+
/**
36+
* Returns {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
37+
*
38+
* @return {@link HttpPipeline} to use for polling and getting the final result of the long-running operation.
39+
*/
40+
public HttpPipeline getHttpPipeline() {
41+
return this.httpPipeline;
42+
}
43+
44+
/**
45+
* Returns the endpoint that will be used as prefix if the service response returns a relative path for getting the
46+
* long-running operation status and final result.
47+
*
48+
* @return the endpoint that will be used as prefix if the service response returns a relative path for getting the
49+
* long-running operation status and final result.
50+
*/
51+
public String getEndpoint() {
52+
return endpoint;
53+
}
54+
55+
/**
56+
* Sets the endpoint that will be used as prefix if the service response returns a relative path for getting the
57+
* long-running operation status and final result.
58+
*
59+
* @param endpoint the endpoint that will be used as prefix if the service response returns a relative path for getting the
60+
* long-running operation status and final result.
61+
* @return the updated {@link PollingStrategyOptions} instance.
62+
*/
63+
public PollingStrategyOptions setEndpoint(String endpoint) {
64+
this.endpoint = endpoint;
65+
return this;
66+
}
67+
68+
/**
69+
* Returns the serializer to use for serializing and deserializing the request and response.
70+
*
71+
* @return the serializer to use for serializing and deserializing the request and response.
72+
*/
73+
public ObjectSerializer getSerializer() {
74+
return serializer;
75+
}
76+
77+
/**
78+
* Set the serializer to use for serializing and deserializing the request and response.
79+
*
80+
* @param serializer the serializer to use for serializing and deserializing the request and response.
81+
* @return the updated {@link PollingStrategyOptions} instance.
82+
*/
83+
public PollingStrategyOptions setSerializer(ObjectSerializer serializer) {
84+
this.serializer = serializer;
85+
return this;
86+
}
87+
88+
/**
89+
* Returns the context to use for sending the request using the {@link #getHttpPipeline()}.
90+
*
91+
* @return the context to use for sending the request using the {@link #getHttpPipeline()}.
92+
*/
93+
public Context getContext() {
94+
return context;
95+
}
96+
97+
/**
98+
* Sets the context to use for sending the request using the {@link #getHttpPipeline()}.
99+
*
100+
* @param context the context to use for sending the request using the {@link #getHttpPipeline()}.
101+
* @return the updated {@link PollingStrategyOptions} instance.
102+
*/
103+
public PollingStrategyOptions setContext(Context context) {
104+
this.context = context;
105+
return this;
106+
}
107+
108+
/**
109+
* Returns the service version that will be added as query param to each polling
110+
* request and final result request URL. If the request URL already contains a service version, it will be replaced
111+
* by the service version set in this constructor.
112+
*
113+
* @return the service version to use for polling and getting the final result.
114+
*/
115+
public String getServiceVersion() {
116+
return serviceVersion;
117+
}
118+
119+
/**
120+
* Sets the service version that will be added as query param to each polling
121+
* request and final result request URL. If the request URL already contains a service version, it will be replaced
122+
* by the service version set in this constructor.
123+
*
124+
* @param serviceVersion the service version to use for polling and getting the final result.
125+
* @return the updated {@link PollingStrategyOptions} instance.
126+
*/
127+
public PollingStrategyOptions setServiceVersion(String serviceVersion) {
128+
this.serviceVersion = serviceVersion;
129+
return this;
130+
}
131+
}

sdk/core/azure-core/src/main/java/com/azure/core/util/polling/SyncDefaultPollingStrategy.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.azure.core.util.serializer.TypeReference;
1212

1313
import java.util.Arrays;
14+
import java.util.Objects;
1415

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

87+
/**
88+
* Creates a chained polling strategy with 3 known polling strategies, {@link SyncOperationResourcePollingStrategy},
89+
* {@link SyncLocationPollingStrategy}, and {@link SyncStatusCheckPollingStrategy}, in this order, with a custom
90+
* serializer.
91+
*
92+
* @param pollingStrategyOptions options to configure this polling strategy.
93+
* @throws NullPointerException If {@code pollingStrategyOptions} is null.
94+
*/
95+
public SyncDefaultPollingStrategy(PollingStrategyOptions pollingStrategyOptions) {
96+
Objects.requireNonNull(pollingStrategyOptions, "'pollingStrategyOptions' cannot be null");
97+
this.chainedPollingStrategy = new SyncChainedPollingStrategy<>(Arrays.asList(
98+
new SyncOperationResourcePollingStrategy<>(null, pollingStrategyOptions),
99+
new SyncLocationPollingStrategy<>(pollingStrategyOptions),
100+
new SyncStatusCheckPollingStrategy<>(pollingStrategyOptions.getSerializer())));
101+
}
102+
86103
@Override
87104
public U getResult(PollingContext<T> pollingContext, TypeReference<U> resultType) {
88105
return chainedPollingStrategy.getResult(pollingContext, resultType);

0 commit comments

Comments
 (0)