content: package ru.andreymarkelov.atlas.plugins.prombitbucketexporter.manager;
  
  import com.atlassian.bitbucket.permission.Permission;
  import com.atlassian.bitbucket.project.ProjectService;
  import com.atlassian.bitbucket.pull.PullRequest;
  import com.atlassian.bitbucket.pull.PullRequestSearchRequest;
  import com.atlassian.bitbucket.pull.PullRequestService;
  import com.atlassian.bitbucket.repository.Repository;
  import com.atlassian.bitbucket.repository.RepositoryService;
  import com.atlassian.bitbucket.user.SecurityService;
  import com.atlassian.bitbucket.util.Operation;
  import com.atlassian.bitbucket.util.Page;
  import com.atlassian.bitbucket.util.PageRequest;
  import com.atlassian.bitbucket.util.PageRequestImpl;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.DisposableBean;
  import org.springframework.beans.factory.InitializingBean;
  
  import javax.annotation.Nonnull;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.ScheduledFuture;
  import java.util.concurrent.ThreadFactory;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.concurrent.atomic.AtomicLong;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  
  import static com.atlassian.bitbucket.pull.PullRequestState.OPEN;
  import static java.lang.Thread.MIN_PRIORITY;
  import static java.util.concurrent.Executors.defaultThreadFactory;
  import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
  
  public class ScheduledMetricEvaluatorImpl implements ScheduledMetricEvaluator, DisposableBean, InitializingBean {
      private static final Logger log = LoggerFactory.getLogger(ScheduledMetricEvaluator.class);
  
      /**
       * Bitbucket components.
       */
      private final ScrapingSettingsManager scrapingSettingsManager;
      private final RepositoryService repositoryService;
      private final ProjectService projectService;
      private final PullRequestService pullRequestService;
      private final SecurityService securityService;
  
      /**
       * Scheduled executor to grab metrics.
       */
      private final ScheduledExecutorService executorService;
      private final AtomicLong lastExecutionTimestamp;
      private final Lock lock;
  
      private final AtomicInteger totalProjects;
      private final AtomicInteger totalRepositories;
      private final AtomicLong totalPullRequests;
  
      private ScheduledFuture<?> scraper;
  
      public ScheduledMetricEvaluatorImpl(
              ScrapingSettingsManager scrapingSettingsManager,
              RepositoryService repositoryService,
              ProjectService projectService,
              PullRequestService pullRequestService,
              SecurityService securityService) {
          this.scrapingSettingsManager = scrapingSettingsManager;
          this.repositoryService = repositoryService;
          this.projectService = projectService;
          this.pullRequestService = pullRequestService;
          this.securityService = securityService;
          this.executorService = newSingleThreadScheduledExecutor(new ThreadFactory() {
              @Override
              public Thread newThread(@Nonnull Runnable r) {
                  Thread thread = defaultThreadFactory().newThread(r);
                  thread.setPriority(MIN_PRIORITY);
                  return thread;
              }
          });
          this.lastExecutionTimestamp = new AtomicLong(-1);
          this.totalProjects = new AtomicInteger(0);
          this.totalRepositories = new AtomicInteger(0);
          this.totalPullRequests = new AtomicLong(0);
          this.lock = new ReentrantLock();
      }
  
      @Override
      public long getLastExecutionTimestamp() {
          return lastExecutionTimestamp.get();
      }
  
      @Override
      public void restartScraping(int newDelay) {
          lock.lock();
          try{
              stopScraping();
              startScraping(newDelay);
          } finally {
              lock.unlock();
          }
      }
  
      @Override
      public void afterPropertiesSet() {
          lock.lock();
          try {
              startScraping(scrapingSettingsManager.getDelay());
          } finally {
              lock.unlock();
          }
      }
  
      @Override
      public void destroy() {
          executorService.shutdown();
          try {
              if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
                  executorService.shutdownNow();
              }
          } catch (InterruptedException e) {
              executorService.shutdownNow();
          }
      }
  
      @Override
      public long getTotalProjects() {
          return totalProjects.get();
      }
  
      @Override
      public long getTotalRepositories() {
          return totalRepositories.get();
      }
  
      @Override
      public long getTotalPullRequests() {
          return totalPullRequests.get();
      }
  
      private void startScraping(int delay) {
          scraper = executorService.scheduleWithFixedDelay(new Runnable() {
              @Override
              public void run() {
                  calculateTotalProjects();
                  calculateTotalRepositories();
                  calculateTotalPullRequests();
                  lastExecutionTimestamp.set(System.currentTimeMillis());
              }
          }, 0, delay, TimeUnit.MINUTES);
      }
  
      private void calculateTotalProjects() {
          try {
              securityService.withPermission(Permission.ADMIN, "Read all projects").call(new Operation<Object, Throwable>() {
                  @Override
                  public Object perform() {
                      totalProjects.set(projectService.findAllKeys().size());
                      return null;
                  }
              });
          } catch (Throwable th) {
              log.error("Cannot read all projects", th);
          }
      }
  
      private void calculateTotalRepositories() {
          try {
              securityService.withPermission(Permission.ADMIN, "Read all repositories").call(new Operation<Object, Throwable>() {
                  @Override
                  public Object perform() {
                      int repositories = 0;
                      PageRequest nextPage = new PageRequestImpl(0, 10000);
                      do {
                          Page<Repository> repositoryPage = repositoryService.findAll(nextPage);
                          repositories += repositoryPage.getSize();
                          nextPage = repositoryPage.getNextPageRequest();
                      } while (nextPage != null);
                      totalRepositories.set(repositories);
                      return null;
                  }
              });
          } catch (Throwable th) {
              log.error("Cannot read all repositories", th);
          }
      }
  
      private void calculateTotalPullRequests() {
          try {
              securityService.withPermission(Permission.ADMIN, "Read all pull requests").call(new Operation<Object, Throwable>() {
                  @Override
                  public Object perform() {
                      int pullRequests = 0;
                      PageRequest nextPage = new PageRequestImpl(0, 10000);
                      do {
                          Page<PullRequest> pullRequestPage = pullRequestService.search(new PullRequestSearchRequest.Builder().state(OPEN).build(), nextPage);
                          pullRequests += pullRequestPage.getSize();
                          nextPage = pullRequestPage.getNextPageRequest();
                      } while (nextPage != null);
                      totalPullRequests.set(pullRequests);
                      return null;
                  }
              });
          } catch (Throwable th) {
              log.error("Cannot read all pull requests", th);
          }
      }
  
      private void stopScraping() {
          if (!scraper.cancel(true)) {
              log.debug("Unable to cancel scraping, typically because it has already completed.");
          }
      }
  }
  