public ItemHolder getPrefilteredItems()

in rest-api/src/jetbrains/buildServer/server/rest/data/finder/impl/BuildPromotionFinder.java [1121:1504]


  public ItemHolder<BuildPromotion> getPrefilteredItems(@NotNull Locator locator) {

    if (!myPermissionChecker.hasPermissionInAnyProject(Permission.VIEW_PROJECT)){
      return ItemHolder.of(Collections.emptyList()); //TeamCity issue: should be handled in core. Do not spend any resources on user without permissions
    }

    final Boolean byPromotion = locator.getSingleDimensionValueAsBoolean(BY_PROMOTION);
    if (byPromotion != null && !byPromotion) {
      throw new BadRequestException("Found '" + BY_PROMOTION + "' locator set to 'false' which is not supported");
    }

    final String strob = locator.getSingleDimensionValue(STROB);
    if (strob != null) {
      final Locator strobLocator = new Locator(strob, BUILD_TYPE, BRANCH, STROB_BUILD_LOCATOR);

      List<Locator> partialLocators = Collections.singletonList(Locator.createEmptyLocator());
      partialLocators = getPartialLocatorsForStrobDimension(partialLocators, strobLocator, BUILD_TYPE, myBuildTypeFinder);
      partialLocators = getPartialLocatorsForStrobDimension(partialLocators, strobLocator, BRANCH, myBranchFinder);

      final String strobBuildLocator = strobLocator.getSingleDimensionValue(STROB_BUILD_LOCATOR);

      final List<ItemHolder<BuildPromotion>> strobResult = new ArrayList<>();
      for (Locator partialLocator : partialLocators) {
        partialLocator.setDimensionIfNotPresent(PagerData.COUNT, "1");  //limit to single item per strob item by default
        final String finalBuildLocator = Locator.createLocator(strobBuildLocator, partialLocator, new String[]{}).getStringRepresentation();
        strobResult.add(ItemHolder.of(getItems(finalBuildLocator).getEntries()));
      }
      strobLocator.checkLocatorFullyProcessed();
      return ItemHolder.concat(strobResult);
    }

    setLocatorDefaults(locator);

    final String equivalent = locator.getSingleDimensionValue(EQUIVALENT);
    if (equivalent != null) {
      final BuildPromotionEx build = (BuildPromotionEx)getItem(equivalent);
      return ItemHolder.of(sortPromotions(build.getStartedEquivalentPromotions(-1).stream()));
    }

    final String metadata = locator.getSingleDimensionValue(METADATA);
    if (metadata != null) {
      Iterable<BuildMetadataEntry> metadataEntries = () -> getBuildMetadataEntryIterator(metadata);
      return processor -> {
        for (BuildMetadataEntry metadataEntry : metadataEntries) {
          SBuild build = myBuildsManager.findBuildInstanceById(metadataEntry.getBuildId());
          if (build != null && Build.canViewRuntimeData(myPermissionChecker, build.getBuildPromotion())) {
            processor.processItem(build.getBuildPromotion());
          }
        }
      };
    }

    final String graphLocator = locator.getSingleDimensionValue(ORDERED);
    if (graphLocator != null) {
      final GraphFinder<BuildPromotion> graphFinder = new BuildPromotionOrderedFinder(this);
      //consider performance optimization by converting id to build only on actual retrieve (use GraphFinder<Int/buildId>)
      return ItemHolder.of(graphFinder.getItems(graphLocator).getEntries());
    }

    final String snapshotDepDimension = locator.getSingleDimensionValue(SNAPSHOT_DEP);
    if (snapshotDepDimension != null) {
      return ItemHolder.of(getSnapshotRelatedBuilds(snapshotDepDimension));
    }

    final String artifactDepDimension = locator.getSingleDimensionValue(ARTIFACT_DEP);
    if (artifactDepDimension != null) {
      return ItemHolder.of(getArtifactRelatedBuilds(artifactDepDimension, locator));
    }

    final String snapshotDepProblem = locator.getSingleDimensionValue(SNAPSHOT_PROBLEM);
    if (snapshotDepProblem != null) {
      return ItemHolder.of(getSnapshotDepProblemBuilds(snapshotDepProblem));
    }

    if(locator.isUnused(NUMBER) && locator.isUnused(BUILD_TYPE)) {
      final String buildNumber = getBuildNumberIfEqualsCondition(locator);
      if(buildNumber != null) {
        final String buildTypeLocator = locator.getSingleDimensionValue(BUILD_TYPE);

        final List<SBuildType> buildTypes = myBuildTypeFinder.getBuildTypes(null, buildTypeLocator);
        Stream<BuildPromotion> promotions = buildTypes.stream()
                                                      .flatMap(bt -> myBuildsManager.findBuildInstancesByBuildNumber(bt.getBuildTypeId(), buildNumber).stream())
                                                      .map(SBuild::getBuildPromotion);

        return ItemHolder.of(sortPromotions(promotions));
      }
    }

    /*
      Search by build number and project id
     */
    if(TeamCityProperties.getBooleanOrTrue("rest.builds.selectByProjectAndBuildNumberOptimization.enabled")) {
      if (locator.isUnused(NUMBER) && locator.isUnused(PROJECT)) {
        final String buildNumber = getBuildNumberIfEqualsCondition(locator);
        if (buildNumber != null) {
          SProject project = getProjectFromDimension(locator, PROJECT);
          if (project != null) {
            List<SBuildType> buildTypes = project.getOwnBuildTypes();
            return getBuildsByBuildTypesAndBuildNumber(buildTypes, buildNumber);
          }
        }
      }
      if(locator.isUnused(NUMBER) && locator.isUnused(AFFECTED_PROJECT)) {
        final String buildNumber = getBuildNumberIfEqualsCondition(locator);
        if (buildNumber != null) {
          SProject project = getProjectFromDimension(locator, AFFECTED_PROJECT);
          if (project != null) {
            List<SBuildType> buildTypes = project.getBuildTypes();
            return getBuildsByBuildTypesAndBuildNumber(buildTypes, buildNumber);
          }
        }
      }
    }

    if (locator.isAnyPresent(TAG) && TeamCityProperties.getBooleanOrTrue("rest.request.builds.prefilterByTag")) {
      Locator stateLocator = getStateLocator(new Locator(locator)); //using locator copy so that no dimensions are marked as used
      if (isStateIncluded(stateLocator, STATE_FINISHED)) {//no sense in going further here if no finished builds are requested
        final List<String> tagLocators = locator.lookupDimensionValue(TAG); //not marking as used to enforce filter processing later

        Stream<BuildPromotion> finishedBuilds = TagFinder.getPrefilteredFinishedBuildPromotions(tagLocators, myServiceLocator);
        if (finishedBuilds != null) {
          FilterConditionChecker<BuildPromotion> tagsFilter = getFilterByTag(tagLocators);
          // After this point no other builds will be added
          locator.markUsed(TAG);

          Stream<BuildPromotion> queuedBuilds = Stream.empty();
          if(isStateIncluded(stateLocator, STATE_QUEUED)) {
             queuedBuilds = myBuildQueue.getItems().stream()
                                        .map(SQueuedBuild::getBuildPromotion);
          }

          Stream<BuildPromotion> runningBuilds = Stream.empty();
          if(isStateIncluded(stateLocator, STATE_RUNNING)) {
            // BuildsManager.processBuilds could be used here instead of getting finished and running builds separately, but BuildQueryOptions support
            // only exact tag name match, so we can't satisfy all possible TAG queriy conditions with it. To counter that we use
            // TagFinder.getPrefilteredFinishedBuildPromotions to obtain a subset of required finished builds and BuildsManager.getRunningBuilds()
            // to get all runing builds and later filter everything properly.
            runningBuilds = myBuildsManager.getRunningBuilds().stream()
                                           .map(SRunningBuild::getBuildPromotion);
          }

          // None of the concatenated streams were properly filtered, so let's filter them now.
          // This hopefully allows for avoiding of hitting a lookupLimit because of a large queue.
          Stream<BuildPromotion> allBuildsFilteredByTag = Stream.concat(Stream.concat(queuedBuilds, runningBuilds), finishedBuilds)
                                                                .filter(tagsFilter::isIncluded);

          return processor -> allBuildsFilteredByTag.forEach(processor::processItem);
        }
      }
    }

    final String testOccurrence = locator.getSingleDimensionValue(TEST_OCCURRENCE);
    if (testOccurrence != null) {
      TestOccurrenceFinder testOccurrenceFinder = myServiceLocator.getSingletonService(TestOccurrenceFinder.class);
      return ItemHolder.of(testOccurrenceFinder.getItems(testOccurrence).getEntries().stream().map(sTestRun -> sTestRun.getBuild().getBuildPromotion()));
    }

    SBuildAgent agent;
    final String agentLocator = locator.getSingleDimensionValue(AGENT);
    if (agentLocator != null) {
      List<SBuildAgent> agents = myAgentFinder.getItemsNotEmpty(agentLocator).getEntries();
      if (agents.size() == 1) {
        agent = agents.get(0);
      } else {  //only if builds processor cannot handle this
        Stream<BuildPromotion> result = Stream.empty();

        Locator stateLocator = getStateLocator(locator);
        if (isStateIncluded(stateLocator, STATE_QUEUED)) {
          //todo: should sort backwards as currently the order does not seem right...
          result =
            Stream.concat(result, myBuildQueue.getItems().stream().filter(build -> !CollectionsUtil.intersect(build.getCanRunOnAgents(), agents).isEmpty())
                                              .map(build -> build.getBuildPromotion()));
        }

        if (isStateIncluded(stateLocator, STATE_RUNNING)) {  //todo: address an issue when a build can appear twice in the output
          // agent instance can be different when disconnecting, so need to check id
          Set<Integer> agentIds = agents.stream().map(a -> a.getId()).collect(Collectors.toSet());
          Set<String> agentNames = agents.stream().map(a -> a.getName()).collect(Collectors.toSet());
          result = Stream.concat(result, myBuildsManager.getRunningBuilds().stream().filter(build -> {
            SBuildAgent buildAgent = build.getAgent();
            int agentId = buildAgent.getId();
            return agentId > 0 ? agentIds.contains(agentId) : agentNames.contains(buildAgent.getName());
          }).map(build -> build.getBuildPromotion()));
        }

        if (isStateIncluded(stateLocator, STATE_FINISHED)) {
          //todo: optimize for user and canceled
          Stream<BuildPromotion> finishedBuilds = sortPromotions(
            agents.stream()
                  .flatMap(a -> a.getBuildHistory(null, true).stream().map(b -> b.getBuildPromotion()))
          );


          result = Stream.concat(result, finishedBuilds);
        }
        return ItemHolder.of(result);
      }
    } else {
      agent = null;
    }

    // process by build states

    final List<BuildPromotion> result = new ArrayList<>();
    Set<Long> includedPromotionIds = new HashSet<>();
    @Nullable Set<SBuildType> buildTypes = getBuildTypes(locator);

    String agentName;
    String agentNameCondition = locator.lookupSingleDimensionValue(AGENT_NAME);
    if (agentNameCondition != null) {
      agentName = ParameterCondition.createValueCondition(agentNameCondition).getConstantValueIfSimpleEqualsCondition();
      if (agentName != null) locator.markUsed(AGENT_NAME);
    } else {
      agentName = null;
    }

    Long agentTypeId = locator.getSingleDimensionValueAsLong(AGENT_TYPE_ID);
    Locator stateLocator = getStateLocator(locator);

    IncludePersonalBuildsRuling personalBuildsRuling = computePersonalBuildsRuling(locator);

    if (isStateIncluded(stateLocator, STATE_QUEUED)) {
      //todo: should sort backwards as currently the order does not seem right...
      Stream<SQueuedBuild> builds = myBuildQueue.getItems().stream();
      if(!personalBuildsRuling.isIncludePersonal()) {
        builds = builds.filter(build -> !build.isPersonal());
      } else {
        if(personalBuildsRuling.getOwner() != null) {
          builds = builds.filter(build -> !build.isPersonal() || personalBuildsRuling.getOwner().equals(build.getBuildPromotion().getOwner()));
        }
      }

      if (buildTypes != null) { //make sure buildTypes retrieved from the locator are used
        builds = builds.filter(qb -> buildTypes.contains(qb.getBuildPromotion().getParentBuildType()));
      }
      if (agentTypeId != null) {
        builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.getAgentTypeId() == agentTypeId.intValue()));
      }
      if (agent != null) {
        builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.equals(agent))); //todo: check
      }
      if (agentName != null) {
        builds = builds.filter(build -> build.getCanRunOnAgents().stream().anyMatch(a -> a.getName().equals(agentName)));
      }

      builds.map(b -> b.getBuildPromotion()).forEach(p -> {
        if (includedPromotionIds.add(p.getId())) {
          result.add(p);
        }
      });
    }

    if (isStateIncluded(stateLocator, STATE_RUNNING)) {
      Stream<SRunningBuild> builds;
      if(personalBuildsRuling.isIncludePersonal()) {
        builds = myBuildsManager.getRunningBuilds(personalBuildsRuling.myOwner, null).stream();
      } else {
        builds = myBuildsManager.getRunningBuilds().stream()
                                .filter(build -> !build.isPersonal());
      }

      if (buildTypes != null) { //make sure buildTypes retrieved from the locator are used
        builds = builds.filter(b -> buildTypes.contains(b.getBuildPromotion().getParentBuildType()));
      }

      if (agentTypeId != null) {
        builds = builds.filter(build -> build.getAgent().getAgentTypeId() == agentTypeId.intValue());
      }
      if (agent != null) {
        builds = builds.filter(build -> {
          SBuildAgent buildAgent = build.getAgent();
          int agentId = buildAgent.getId();
          return agentId > 0 ? agent.getId() == agentId : agent.getName().equals(buildAgent.getName());
        });
      }
      if (agentName != null) {
        builds = builds.filter(build -> build.getAgentName().equals(agentName));
      }

      builds.map(b -> b.getBuildPromotion()).forEach(p -> {
        if (includedPromotionIds.add(p.getId())) {
          result.add(p);
        }
      });
    }

    ItemHolder<BuildPromotion> finishedBuilds = null;
    if (isStateIncluded(stateLocator, STATE_FINISHED)) {
      final BuildQueryOptions options = new BuildQueryOptions();
      if (buildTypes != null) {
        options.setBuildTypeIds(buildTypes.stream().map(bt -> bt.getBuildTypeId()).collect(Collectors.toList()));
      }

      if (agentTypeId != null) {
        options.setAgentTypeId(agentTypeId.intValue());
      }
      if (agent != null) {
        options.setAgent(agent);
      }
      if (agentName != null) {
        options.setAgentName(agentName);
      }

      final Boolean failedToStart = locator.lookupSingleDimensionValueAsBoolean(FAILED_TO_START);
      final Boolean canceled = locator.lookupSingleDimensionValueAsBoolean(CANCELED);
      if (canceled == null || canceled || failedToStart == null || failedToStart) {
        options.setIncludeCanceled(true); //also includes failed to start builds, TW-32060
      } else {
        options.setIncludeCanceled(false);
      }

      final Boolean pinned = locator.getSingleDimensionValueAsBoolean(PINNED);
      if (pinned != null) {
        options.setPinStatus(pinned);
      }

      TagFinder.FilterOptions tagFilterOptions = TagFinder.getFilterOptions(locator.lookupDimensionValue(TAG), myServiceLocator);
      if (tagFilterOptions != null) {
        options.setTagName(tagFilterOptions.getTagName(), tagFilterOptions.getTagOwner());
      }

      final String branchLocatorValue = locator.lookupSingleDimensionValue(BRANCH); // do not mark dimension as used as not all can be used from it
      if (branchLocatorValue != null) {
        BranchFinder.BranchFilterDetails branchFilterDetails;
        try {
          branchFilterDetails = myBranchFinder.getBranchFilterDetailsWithoutLocatorCheck(branchLocatorValue);
          // parsed OK, setting options
          if (branchFilterDetails.isAnyBranch()) {
            options.setMatchAllBranches(true);
          } else {
            if (branchFilterDetails.isDefaultBranchOrNotBranched()) {
              options.setMatchAllBranches(false);
              options.setBranch(Branch.DEFAULT_BRANCH_NAME);
            }
            if (branchFilterDetails.getBranchName() != null) {
              options.setMatchAllBranches(false); //causes a bug in certain cases, see https://youtrack.jetbrains.com/issue/TW-61530 however, no performant fix is possible so far
              options.setBranch(branchFilterDetails.getBranchName());
            }
          }
        } catch (LocatorProcessException e) {
          // not parsed, cannot extract name or default status
          options.setMatchAllBranches(true);
        }
      } else {
        options.setMatchAllBranches(true);
      }

      options.setIncludeRunning(false); //running builds are retrieved separately and appear before finished ones
      options.setOrderByChanges(false);

      // Do not mark COUNT dimension used as we can recieve more results than we need.
      Long count = locator.lookupSingleDimensionValueAsLong(PagerData.COUNT, getDefaultPageItemsCount());
      if (count != null && count != -1) {
        options.setPageSize(count.intValue());
      }

      // In a case when we need personal builds there is a twist, hence the following check.
      // BuildQueryOptions and underlayoing mechanics treats given user as if we are acting at his will.
      // This means that his it will check if the given user can see
      // a specific personal build according to his own permissions and user settings, which is not what we want.
      // Instead, we pretend that we need all personal builds and filter out unwanted ones later.
      final SUser currentUser = (SUser) myServiceLocator.getSingletonService(SecurityContext.class).getAuthorityHolder().getAssociatedUser();
      if(Objects.equals(personalBuildsRuling.myOwner, currentUser)) {
        options.setIncludePersonal(personalBuildsRuling.isIncludePersonal(), personalBuildsRuling.getOwner());
      } else {
        options.setIncludePersonal(personalBuildsRuling.isIncludePersonal(), null);
      }

      finishedBuilds = processor -> myBuildsManager.processBuilds(options, item -> {
        if (includedPromotionIds.contains(item.getBuildPromotion().getId())) return true; // ignore already added builds
        return processor.processItem(item.getBuildPromotion());
      });
    }

    stateLocator.checkLocatorFullyProcessed();

    final ItemHolder<BuildPromotion> finishedBuildsFinal = finishedBuilds;
    return processor -> {
      ItemHolder.of(result).process(processor);
      if (finishedBuildsFinal != null) {
        finishedBuildsFinal.process(processor);
      }
    };
  }