package com.microsoft.bingads.reporting;
import com.microsoft.bingads.AsyncCallback;
import com.microsoft.bingads.internal.OperationStatusRetry;
import com.microsoft.bingads.internal.ParentCallback;
import com.microsoft.bingads.ServiceClient;
import com.microsoft.bingads.internal.ResultFuture;
import com.microsoft.bingads.internal.functionalinterfaces.Consumer;
import com.microsoft.bingads.internal.functionalinterfaces.TriConsumer;
import com.microsoft.bingads.internal.utilities.ThreadPool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Track the status of reporting operation.
*/
public class ReportingOperationTracker{
private static final int INITIAL_STATUS_CHECK_INTERVAL_IN_MS = 1000;
private static final int NUMBER_OF_INITIAL_STATUS_CHECKS = 5;
private int numberOfStatusChecks = 0;
private ScheduledExecutorService executorService;
private ReportingStatusProvider statusProvider;
private boolean stopTracking;
protected int lastProgressReported;
private ReportingOperationStatus currentStatus;
private final int statusCheckIntervalInMs;
private ResultFuture<ReportingOperationStatus> trackResultFuture;
private ServiceClient<IReportingService> serviceClient;
private OperationStatusRetry<ReportingOperationStatus, ReportingStatusProvider, IReportingService> operationStatusRetry;
private int numberOfStatusRetry = 3;
private final Runnable pollExecutorTask = new Runnable() {
@Override
public void run() {
pollOperationStatus();
}
};
public ReportingOperationTracker(
ReportingStatusProvider statusProvider,
ServiceClient<IReportingService> serviceClient,
int statusCheckIntervalInMs) {
this.statusCheckIntervalInMs = statusCheckIntervalInMs;
this.statusProvider = statusProvider;
this.serviceClient = serviceClient;
this.operationStatusRetry = new OperationStatusRetry<ReportingOperationStatus, ReportingStatusProvider, IReportingService>();
}
AtomicBoolean statusUpdateInProgress = new AtomicBoolean(false);
public void pollOperationStatus() {
try {
if (!statusUpdateInProgress.compareAndSet(false, true)) {
return;
}
if (trackingWasStopped()) {
return;
}
if (cancelPollingIfRequestedByUser()) {
return;
}
refreshStatus(new AsyncCallback<ReportingOperationStatus>() {
@Override
public void onCompleted(Future<ReportingOperationStatus> result) {
try {
result.get();
numberOfStatusChecks++;
completeTaskIfOperationIsComplete();
} catch (InterruptedException ex) {
stopTracking();
propagateExceptionToCallingThread(new CouldNotGetReportingDownloadStatusException(ex));
} catch (ExecutionException ex) {
stopTracking();
propagateExceptionToCallingThread(new CouldNotGetReportingDownloadStatusException(ex));
} finally {
statusUpdateInProgress.set(false);
if (!stopTracking) {
int interval = INITIAL_STATUS_CHECK_INTERVAL_IN_MS;
if (numberOfStatusChecks >= NUMBER_OF_INITIAL_STATUS_CHECKS) {
interval = statusCheckIntervalInMs;
}
executorService.schedule(pollExecutorTask, interval, TimeUnit.MILLISECONDS);
}
}
}
});
} catch (Throwable ex) {
stopTracking();
propagateExceptionToCallingThread(ex);
}
}
private boolean cancelPollingIfRequestedByUser() {
if (trackResultFuture.isCancelled()) {
stopTracking();
return true;
}
return false;
}
private void propagateExceptionToCallingThread(Throwable ex) {
trackResultFuture.setException(ex);
}
private void completeTaskIfOperationIsComplete() {
if (operationIsComplete()) {
stopTracking();
completeTaskWithResult();
}
}
private void completeTaskWithResult() {
trackResultFuture.setResult(currentStatus);
}
private boolean operationIsComplete() {
return statusProvider.isFinalStatus(currentStatus);
}
private void refreshStatus(AsyncCallback<ReportingOperationStatus> callback) {
final ResultFuture<ReportingOperationStatus> resultFuture = new ResultFuture<ReportingOperationStatus>(callback);
operationStatusRetry.executeWithRetry(
new TriConsumer<ReportingStatusProvider, ServiceClient<IReportingService>, AsyncCallback<ReportingOperationStatus>>() {
@Override
public void accept(ReportingStatusProvider statusProvider, ServiceClient<IReportingService> serviceClient,
AsyncCallback<ReportingOperationStatus> callback) {
statusProvider.getCurrentStatus(serviceClient, callback);
}
},
statusProvider,
serviceClient,
new Consumer<ReportingOperationStatus>() {
@Override
public void accept(ReportingOperationStatus status) {
currentStatus = status;
resultFuture.setResult(currentStatus);
}
},
new Consumer<Exception>() {
@Override
public void accept(Exception exception) {
resultFuture.setException(exception);
}
},
numberOfStatusRetry
);
}
private boolean trackingWasStopped() {
return this.stopTracking;
}
private void stopTracking() {
this.stopTracking = true;
this.executorService.shutdown();
}
public Future<ReportingOperationStatus> trackResultFileAsync(AsyncCallback<ReportingOperationStatus> callback) {
this.trackResultFuture = new ResultFuture<ReportingOperationStatus>(callback);
this.startTracking();
return trackResultFuture;
}
private void startTracking() {
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.schedule(pollExecutorTask, INITIAL_STATUS_CHECK_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
}
}