1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License.
3
4 package com.azure.data.appconfiguration;
5
6 import com.azure.core.implementation.annotation.ReturnType;
7 import com.azure.core.implementation.annotation.ServiceClient;
8 import com.azure.core.implementation.annotation.ServiceMethod;
9 import com.azure.data.appconfiguration.credentials.ConfigurationClientCredentials;
10 import com.azure.data.appconfiguration.models.ConfigurationSetting;
11 import com.azure.data.appconfiguration.models.SettingFields;
12 import com.azure.data.appconfiguration.models.SettingSelector;
13 import com.azure.core.exception.HttpResponseException;
14 import com.azure.core.exception.ResourceModifiedException;
15 import com.azure.core.exception.ResourceNotFoundException;
16 import com.azure.core.http.HttpPipeline;
17 import com.azure.core.http.rest.PagedFlux;
18 import com.azure.core.http.rest.PagedResponse;
19 import com.azure.core.http.rest.Response;
20 import com.azure.core.implementation.RestProxy;
21 import com.azure.core.util.logging.ClientLogger;
22 import com.azure.core.implementation.util.ImplUtils;
23 import com.azure.core.util.Context;
24 import org.reactivestreams.Publisher;
25 import reactor.core.publisher.Flux;
26 import reactor.core.publisher.Mono;
27
28 import java.net.URL;
29 import java.util.Objects;
30
31 import static com.azure.core.implementation.util.FluxUtil.withContext;
32
33 /**
34 * This class provides a client that contains all the operations for {@link ConfigurationSetting ConfigurationSettings}
35 * in Azure App Configuration Store. Operations allowed by the client are adding, retrieving, updating, and deleting
36 * ConfigurationSettings, and listing settings or revision of a setting based on a {@link SettingSelector filter}.
37 *
38 * <p><strong>Instantiating an Asynchronous Configuration Client</strong></p>
39 *
40 * {@codesnippet com.azure.data.applicationconfig.async.configurationclient.instantiation}
41 *
42 * <p>View {@link ConfigurationClientBuilder this} for additional ways to construct the client.</p>
43 *
44 * @see ConfigurationClientBuilder
45 * @see ConfigurationClientCredentials
46 */
47 @ServiceClient(builder = ConfigurationClientBuilder.class, isAsync = true, serviceInterfaces = ConfigurationService.class)
48 public final class ConfigurationAsyncClient {
49 private final ClientLoggerLogger.html#ClientLogger">ClientLogger logger = new ClientLogger(ConfigurationAsyncClient.class);
50
51 private static final String ETAG_ANY = "*";
52 private static final String RANGE_QUERY = "items=%s";
53
54 private final String serviceEndpoint;
55 private final ConfigurationService service;
56
57 /**
58 * Creates a ConfigurationAsyncClient that sends requests to the configuration service at {@code serviceEndpoint}.
59 * Each service call goes through the {@code pipeline}.
60 *
61 * @param serviceEndpoint URL for the App Configuration service.
62 * @param pipeline HttpPipeline that the HTTP requests and responses flow through.
63 */
64 ConfigurationAsyncClient(URL serviceEndpoint, HttpPipeline pipeline) {
65 this.service = RestProxy.create(ConfigurationService.class, pipeline);
66 this.serviceEndpoint = serviceEndpoint.toString();
67 }
68
69 /**
70 * Adds a configuration value in the service if that key does not exist.
71 *
72 * <p><strong>Code Samples</strong></p>
73 *
74 * <p>Add a setting with the key "prodDBConnection" and value "db_connection".</p>
75 *
76 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.addSetting#string-string}
77 *
78 * @param key The key of the configuration setting to add.
79 * @param value The value associated with this configuration setting key.
80 * @return The {@link ConfigurationSetting} that was created, if a key collision occurs or the key
81 * is an invalid value (which will also throw HttpResponseException described below).
82 * @throws IllegalArgumentException If {@code key} is {@code null}.
83 * @throws ResourceModifiedException If a ConfigurationSetting with the same key exists.
84 * @throws HttpResponseException If {@code key} is an empty string.
85 */
86 @ServiceMethod(returns = ReturnType.SINGLE)
87 public Mono<ConfigurationSetting> addSetting(String key, String value) {
88 return withContext(
89 context -> addSetting(new ConfigurationSetting().key(key).value(value), context))
90 .flatMap(response -> Mono.justOrEmpty(response.value()));
91 }
92
93 /**
94 * Adds a configuration value in the service if that key and label does not exist. The label value of the
95 * ConfigurationSetting is optional.
96 *
97 * <p><strong>Code Samples</strong></p>
98 *
99 * <p>Add a setting with the key "prodDBConnection", label "westUS", and value "db_connection".</p>
100 *
101 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.addSetting#ConfigurationSetting}
102 *
103 * @param setting The setting to add to the configuration service.
104 * @return The {@link ConfigurationSetting} that was created, if a key collision occurs or the key
105 * is an invalid value (which will also throw HttpResponseException described below).
106 * @throws NullPointerException If {@code setting} is {@code null}.
107 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
108 * @throws ResourceModifiedException If a ConfigurationSetting with the same key and label exists.
109 * @throws HttpResponseException If {@code key} is an empty string.
110 */
111 @ServiceMethod(returns = ReturnType.SINGLE)
112 public Mono<ConfigurationSetting> addSetting(ConfigurationSetting setting) {
113 return withContext(context -> addSetting(setting, context))
114 .flatMap(response -> Mono.justOrEmpty(response.value()));
115 }
116
117 /**
118 * Adds a configuration value in the service if that key and label does not exist. The label value of the
119 * ConfigurationSetting is optional.
120 *
121 * <p><strong>Code Samples</strong></p>
122 *
123 * <p>Add a setting with the key "prodDBConnection", label "westUS", and value "db_connection".</p>
124 *
125 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.addSettingWithResponse#ConfigurationSetting}
126 *
127 * @param setting The setting to add to the configuration service.
128 * @return A REST response containing the {@link ConfigurationSetting} that was created, if a key collision occurs or the key
129 * is an invalid value (which will also throw HttpResponseException described below).
130 * @throws NullPointerException If {@code setting} is {@code null}.
131 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
132 * @throws ResourceModifiedException If a ConfigurationSetting with the same key and label exists.
133 * @throws HttpResponseException If {@code key} is an empty string.
134 */
135 @ServiceMethod(returns = ReturnType.SINGLE)
136 public Mono<Response<ConfigurationSetting>> addSettingWithResponse(ConfigurationSetting setting) {
137 return withContext(context -> addSetting(setting, context));
138 }
139
140 Mono<Response<ConfigurationSetting>> addSetting(ConfigurationSetting setting, Context context) {
141 // Validate that setting and key is not null. The key is used in the service URL so it cannot be null.
142 validateSetting(setting);
143
144 // This service method call is similar to setSetting except we're passing If-Not-Match = "*". If the service
145 // finds any existing configuration settings, then its e-tag will match and the service will return an error.
146 return service.setKey(serviceEndpoint, setting.key(), setting.label(), setting, null, getETagValue(ETAG_ANY), context)
147 .doOnRequest(ignoredValue -> logger.info("Adding ConfigurationSetting - {}", setting))
148 .doOnSuccess(response -> logger.info("Added ConfigurationSetting - {}", response.value()))
149 .onErrorMap(ConfigurationAsyncClient::addSettingExceptionMapper)
150 .doOnError(error -> logger.warning("Failed to add ConfigurationSetting - {}", setting, error));
151 }
152
153 /**
154 * Creates or updates a configuration value in the service with the given key.
155 *
156 * <p><strong>Code Samples</strong></p>
157 *
158 * <p>Add a setting with the key "prodDBConnection" and value "db_connection".</p>
159 *
160 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.setSettingWithResponse#ConfigurationSetting}
161 *
162 * @param key The key of the configuration setting to create or update.
163 * @param value The value of this configuration setting.
164 * @return The {@link ConfigurationSetting} that was created or updated, if the key is an invalid
165 * value (which will also throw HttpResponseException described below).
166 * @throws IllegalArgumentException If {@code key} is {@code null}.
167 * @throws ResourceModifiedException If the setting exists and is locked.
168 * @throws HttpResponseException If {@code key} is an empty string.
169 */
170 @ServiceMethod(returns = ReturnType.SINGLE)
171 public Mono<ConfigurationSetting> setSetting(String key, String value) {
172 return withContext(
173 context -> setSetting(new ConfigurationSetting().key(key).value(value), context))
174 .flatMap(response -> Mono.justOrEmpty(response.value()));
175 }
176
177 /**
178 * Creates or updates a configuration value in the service. Partial updates are not supported and the entire
179 * configuration setting is updated.
180 *
181 * If {@link ConfigurationSetting#etag() etag} is specified, the configuration value is updated if the current
182 * setting's etag matches. If the etag's value is equal to the wildcard character ({@code "*"}), the setting
183 * will always be updated.
184 *
185 * <p><strong>Code Samples</strong></p>
186 *
187 * <p>Add a setting with the key "prodDBConnection", label "westUS", and value "db_connection".</p>
188 *
189 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.setSetting#ConfigurationSetting}
190 *
191 * @param setting The configuration setting to create or update.
192 * @return The {@link ConfigurationSetting} that was created or updated, if the key is an invalid
193 * value, the setting is locked, or an etag was provided but does not match the service's current etag value (which
194 * will also throw HttpResponseException described below).
195 * @throws NullPointerException If {@code setting} is {@code null}.
196 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
197 * @throws ResourceModifiedException If the {@link ConfigurationSetting#etag() etag} was specified, is not the
198 * wildcard character, and the current configuration value's etag does not match, or the
199 * setting exists and is locked.
200 * @throws HttpResponseException If {@code key} is an empty string.
201 */
202 @ServiceMethod(returns = ReturnType.SINGLE)
203 public Mono<ConfigurationSetting> setSetting(ConfigurationSetting setting) {
204 return withContext(context -> setSetting(setting, context))
205 .flatMap(response -> Mono.justOrEmpty(response.value()));
206 }
207
208 /**
209 * Creates or updates a configuration value in the service. Partial updates are not supported and the entire
210 * configuration setting is updated.
211 *
212 * If {@link ConfigurationSetting#etag() etag} is specified, the configuration value is updated if the current
213 * setting's etag matches. If the etag's value is equal to the wildcard character ({@code "*"}), the setting
214 * will always be updated.
215 *
216 * <p><strong>Code Samples</strong></p>
217 *
218 * <p>Add a setting with the key "prodDBConnection", label "westUS", and value "db_connection".</p>
219 *
220 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.setSettingWithResponse#ConfigurationSetting}
221 *
222 * @param setting The configuration setting to create or update.
223 * @return A REST response containing the {@link ConfigurationSetting} that was created or updated, if the key is an invalid
224 * value, the setting is locked, or an etag was provided but does not match the service's current etag value (which
225 * will also throw HttpResponseException described below).
226 * @throws NullPointerException If {@code setting} is {@code null}.
227 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
228 * @throws ResourceModifiedException If the {@link ConfigurationSetting#etag() etag} was specified, is not the
229 * wildcard character, and the current configuration value's etag does not match, or the
230 * setting exists and is locked.
231 * @throws HttpResponseException If {@code key} is an empty string.
232 */
233 @ServiceMethod(returns = ReturnType.SINGLE)
234 public Mono<Response<ConfigurationSetting>> setSettingWithResponse(ConfigurationSetting setting) {
235 return withContext(context -> setSetting(setting, context));
236 }
237
238 Mono<Response<ConfigurationSetting>> setSetting(ConfigurationSetting setting, Context context) {
239 // Validate that setting and key is not null. The key is used in the service URL so it cannot be null.
240 validateSetting(setting);
241
242 // This service method call is similar to addSetting except it will create or update a configuration setting.
243 // If the user provides an etag value, it is passed in as If-Match = "{etag value}". If the current value in the
244 // service has a matching etag then it matches, then its value is updated with what the user passed in.
245 // Otherwise, the service throws an exception because the current configuration value was updated and we have an
246 // old value locally.
247 // If no etag value was passed in, then the value is always added or updated.
248 return service.setKey(serviceEndpoint, setting.key(), setting.label(), setting, getETagValue(setting.etag()), null, context)
249 .doOnRequest(ignoredValue -> logger.info("Setting ConfigurationSetting - {}", setting))
250 .doOnSuccess(response -> logger.info("Set ConfigurationSetting - {}", response.value()))
251 .doOnError(error -> logger.warning("Failed to set ConfigurationSetting - {}", setting, error));
252 }
253
254 /**
255 * Updates an existing configuration value in the service with the given key. The setting must already exist.
256 *
257 *
258 * ><strong>Code Samples</strong></p>
259 *
260 * <p>Update a setting with the key "prodDBConnection" to have the value "updated_db_connection".</p>
261 *
262 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.updateSetting#string-string}
263 *
264 * @param key The key of the configuration setting to update.
265 * @param value The updated value of this configuration setting.
266 * @return The {@link ConfigurationSetting} that was updated, if the configuration value does not
267 * exist, is locked, or the key is an invalid value (which will also throw HttpResponseException described below).
268 * @throws IllegalArgumentException If {@code key} is {@code null}.
269 * @throws HttpResponseException If a ConfigurationSetting with the key does not exist or the configuration value
270 * is locked.
271 * @throws HttpResponseException If {@code key} is an empty string.
272 */
273 @ServiceMethod(returns = ReturnType.SINGLE)
274 public Mono<ConfigurationSetting> updateSetting(String key, String value) {
275 return withContext(
276 context -> updateSetting(new ConfigurationSetting().key(key).value(value), context))
277 .flatMap(response -> Mono.justOrEmpty(response.value()));
278 }
279
280 /**
281 * Updates an existing configuration value in the service. The setting must already exist. Partial updates are not
282 * supported, the entire configuration value is replaced.
283 *
284 * If {@link ConfigurationSetting#etag() etag} is specified, the configuration value is only updated if it matches.
285 *
286 * <p><strong>Code Samples</strong></p>
287 *
288 * <p>Update the setting with the key-label pair "prodDBConnection"-"westUS" to have the value "updated_db_connection".</p>
289 *
290 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.updateSetting#ConfigurationSetting}
291 *
292 * @param setting The setting to add or update in the service.
293 * @return The {@link ConfigurationSetting} that was updated, if the configuration value does not
294 * exist, is locked, or the key is an invalid value (which will also throw HttpResponseException described below).
295 * @throws NullPointerException If {@code setting} is {@code null}.
296 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
297 * @throws ResourceModifiedException If a ConfigurationSetting with the same key and label does not
298 * exist, the setting is locked, or {@link ConfigurationSetting#etag() etag} is specified but does not match
299 * the current value.
300 * @throws HttpResponseException If {@code key} is an empty string.
301 */
302 @ServiceMethod(returns = ReturnType.SINGLE)
303 public Mono<ConfigurationSetting> updateSetting(ConfigurationSetting setting) {
304 return withContext(context -> updateSetting(setting, context))
305 .flatMap(response -> Mono.justOrEmpty(response.value()));
306 }
307
308 /**
309 * Updates an existing configuration value in the service. The setting must already exist. Partial updates are not
310 * supported, the entire configuration value is replaced.
311 *
312 * If {@link ConfigurationSetting#etag() etag} is specified, the configuration value is only updated if it matches.
313 *
314 * <p><strong>Code Samples</strong></p>
315 *
316 * <p>Update the setting with the key-label pair "prodDBConnection"-"westUS" to have the value "updated_db_connection".</p>
317 *
318 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.updateSettingWithResponse#ConfigurationSetting}
319 *
320 * @param setting The setting to add or update in the service.
321 * @return A REST response containing the {@link ConfigurationSetting} that was updated, if the configuration value does not
322 * exist, is locked, or the key is an invalid value (which will also throw HttpResponseException described below).
323 * @throws NullPointerException If {@code setting} is {@code null}.
324 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
325 * @throws ResourceModifiedException If a ConfigurationSetting with the same key and label does not
326 * exist, the setting is locked, or {@link ConfigurationSetting#etag() etag} is specified but does not match
327 * the current value.
328 * @throws HttpResponseException If {@code key} is an empty string.
329 */
330 @ServiceMethod(returns = ReturnType.SINGLE)
331 public Mono<Response<ConfigurationSetting>> updateSettingWithResponse(ConfigurationSetting setting) {
332 return withContext(context -> updateSetting(setting, context));
333 }
334
335 Mono<Response<ConfigurationSetting>> updateSetting(ConfigurationSetting setting, Context context) {
336 // Validate that setting and key is not null. The key is used in the service URL so it cannot be null.
337 validateSetting(setting);
338
339 String etag = setting.etag() == null ? ETAG_ANY : setting.etag();
340
341 return service.setKey(serviceEndpoint, setting.key(), setting.label(), setting, getETagValue(etag), null, context)
342 .doOnRequest(ignoredValue -> logger.info("Updating ConfigurationSetting - {}", setting))
343 .doOnSuccess(response -> logger.info("Updated ConfigurationSetting - {}", response.value()))
344 .doOnError(error -> logger.warning("Failed to update ConfigurationSetting - {}", setting, error));
345 }
346
347 /**
348 * Attempts to get a ConfigurationSetting that matches the {@code key}.
349 *
350 * <p><strong>Code Samples</strong></p>
351 *
352 * <p>Retrieve the setting with the key "prodDBConnection".</p>
353 *
354 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.getSetting#string}
355 *
356 * @param key The key of the setting to retrieve.
357 * @return The {@link ConfigurationSetting} stored in the service, if the configuration value does
358 * not exist or the key is an invalid value (which will also throw HttpResponseException described below).
359 * @throws IllegalArgumentException If {@code key} is {@code null}.
360 * @throws ResourceNotFoundException If a ConfigurationSetting with {@code key} does not exist.
361 * @throws HttpResponseException If {@code key} is an empty string.
362 */
363 @ServiceMethod(returns = ReturnType.SINGLE)
364 public Mono<ConfigurationSetting> getSetting(String key) {
365 return withContext(
366 context -> getSetting(new ConfigurationSetting().key(key), context))
367 .flatMap(response -> Mono.justOrEmpty(response.value()));
368 }
369
370 /**
371 * Attempts to get the ConfigurationSetting given the {@code key}, optional {@code label}.
372 *
373 * <p><strong>Code Samples</strong></p>
374 *
375 * <p>Retrieve the setting with the key-label "prodDBConnection"-"westUS".</p>
376 *
377 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.getSetting#ConfigurationSetting}
378 *
379 * @param setting The setting to retrieve based on its key and optional label combination.
380 * @return The {@link ConfigurationSetting} stored in the service, if the configuration value does
381 * not exist or the key is an invalid value (which will also throw HttpResponseException described below).
382 * @throws NullPointerException If {@code setting} is {@code null}.
383 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
384 * @throws ResourceNotFoundException If a ConfigurationSetting with the same key and label does not exist.
385 * @throws HttpResponseException If the {@code} key is an empty string.
386 */
387 @ServiceMethod(returns = ReturnType.SINGLE)
388 public Mono<ConfigurationSetting> getSetting(ConfigurationSetting setting) {
389 return withContext(context -> getSetting(setting, context))
390 .flatMap(response -> Mono.justOrEmpty(response.value()));
391 }
392
393 /**
394 * Attempts to get the ConfigurationSetting given the {@code key}, optional {@code label}.
395 *
396 * <p><strong>Code Samples</strong></p>
397 *
398 * <p>Retrieve the setting with the key-label "prodDBConnection"-"westUS".</p>
399 *
400 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.getSettingWithResponse#ConfigurationSetting}
401 *
402 * @param setting The setting to retrieve based on its key and optional label combination.
403 * @return A REST response containing the {@link ConfigurationSetting} stored in the service, if the configuration value does
404 * not exist or the key is an invalid value (which will also throw HttpResponseException described below).
405 * @throws NullPointerException If {@code setting} is {@code null}.
406 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
407 * @throws ResourceNotFoundException If a ConfigurationSetting with the same key and label does not exist.
408 * @throws HttpResponseException If the {@code} key is an empty string.
409 */
410 @ServiceMethod(returns = ReturnType.SINGLE)
411 public Mono<Response<ConfigurationSetting>> getSettingWithResponse(ConfigurationSetting setting) {
412 return withContext(context -> getSetting(setting, context));
413 }
414
415 Mono<Response<ConfigurationSetting>> getSetting(ConfigurationSetting setting, Context context) {
416 // Validate that setting and key is not null. The key is used in the service URL so it cannot be null.
417 validateSetting(setting);
418
419 return service.getKeyValue(serviceEndpoint, setting.key(), setting.label(), null, null, null, null, context)
420 .doOnRequest(ignoredValue -> logger.info("Retrieving ConfigurationSetting - {}", setting))
421 .doOnSuccess(response -> logger.info("Retrieved ConfigurationSetting - {}", response.value()))
422 .doOnError(error -> logger.warning("Failed to get ConfigurationSetting - {}", setting, error));
423 }
424
425 /**
426 * Deletes the ConfigurationSetting with a matching {@code key}.
427 *
428 * <p><strong>Code Samples</strong></p>
429 *
430 * <p>Delete the setting with the key "prodDBConnection".</p>
431 *
432 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.deleteSetting#string}
433 *
434 * @param key The key of the setting to delete.
435 * @return The deleted ConfigurationSetting or {@code null} if it didn't exist. {@code null} is also returned if
436 * the {@code key} is an invalid value (which will also throw HttpResponseException described below).
437 * @throws IllegalArgumentException If {@code key} is {@code null}.
438 * @throws ResourceModifiedException If the ConfigurationSetting is locked.
439 * @throws HttpResponseException If {@code key} is an empty string.
440 */
441 @ServiceMethod(returns = ReturnType.SINGLE)
442 public Mono<ConfigurationSetting> deleteSetting(String key) {
443 return withContext(
444 context -> deleteSetting(new ConfigurationSetting().key(key), context))
445 .flatMap(response -> Mono.justOrEmpty(response.value()));
446 }
447
448 /**
449 * Deletes the {@link ConfigurationSetting} with a matching key, along with the given label and etag.
450 *
451 * If {@link ConfigurationSetting#etag() etag} is specified and is not the wildcard character ({@code "*"}),
452 * then the setting is <b>only</b> deleted if the etag matches the current etag; this means that no one has updated
453 * the ConfigurationSetting yet.
454 *
455 * <p><strong>Code Samples</strong></p>
456 *
457 * <p>Delete the setting with the key-label "prodDBConnection"-"westUS".</p>
458 *
459 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.deleteSetting#ConfigurationSetting}
460 *
461 * @param setting The ConfigurationSetting to delete.
462 * @return The deleted ConfigurationSetting or {@code null} if didn't exist. {@code null} is also returned if
463 * the {@code key} is an invalid value or {@link ConfigurationSetting#etag() etag} is set but does not match the
464 * current etag (which will also throw HttpResponseException described below).
465 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
466 * @throws NullPointerException When {@code setting} is {@code null}.
467 * @throws ResourceModifiedException If the ConfigurationSetting is locked.
468 * @throws ResourceNotFoundException If {@link ConfigurationSetting#etag() etag} is specified, not the wildcard
469 * character, and does not match the current etag value.
470 * @throws HttpResponseException If {@code key} is an empty string.
471 */
472 @ServiceMethod(returns = ReturnType.SINGLE)
473 public Mono<ConfigurationSetting> deleteSetting(ConfigurationSetting setting) {
474 return withContext(context -> deleteSetting(setting, context))
475 .flatMap(response -> Mono.justOrEmpty(response.value()));
476 }
477
478 /**
479 * Deletes the {@link ConfigurationSetting} with a matching key, along with the given label and etag.
480 *
481 * If {@link ConfigurationSetting#etag() etag} is specified and is not the wildcard character ({@code "*"}),
482 * then the setting is <b>only</b> deleted if the etag matches the current etag; this means that no one has updated
483 * the ConfigurationSetting yet.
484 *
485 * <p><strong>Code Samples</strong></p>
486 *
487 * <p>Delete the setting with the key-label "prodDBConnection"-"westUS".</p>
488 *
489 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.deleteSettingWithResponse#ConfigurationSetting}
490 *
491 * @param setting The ConfigurationSetting to delete.
492 * @return A REST response containing the deleted ConfigurationSetting or {@code null} if didn't exist. {@code null} is also returned if
493 * the {@code key} is an invalid value or {@link ConfigurationSetting#etag() etag} is set but does not match the
494 * current etag (which will also throw HttpResponseException described below).
495 * @throws IllegalArgumentException If {@link ConfigurationSetting#key() key} is {@code null}.
496 * @throws NullPointerException When {@code setting} is {@code null}.
497 * @throws ResourceModifiedException If the ConfigurationSetting is locked.
498 * @throws ResourceNotFoundException If {@link ConfigurationSetting#etag() etag} is specified, not the wildcard
499 * character, and does not match the current etag value.
500 * @throws HttpResponseException If {@code key} is an empty string.
501 */
502 @ServiceMethod(returns = ReturnType.SINGLE)
503 public Mono<Response<ConfigurationSetting>> deleteSettingWithResponse(ConfigurationSetting setting) {
504 return withContext(context -> deleteSetting(setting, context));
505 }
506
507 Mono<Response<ConfigurationSetting>> deleteSetting(ConfigurationSetting setting, Context context) {
508 // Validate that setting and key is not null. The key is used in the service URL so it cannot be null.
509 validateSetting(setting);
510
511 return service.delete(serviceEndpoint, setting.key(), setting.label(), getETagValue(setting.etag()), null, context)
512 .doOnRequest(ignoredValue -> logger.info("Deleting ConfigurationSetting - {}", setting))
513 .doOnSuccess(response -> logger.info("Deleted ConfigurationSetting - {}", response.value()))
514 .doOnError(error -> logger.warning("Failed to delete ConfigurationSetting - {}", setting, error));
515 }
516
517 /**
518 * Fetches the configuration settings that match the {@code options}. If {@code options} is {@code null}, then all
519 * the {@link ConfigurationSetting configuration settings} are fetched with their current values.
520 *
521 * <p><strong>Code Samples</strong></p>
522 *
523 * <p>Retrieve all settings that use the key "prodDBConnection".</p>
524 *
525 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.listsettings}
526 *
527 * @param options Optional. Options to filter configuration setting results from the service.
528 * @return A Flux of ConfigurationSettings that matches the {@code options}. If no options were provided, the Flux
529 * contains all of the current settings in the service.
530 */
531 @ServiceMethod(returns = ReturnType.COLLECTION)
532 public PagedFlux<ConfigurationSetting> listSettings(SettingSelector options) {
533 return new PagedFlux<>(() -> withContext(context -> listFirstPageSettings(options, context)),
534 continuationToken -> withContext(context -> listNextPageSettings(context, continuationToken)));
535 }
536
537 PagedFlux<ConfigurationSetting> listSettings(SettingSelector options, Context context) {
538 return new PagedFlux<>(() -> listFirstPageSettings(options, context),
539 continuationToken -> listNextPageSettings(context, continuationToken));
540 }
541
542 private Mono<PagedResponse<ConfigurationSetting>> listNextPageSettings(Context context, String continuationToken) {
543 if (continuationToken == null || continuationToken.isEmpty()) {
544 return Mono.empty();
545 }
546
547 return service.listKeyValues(serviceEndpoint, continuationToken, context)
548 .doOnRequest(ignoredValue -> logger.info("Retrieving the next listing page - Page {}", continuationToken))
549 .doOnSuccess(response -> logger.info("Retrieved the next listing page - Page {}", continuationToken))
550 .doOnError(error -> logger.warning("Failed to retrieve the next listing page - Page {}", continuationToken,
551 error));
552 }
553
554 private Mono<PagedResponse<ConfigurationSetting>> listFirstPageSettings(SettingSelector options, Context context) {
555 if (options == null) {
556 return service.listKeyValues(serviceEndpoint, null, null, null, null, context)
557 .doOnRequest(ignoredValue -> logger.info("Listing all ConfigurationSettings"))
558 .doOnSuccess(response -> logger.info("Listed all ConfigurationSettings"))
559 .doOnError(error -> logger.warning("Failed to list all ConfigurationSetting", error));
560 }
561
562 String fields = ImplUtils.arrayToString(options.fields(), SettingFields::toStringMapper);
563 String keys = ImplUtils.arrayToString(options.keys(), key -> key);
564 String labels = ImplUtils.arrayToString(options.labels(), label -> label);
565
566 return service.listKeyValues(serviceEndpoint, keys, labels, fields, options.acceptDateTime(), context)
567 .doOnRequest(ignoredValue -> logger.info("Listing ConfigurationSettings - {}", options))
568 .doOnSuccess(response -> logger.info("Listed ConfigurationSettings - {}", options))
569 .doOnError(error -> logger.warning("Failed to list ConfigurationSetting - {}", options, error));
570
571 }
572
573 /**
574 * Lists chronological/historical representation of {@link ConfigurationSetting} resource(s). Revisions are provided
575 * in descending order from their {@link ConfigurationSetting#lastModified() lastModified} date. Revisions expire
576 * after a period of time. The service maintains change history for up to 7 days.
577 *
578 * If {@code options} is {@code null}, then all the {@link ConfigurationSetting ConfigurationSettings} are fetched
579 * in their current state. Otherwise, the results returned match the parameters given in {@code options}.
580 *
581 * <p><strong>Code Samples</strong></p>
582 *
583 * <p>Retrieve all revisions of the setting that has the key "prodDBConnection".</p>
584 *
585 * {@codesnippet com.azure.data.appconfiguration.configurationasyncclient.listsettingrevisions}
586 *
587 * @param selector Optional. Used to filter configuration setting revisions from the service.
588 * @return Revisions of the ConfigurationSetting
589 */
590 @ServiceMethod(returns = ReturnType.COLLECTION)
591 public PagedFlux<ConfigurationSetting> listSettingRevisions(SettingSelector selector) {
592 return new PagedFlux<>(() ->
593 withContext(context -> listSettingRevisionsFirstPage(selector, context)),
594 continuationToken -> withContext(context -> listSettingRevisionsNextPage(continuationToken, context)));
595 }
596
597 Mono<PagedResponse<ConfigurationSetting>> listSettingRevisionsFirstPage(SettingSelector selector, Context context) {
598 Mono<PagedResponse<ConfigurationSetting>> result;
599
600 if (selector != null) {
601 String fields = ImplUtils.arrayToString(selector.fields(), SettingFields::toStringMapper);
602 String keys = ImplUtils.arrayToString(selector.keys(), key -> key);
603 String labels = ImplUtils.arrayToString(selector.labels(), label -> label);
604 String range = selector.range() != null ? String.format(RANGE_QUERY, selector.range()) : null;
605
606 result = service.listKeyValueRevisions(serviceEndpoint, keys, labels, fields, selector.acceptDateTime(), range, context)
607 .doOnRequest(ignoredValue -> logger.info("Listing ConfigurationSetting revisions - {}", selector))
608 .doOnSuccess(response -> logger.info("Listed ConfigurationSetting revisions - {}", selector))
609 .doOnError(error -> logger.warning("Failed to list ConfigurationSetting revisions - {}", selector, error));
610 } else {
611 result = service.listKeyValueRevisions(serviceEndpoint, null, null, null, null, null, context)
612 .doOnRequest(ignoredValue -> logger.info("Listing ConfigurationSetting revisions"))
613 .doOnSuccess(response -> logger.info("Listed ConfigurationSetting revisions"))
614 .doOnError(error -> logger.warning("Failed to list all ConfigurationSetting revisions", error));
615 }
616
617 return result;
618 }
619
620 Mono<PagedResponse<ConfigurationSetting>> listSettingRevisionsNextPage(String nextPageLink, Context context) {
621 Mono<PagedResponse<ConfigurationSetting>> result = service.listKeyValues(serviceEndpoint, nextPageLink, context)
622 .doOnRequest(ignoredValue -> logger.info("Retrieving the next listing page - Page {}", nextPageLink))
623 .doOnSuccess(response -> logger.info("Retrieved the next listing page - Page {}", nextPageLink))
624 .doOnError(error -> logger.warning("Failed to retrieve the next listing page - Page {}", nextPageLink, error));
625 return result;
626 }
627
628 Flux<ConfigurationSetting> listSettingRevisions(SettingSelector selector, Context context) {
629 Mono<PagedResponse<ConfigurationSetting>> result;
630
631 if (selector != null) {
632 String fields = ImplUtils.arrayToString(selector.fields(), SettingFields::toStringMapper);
633 String keys = ImplUtils.arrayToString(selector.keys(), key -> key);
634 String labels = ImplUtils.arrayToString(selector.labels(), label -> label);
635 String range = selector.range() != null ? String.format(RANGE_QUERY, selector.range()) : null;
636
637 result = service.listKeyValueRevisions(serviceEndpoint, keys, labels, fields, selector.acceptDateTime(), range, context)
638 .doOnRequest(ignoredValue -> logger.info("Listing ConfigurationSetting revisions - {}", selector))
639 .doOnSuccess(response -> logger.info("Listed ConfigurationSetting revisions - {}", selector))
640 .doOnError(error -> logger.warning("Failed to list ConfigurationSetting revisions - {}", selector, error));
641 } else {
642 result = service.listKeyValueRevisions(serviceEndpoint, null, null, null, null, null, context)
643 .doOnRequest(ignoredValue -> logger.info("Listing ConfigurationSetting revisions"))
644 .doOnSuccess(response -> logger.info("Listed ConfigurationSetting revisions"))
645 .doOnError(error -> logger.warning("Failed to list all ConfigurationSetting revisions", error));
646 }
647
648 return result.flatMapMany(r -> extractAndFetchConfigurationSettings(r, context));
649 }
650
651 private Flux<ConfigurationSetting> listSettings(String nextPageLink, Context context) {
652 Mono<PagedResponse<ConfigurationSetting>> result = service.listKeyValues(serviceEndpoint, nextPageLink, context)
653 .doOnRequest(ignoredValue -> logger.info("Retrieving the next listing page - Page {}", nextPageLink))
654 .doOnSuccess(response -> logger.info("Retrieved the next listing page - Page {}", nextPageLink))
655 .doOnError(error -> logger.warning("Failed to retrieve the next listing page - Page {}", nextPageLink, error));
656
657 return result.flatMapMany(r -> extractAndFetchConfigurationSettings(r, context));
658 }
659
660 private Publisher<ConfigurationSetting> extractAndFetchConfigurationSettings(PagedResponse<ConfigurationSetting> page, Context context) {
661 return ImplUtils.extractAndFetch(page, context, this::listSettings);
662 }
663
664 /*
665 * Azure Configuration service requires that the etag value is surrounded in quotation marks.
666 *
667 * @param etag The etag to get the value for. If null is pass in, an empty string is returned.
668 * @return The etag surrounded by quotations. (ex. "etag")
669 */
670 private static String getETagValue(String etag) {
671 return etag == null ? "" : "\"" + etag + "\"";
672 }
673
674 /*
675 * Ensure that setting is not null. And, key cannot be null because it is part of the service REST URL.
676 */
677 private static void validateSetting(ConfigurationSetting setting) {
678 Objects.requireNonNull(setting);
679
680 if (setting.key() == null) {
681 throw new IllegalArgumentException("Parameter 'key' is required and cannot be null.");
682 }
683 }
684
685 /*
686 * Remaps the exception returned from the service if it is a PRECONDITION_FAILED response. This is performed since
687 * add setting returns PRECONDITION_FAILED when the configuration already exists, all other uses of setKey return
688 * this status when the configuration doesn't exist.
689 *
690 * @param throwable Error response from the service.
691 *
692 * @return Exception remapped to a ResourceModifiedException if the throwable was a ResourceNotFoundException,
693 * otherwise the throwable is returned unmodified.
694 */
695 private static Throwable addSettingExceptionMapper(Throwable throwable) {
696 if (!(throwable instanceof ResourceNotFoundException)) {
697 return throwable;
698 }
699
700 ResourceNotFoundExceptioneption/ResourceNotFoundException.html#ResourceNotFoundException">ResourceNotFoundException notFoundException = (ResourceNotFoundException) throwable;
701 return new ResourceModifiedException(notFoundException.getMessage(), notFoundException.response());
702 }
703 }