All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.security.keyvault.certificates.Poller Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.security.keyvault.certificates;

import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.polling.PollResponse;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * This class offers API that simplifies the task of executing long-running operations against Azure service.
 * The {@link Poller} consist of poll operation, cancel operation if supported by Azure service and polling interval.
 * 

* It provides the following functionality: * *

    *
  • Querying the current state of long-running operations.
  • *
  • Requesting an asynchronous notification for long-running operation's state.
  • *
  • Cancelling the long-running operation if cancellation is supported by the service.
  • *
  • Triggering a poll operation manually.
  • *
  • Enable/Disable auto-polling.
  • *
* *

Auto Polling

* Auto-polling is enabled by-default. It means that the {@link Poller} starts polling as soon as its instance is created. The {@link Poller} will transparently call the poll operation every polling cycle * and track the state of the long-running operation. Azure services can return {@link PollResponse#getRetryAfter()} to override the {@code Poller.pollInterval} defined in the {@link Poller}. * The {@link Poller#getStatus()} represents the status returned by the successful long-running operation at the time the last auto-polling or last manual polling, whichever happened most recently. * *

Disable Auto Polling

* For those scenarios which require manual control of the polling cycle, disable auto-poling by calling {@code setAutoPollingEnabled#false} and perform manual poll * by invoking {@link Poller#poll()} function. It will call poll operation once and update the {@link Poller} with the latest status. *

When auto-polling is disabled, the {@link Poller} will not update its status or other information, unless manual polling is triggered by calling {@link Poller#poll()} function. * *

The {@link Poller} will stop polling when the long-running operation is complete or it is disabled. The polling is considered complete * based on status defined in {@link PollResponse}. * * @param Type of poll response value * @see PollResponse */ public class Poller { private final ClientLogger logger = new ClientLogger(Poller.class); /* * poll operation is a function that takes the previous PollResponse, and * returns a new Mono of PollResponse to represent the current state */ private final Function, Mono>> pollOperation; /* * poll interval before next auto poll. This value will be used if the PollResponse does not include retryAfter from the service. */ private final Duration pollInterval; /* * This will save last poll response. */ private PollResponse pollResponse; /* * This will be called when cancel operation is triggered. */ private final Consumer> cancelOperation; /* * Indicate to poll automatically or not when poller is created. * default value is false; */ private boolean autoPollingEnabled; /* * This handle to Flux allow us to perform polling operation in asynchronous manner. * This could be shared among many subscriber. One of the subscriber will be this poller itself. * Once subscribed, this Flux will continue to poll for status until poll operation is done/complete. */ private final Flux> fluxHandle; /* * Since constructor create a subscriber and start auto polling. * This handle will be used to dispose the subscriber when * client disable auto polling. */ private Disposable fluxDisposable; private final Supplier> fetchResultOperation; /** * Create a {@link Poller} instance with poll interval and poll operation. The polling starts immediately by invoking {@code pollOperation}. * The next poll cycle will be defined by {@code retryAfter} value in {@link PollResponse}. * In absence of {@code retryAfter}, the {@link Poller} will use {@code pollInterval}. * * @param pollInterval Not-null and greater than zero poll interval. * @param pollOperation The polling operation to be called by the {@link Poller} instance. This is a callback into the client library, * which must never return {@code null}, and which must always have a non-null status. * {@link Mono} returned from poll operation should never return {@link Mono#error(Throwable)}.If any unexpected scenario happens in poll operation, * it should be handled by client library and return a valid {@link PollResponse}. However if poll operation returns {@link Mono#error(Throwable)}, * the {@link Poller} will disregard that and continue to poll. * @throws IllegalArgumentException if {@code pollInterval} is less than or equal to zero and if {@code pollInterval} or {@code pollOperation} are {@code null} */ public Poller(Duration pollInterval, Function, Mono>> pollOperation) { this(pollInterval, pollOperation, null, null, null); } /** * Create a {@link Poller} instance with poll interval and poll operation. The polling starts immediately by invoking {@code pollOperation}. * The next poll cycle will be defined by {@code retryAfter} value in {@link PollResponse}. * In absence of {@code retryAfter}, the {@link Poller} will use {@code pollInterval}. * * @param pollInterval Not-null and greater than zero poll interval. * @param pollOperation The polling operation to be called by the {@link Poller} instance. This is a callback into the client library, * which must never return {@code null}, and which must always have a non-null status. * {@link Mono} returned from poll operation should never return {@link Mono#error(Throwable)}.If any unexpected scenario happens in poll operation, * it should be handled by client library and return a valid {@link PollResponse}. However if poll operation returns {@link Mono#error(Throwable)}, * the {@link Poller} will disregard that and continue to poll. * @param activationOperation the operation to be called before polling begins. It can be {@code null} which will indicate to the {@link Poller} * that polling can begin straight away. * @param fetchResultOperation the operation to be called to fetch final result after polling has been completed. * @param cancelOperation cancel operation if cancellation is supported by the service. It can be {@code null} which will indicate to the {@link Poller} * that cancel operation is not supported by Azure service. * @throws IllegalArgumentException if {@code pollInterval} is less than or equal to zero and if {@code pollInterval} or {@code pollOperation} are {@code null} */ public Poller(Duration pollInterval, Function, Mono>> pollOperation, Supplier> activationOperation, Supplier> fetchResultOperation, Consumer> cancelOperation) { if (pollInterval == null || pollInterval.toNanos() <= 0) { logger.logExceptionAsError(new IllegalArgumentException("Null, negative or zero value for poll interval is not allowed.")); } if (pollOperation == null) { logger.logExceptionAsError(new IllegalArgumentException("Null value for poll operation is not allowed.")); } this.pollInterval = pollInterval; this.pollOperation = pollOperation; this.pollResponse = new PollResponse<>(PollResponse.OperationStatus.NOT_STARTED, null); this.fetchResultOperation = fetchResultOperation; this.fluxHandle = asyncPollRequestWithDelay() .flux() .repeat() .takeUntil(pollResponse -> hasCompleted()) .share() .delaySubscription(activationOperation != null ? activationOperation.get() : Mono.empty()); // auto polling start here this.fluxDisposable = fluxHandle.subscribe(); this.autoPollingEnabled = true; this.cancelOperation = cancelOperation; } /** * Create a {@link Poller} instance with poll interval, poll operation and cancel operation. The polling starts immediately by invoking {@code pollOperation}. * The next poll cycle will be defined by retryAfter value in {@link PollResponse}. * In absence of {@link PollResponse#getRetryAfter()}, the {@link Poller} will use {@code pollInterval}. * * @param pollInterval Not-null and greater than zero poll interval. * @param pollOperation The polling operation to be called by the {@link Poller} instance. This is a callback into the client library, * which must never return {@code null}, and which must always have a non-null status. *{@link Mono} returned from poll operation should never return {@link Mono#error(Throwable)}.If any unexpected scenario happens in poll operation, * it should handle it and return a valid {@link PollResponse}. However if poll operation returns {@link Mono#error(Throwable)}, * the {@link Poller} will disregard that and continue to poll. * @param fetchResultOperation the operation to be called to fetch final result after polling has been completed. * @param cancelOperation cancel operation if cancellation is supported by the service. It can be {@code null} which will indicate to the {@link Poller} * that cancel operation is not supported by Azure service. * @throws IllegalArgumentException if {@code pollInterval} is less than or equal to zero and if {@code pollInterval} or {@code pollOperation} are {@code null} */ public Poller(Duration pollInterval, Function, Mono>> pollOperation, Supplier> fetchResultOperation, Consumer> cancelOperation) { this(pollInterval, pollOperation, null, fetchResultOperation, cancelOperation); } /** * Create a {@link Poller} instance with poll interval, poll operation and cancel operation. The polling starts immediately by invoking {@code pollOperation}. * The next poll cycle will be defined by retryAfter value in {@link PollResponse}. * In absence of {@link PollResponse#getRetryAfter()}, the {@link Poller} will use {@code pollInterval}. * * @param pollInterval Not-null and greater than zero poll interval. * @param pollOperation The polling operation to be called by the {@link Poller} instance. This is a callback into the client library, * which must never return {@code null}, and which must always have a non-null status. * {@link Mono} returned from poll operation should never return {@link Mono#error(Throwable)}.If any unexpected scenario happens in poll operation, * it should handle it and return a valid {@link PollResponse}. However if poll operation returns {@link Mono#error(Throwable)}, * the {@link Poller} will disregard that and continue to poll. * @param fetchResultOperation the operation to be called to fetch final result after polling has been completed. * @param activationOperation the operation to be called before polling begins. It can be {@code null} which will indicate to the {@link Poller} * that polling can begin straight away. * @throws IllegalArgumentException if {@code pollInterval} is less than or equal to zero and if {@code pollInterval} or {@code pollOperation} are {@code null} */ public Poller(Duration pollInterval, Function, Mono>> pollOperation, Supplier> fetchResultOperation, Supplier> activationOperation) { this(pollInterval, pollOperation, activationOperation, fetchResultOperation, null); } /** * Attempts to cancel the long-running operation that this {@link Poller} represents. This is possible only if the service supports it, * otherwise an {@code UnsupportedOperationException} will be thrown. *

* It will call cancelOperation if status is 'In Progress' otherwise it does nothing. * * @throws UnsupportedOperationException when cancel operation is not provided. */ public void cancelOperation() throws UnsupportedOperationException { if (this.cancelOperation == null) { logger.logExceptionAsError(new UnsupportedOperationException("Cancel operation is not supported on this service/resource.")); } // We can not cancel an operation if it was never started // It only make sense to call cancel operation if current status IN_PROGRESS. if (this.pollResponse != null && this.pollResponse.getStatus() != PollResponse.OperationStatus.IN_PROGRESS) { return; } //Time to call cancel this.cancelOperation.accept(this); } /** * This method returns a {@link Flux} that can be subscribed to, enabling a subscriber to receive notification of * every {@link PollResponse}, as it is received. * * @return A {@link Flux} that can be subscribed to receive poll responses as the long-running operation executes. */ public Flux> getObserver() { return this.fluxHandle; } /** * Enable user to take control of polling and trigger manual poll operation. It will call poll operation once. * This will not turn off auto polling. * * @return a Mono of {@link PollResponse} This will call poll operation once. The {@link Mono} returned here could be subscribed * for receiving {@link PollResponse} in async manner. */ public Mono> poll() { return this.pollOperation.apply(this.pollResponse) .doOnEach(pollResponseSignal -> { if (pollResponseSignal.get() != null) { this.pollResponse = pollResponseSignal.get(); } }); } /** * Blocks execution and wait for polling to complete. The polling is considered complete based on status defined in {@link PollResponse}. *

It will enable auto-polling if it was disable by user. * * @return returns final {@link PollResponse} when polling is complete. */ public R block() { if (!isAutoPollingEnabled()) { setAutoPollingEnabled(true); } this.fluxHandle.blockLast(); return result().block(); } public Mono result() { if (!getStatus().equals(PollResponse.OperationStatus.SUCCESSFULLY_COMPLETED)) { return Mono.error(new IllegalAccessException("The poll operation has not successfully completed.")); } return fetchResultOperation.get(); } /** * Blocks execution and wait for polling to complete. The polling is considered complete based on status defined in {@link PollResponse}. *

It will enable auto-polling if it was disable by user. * * @param timeout the duration for which the excecution is blocked and waits for polling to complete. * @return returns final {@link PollResponse} when polling is complete. */ public R block(Duration timeout) { if (!isAutoPollingEnabled()) { setAutoPollingEnabled(true); } this.fluxHandle.blockLast(timeout); return result().block(); } /** * Blocks indefinitely until given {@code statusToBlockFor} is received. * @param statusToBlockFor The desired status to block for. * @return {@link PollResponse} for matching desired status. * @throws IllegalArgumentException If {@code statusToBlockFor} is {@code null}. */ public PollResponse blockUntil(PollResponse.OperationStatus statusToBlockFor) { return blockUntil(statusToBlockFor, null); } /** * Blocks until given {@code statusToBlockFor} is received or a timeout expires if provided. A {@code null} {@code timeout} will cause to block indefinitely for desired status. * @param statusToBlockFor The desired status to block for. * @param timeout The time after which it will stop blocking. A {@code null} value will cause to block indefinitely. Zero or negative are not valid values. * @return {@link PollResponse} for matching desired status to block for. * @throws IllegalArgumentException if {@code timeout} is zero or negative and if {@code statusToBlockFor} is {@code null}. */ public PollResponse blockUntil(PollResponse.OperationStatus statusToBlockFor, Duration timeout) { if (statusToBlockFor == null) { logger.logExceptionAsError(new IllegalArgumentException("Null value for status is not allowed.")); } if (timeout != null && timeout.toNanos() <= 0) { logger.logExceptionAsError(new IllegalArgumentException("Negative or zero value for timeout is not allowed.")); } if (!isAutoPollingEnabled()) { setAutoPollingEnabled(true); } if (timeout != null) { return this.fluxHandle.filter(tPollResponse -> matchStatus(tPollResponse, statusToBlockFor)).blockFirst(timeout); } else { return this.fluxHandle.filter(tPollResponse -> matchStatus(tPollResponse, statusToBlockFor)).blockFirst(); } } /* * Indicate that the @{link PollResponse} matches with the status to block for. * @param currentPollResponse The poll response which we have received from the flux. * @param statusToBlockFor The {@link OperationStatus} to block and it can be any valid {@link OperationStatus} value. * @return True if the {@link PollResponse} return status matches the status to block for. */ private boolean matchStatus(PollResponse currentPollResponse, PollResponse.OperationStatus statusToBlockFor) { // perform validation if (currentPollResponse == null || statusToBlockFor == null) { return false; } if (statusToBlockFor == currentPollResponse.getStatus()) { return true; } return false; } /* * This function will apply delay and call poll operation function async. * We expect Mono from pollOperation should never return Mono.error() . If any unexpected * scenario happens in pollOperation, it should catch it and return a valid PollResponse. * This is because poller does not know what to do in case on Mono.error. * This function will return empty mono in case of Mono.error() returned by poll operation. * * @return mono of poll response */ private Mono> asyncPollRequestWithDelay() { return Mono.defer(() -> this.pollOperation.apply(this.pollResponse) .delaySubscription(getCurrentDelay()) .onErrorResume(throwable -> { // We should never get here and since we want to continue polling //Log the error return Mono.empty(); }) .doOnEach(pollResponseSignal -> { if (pollResponseSignal.get() != null) { this.pollResponse = pollResponseSignal.get(); } })); } /* * We will use {@link PollResponse#getRetryAfter} if it is greater than zero otherwise use poll interval. */ private Duration getCurrentDelay() { return (this.pollResponse != null && this.pollResponse.getRetryAfter() != null && this.pollResponse.getRetryAfter().toNanos() > 0) ? this.pollResponse.getRetryAfter() : this.pollInterval; } /** * Controls whether auto-polling is enabled or disabled. Refer to the {@link Poller} class-level JavaDoc for more details on auto-polling. * * @param autoPollingEnabled If true, auto-polling will occur transparently in the background, otherwise it requires * manual polling by the user to get the latest state. */ public final void setAutoPollingEnabled(boolean autoPollingEnabled) { this.autoPollingEnabled = autoPollingEnabled; if (this.autoPollingEnabled) { if (!activeSubscriber()) { this.fluxDisposable = this.fluxHandle.subscribe(pr -> this.pollResponse = pr); } } else { if (activeSubscriber()) { this.fluxDisposable.dispose(); } } } /* * An operation will be considered complete if it is in one of the following state: *

    *
  • SUCCESSFULLY_COMPLETED
  • *
  • USER_CANCELLED
  • *
  • FAILED
  • *
* Also see {@link OperationStatus} * @return true if operation is done/complete. */ private boolean hasCompleted() { return pollResponse != null && (pollResponse.getStatus() == PollResponse.OperationStatus.SUCCESSFULLY_COMPLETED || pollResponse.getStatus() == PollResponse.OperationStatus.FAILED || pollResponse.getStatus() == PollResponse.OperationStatus.USER_CANCELLED); } /* * Determine if this poller's internal subscriber exists and active. */ private boolean activeSubscriber() { return (this.fluxDisposable != null && !this.fluxDisposable.isDisposed()); } /** * Indicates if auto polling is enabled. Refer to the {@link Poller} class-level JavaDoc for more details on auto-polling. * @return A boolean value representing if auto-polling is enabled or not.. */ public boolean isAutoPollingEnabled() { return this.autoPollingEnabled; } /** * Current known status as a result of last poll event or last response from a manual polling. * * @return current status or {@code null} if no status is available. */ public PollResponse.OperationStatus getStatus() { return this.pollResponse != null ? this.pollResponse.getStatus() : null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy