]> source.dussan.org Git - sonarqube.git/commitdiff
move UserSession out of sonar-server-common
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 20 Jul 2018 14:22:37 +0000 (16:22 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 24 Jul 2018 07:31:07 +0000 (09:31 +0200)
and as a consequence remove any need to have UserSession in Compute Engine

132 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java [deleted file]
server/sonar-ce/src/main/java/org/sonar/ce/user/package-info.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/user/CeUserSessionTest.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndex.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentQuery.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/es/EsModule.java
server/sonar-server-common/src/main/java/org/sonar/server/es/NewIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQueryFactory.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationScope.java
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexAuthorizationConstants.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexPermissions.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/NeedAuthorizationIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/user/BaseUserSession.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/user/UserSession.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/EsModuleTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndex.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexer.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/user/LightUserSessionRule.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexerTest.java
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentQuery.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/component/index/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/IssuesFinderSort.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQuery.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/index/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/permission/index/WebAuthorizationTypeSupport.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/permission/index/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileComparison.java
server/sonar-server/src/main/java/org/sonar/server/securityreport/ws/ShowAction.java
server/sonar-server/src/main/java/org/sonar/server/telemetry/TelemetryDataLoader.java
server/sonar-server/src/main/java/org/sonar/server/user/BaseUserSession.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssuesFinderSortTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndex.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexer.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/WebAuthorizationTypeSupportTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/securityreport/ws/ShowActionTest.java

index ecdf7f84cb846c81a828618848596fcc45daf33d..6a70c5d6b796a08cd6c18b368264661b90609a64 100644 (file)
@@ -62,7 +62,6 @@ import org.sonar.ce.task.projectanalysis.analysis.ProjectConfigurationFactory;
 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationModule;
 import org.sonar.ce.taskprocessor.CeProcessingScheduler;
 import org.sonar.ce.taskprocessor.CeTaskProcessorModule;
-import org.sonar.ce.user.CeUserSession;
 import org.sonar.core.component.DefaultResourceTypes;
 import org.sonar.core.config.CorePropertyDefinitions;
 import org.sonar.core.extension.CoreExtensionRepositoryImpl;
@@ -96,7 +95,6 @@ import org.sonar.server.extension.CoreExtensionStopper;
 import org.sonar.server.favorite.FavoriteUpdater;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.IssueStorage;
-import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
 import org.sonar.server.issue.notification.ChangesOnMyIssueNotificationDispatcher;
@@ -111,7 +109,6 @@ import org.sonar.server.issue.workflow.FunctionExecutor;
 import org.sonar.server.issue.workflow.IssueWorkflow;
 import org.sonar.server.l18n.ServerI18n;
 import org.sonar.server.log.ServerLogging;
-import org.sonar.server.measure.index.ProjectMeasuresIndex;
 import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.metric.CoreCustomMetrics;
 import org.sonar.server.metric.DefaultMetricFinder;
@@ -122,7 +119,6 @@ import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.organization.BillingValidationsProxyImpl;
 import org.sonar.server.organization.DefaultOrganizationProviderImpl;
 import org.sonar.server.organization.OrganizationFlagsImpl;
-import org.sonar.server.permission.index.PermissionIndexer;
 import org.sonar.server.platform.DefaultServerUpgradeStatus;
 import org.sonar.server.platform.OfficialDistribution;
 import org.sonar.server.platform.ServerFileSystemImpl;
@@ -292,9 +288,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       System2.INSTANCE,
       Clock.systemDefaultZone(),
 
-      // user session
-      CeUserSession.class,
-
       // DB
       DaoModule.class,
       ReadOnlyPropertiesDao.class,
@@ -307,9 +300,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       // rules/qprofiles
       RuleIndex.class,
 
-      // issues
-      IssueIndex.class,
-
       new OkHttpClientProvider(),
       computeEngineStatus,
 
@@ -389,7 +379,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       // measure
       CoreCustomMetrics.class,
       DefaultMetricFinder.class,
-      ProjectMeasuresIndex.class,
 
       UserIndexer.class,
       UserIndex.class,
@@ -410,7 +399,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       IssueStorage.class,
       IssueIndexer.class,
       IssueIteratorFactory.class,
-      PermissionIndexer.class,
       IssueFieldsSetter.class, // used in Web Services and CE's DebtCalculator
       FunctionExecutor.class, // used by IssueWorkflow
       IssueWorkflow.class, // used in Web Services and CE's DebtCalculator
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java b/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java
deleted file mode 100644 (file)
index 4dee6ad..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.user;
-
-import java.util.Collection;
-import java.util.List;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.db.user.GroupDto;
-import org.sonar.server.user.UserSession;
-
-/**
- * Implementation of {@link UserSession} which provide not implementation of any method.
- * <p>
- * Any use of {@link UserSession} in the Compute Engine will raise an error.
- * </p>
- */
-public class CeUserSession implements UserSession {
-
-  private static final String UOE_MESSAGE = "UserSession must not be used from within the Compute Engine";
-
-  @Override
-  public String getLogin() {
-    throw notImplemented();
-  }
-
-  @Override
-  public String getUuid() {
-    throw notImplemented();
-  }
-
-  @Override
-  public String getName() {
-    throw notImplemented();
-  }
-
-  @Override
-  public Integer getUserId() {
-    throw notImplemented();
-  }
-
-  @Override
-  public Collection<GroupDto> getGroups() {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean isLoggedIn() {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean isRoot() {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkIsRoot() {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkLoggedIn() {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean hasPermission(OrganizationPermission permission, String organizationUuid) {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkPermission(OrganizationPermission permission, String organizationUuid) {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization) {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkComponentPermission(String projectPermission, ComponentDto component) {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkComponentUuidPermission(String permission, String componentUuid) {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean isSystemAdministrator() {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkIsSystemAdministrator() {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean hasComponentPermission(String permission, ComponentDto component) {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean hasComponentUuidPermission(String permission, String componentUuid) {
-    throw notImplemented();
-  }
-
-  @Override
-  public List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    throw notImplemented();
-  }
-
-  @Override
-  public boolean hasMembership(OrganizationDto organization) {
-    throw notImplemented();
-  }
-
-  @Override
-  public UserSession checkMembership(OrganizationDto organization) {
-    throw notImplemented();
-  }
-
-  private static RuntimeException notImplemented() {
-    throw new UnsupportedOperationException(UOE_MESSAGE);
-  }
-}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/user/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/user/package-info.java
deleted file mode 100644 (file)
index 174e970..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.ce.user;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 9f1328486fa3ec1ac93264a76a9e1de81adaf1b9..ff8e310bc7f9ec1c821750ff4419078e3c9bb0c9 100644 (file)
@@ -97,7 +97,7 @@ public class ComputeEngineContainerImplTest {
       assertThat(picoContainer.getComponentAdapters())
         .hasSize(
           CONTAINER_ITSELF
-            + 72 // level 4
+            + 70 // level 4
             + 6 // content of CeConfigurationModule
             + 4 // content of CeQueueModule
             + 3 // content of CeHttpModule
@@ -120,7 +120,7 @@ public class ComputeEngineContainerImplTest {
       );
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
-          + 30 // level 1
+          + 27 // level 1
           + 55 // content of DaoModule
           + 3 // content of EsModule
           + 54 // content of CorePropertyDefinitions
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/user/CeUserSessionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/user/CeUserSessionTest.java
deleted file mode 100644 (file)
index 60935e4..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.user;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import static org.sonar.test.ExceptionCauseMatcher.hasType;
-
-@RunWith(DataProviderRunner.class)
-public class CeUserSessionTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private CeUserSession underTest = new CeUserSession();
-
-  @DataProvider
-  public static Object[][] ceUserSessionPublicMethods() {
-    List<Method> declaredMethods = Arrays.stream(CeUserSession.class.getDeclaredMethods())
-      .filter(m -> Modifier.isPublic(m.getModifiers()))
-      .collect(Collectors.toList());
-    Object[][] res = new Object[declaredMethods.size()][1];
-    int i = 0;
-    for (Method declaredMethod : declaredMethods) {
-      res[i][0] = declaredMethod;
-      i++;
-    }
-    return res;
-  }
-
-  @Test
-  @UseDataProvider("ceUserSessionPublicMethods")
-  public void all_methods_of_CeUserSession_throw_UOE(Method method) throws InvocationTargetException, IllegalAccessException {
-    int parametersCount = method.getParameterTypes().length;
-    switch (parametersCount) {
-      case 2:
-        expectUOE();
-        method.invoke(underTest, null, null);
-        break;
-      case 1:
-        expectUOE();
-        method.invoke(underTest, (Object) null);
-        break;
-      case 0:
-        expectUOE();
-        method.invoke(underTest);
-        break;
-      default:
-        throw new IllegalArgumentException("Unsupported number of parameters " + parametersCount);
-    }
-  }
-
-  private void expectUOE() {
-    expectedException.expect(InvocationTargetException.class);
-    expectedException.expectCause(
-      hasType(UnsupportedOperationException.class)
-        .andMessage("UserSession must not be used from within the Compute Engine"));
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndex.java
deleted file mode 100644 (file)
index e41f988..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.SearchHits;
-import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregator.KeyedFilter;
-import org.elasticsearch.search.aggregations.bucket.filters.InternalFilters;
-import org.elasticsearch.search.aggregations.bucket.filters.InternalFilters.InternalBucket;
-import org.elasticsearch.search.aggregations.metrics.tophits.InternalTopHits;
-import org.elasticsearch.search.aggregations.metrics.tophits.TopHitsAggregationBuilder;
-import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
-import org.elasticsearch.search.sort.FieldSortBuilder;
-import org.elasticsearch.search.sort.ScoreSortBuilder;
-import org.elasticsearch.search.sort.SortOrder;
-import org.sonar.api.utils.System2;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory;
-import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory.ComponentTextSearchQuery;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_LANGUAGE;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_ORGANIZATION_UUID;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_QUALIFIER;
-import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_TYPE_COMPONENT;
-import static org.sonar.server.component.index.ComponentIndexDefinition.NAME_ANALYZERS;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
-
-public class ComponentIndex {
-
-  private static final String FILTERS_AGGREGATION_NAME = "filters";
-  private static final String DOCS_AGGREGATION_NAME = "docs";
-
-  private final EsClient client;
-  private final AuthorizationTypeSupport authorizationTypeSupport;
-  private final System2 system2;
-
-  public ComponentIndex(EsClient client, AuthorizationTypeSupport authorizationTypeSupport, System2 system2) {
-    this.client = client;
-    this.authorizationTypeSupport = authorizationTypeSupport;
-    this.system2 = system2;
-  }
-
-  public SearchIdResult<String> search(ComponentQuery query, SearchOptions searchOptions) {
-    SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_COMPONENT)
-      .setFetchSource(false)
-      .setFrom(searchOptions.getOffset())
-      .setSize(searchOptions.getLimit());
-
-    BoolQueryBuilder esQuery = boolQuery();
-    esQuery.filter(authorizationTypeSupport.createQueryFilter());
-    setNullable(query.getQuery(), q -> {
-      ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQuery.builder()
-        .setQueryText(q)
-        .setFieldKey(FIELD_KEY)
-        .setFieldName(FIELD_NAME)
-        .build();
-      esQuery.must(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, ComponentTextSearchFeatureRepertoire.values()));
-    });
-    setEmptiable(query.getQualifiers(), q -> esQuery.must(termsQuery(FIELD_QUALIFIER, q)));
-    setNullable(query.getLanguage(), l -> esQuery.must(termQuery(FIELD_LANGUAGE, l)));
-    setNullable(query.getOrganizationUuid(), o -> esQuery.must(termQuery(FIELD_ORGANIZATION_UUID, o)));
-    requestBuilder.setQuery(esQuery);
-    requestBuilder.addSort(SORTABLE_ANALYZER.subField(FIELD_NAME), SortOrder.ASC);
-
-    return new SearchIdResult<>(requestBuilder.get(), id -> id, system2.getDefaultTimeZone());
-  }
-
-  public ComponentIndexResults searchSuggestions(SuggestionQuery query) {
-    return searchSuggestions(query, ComponentTextSearchFeatureRepertoire.values());
-  }
-
-  @VisibleForTesting
-  ComponentIndexResults searchSuggestions(SuggestionQuery query, ComponentTextSearchFeature... features) {
-    Collection<String> qualifiers = query.getQualifiers();
-    if (qualifiers.isEmpty()) {
-      return ComponentIndexResults.newBuilder().build();
-    }
-
-    SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_COMPONENT)
-      .setQuery(createQuery(query, features))
-      .addAggregation(createAggregation(query))
-
-      // the search hits are part of the aggregations
-      .setSize(0);
-
-    SearchResponse response = request.get();
-
-    return aggregationsToQualifiers(response);
-  }
-
-  private static HighlightBuilder.Field createHighlighterField() {
-    HighlightBuilder.Field field = new HighlightBuilder.Field(FIELD_NAME);
-    field.highlighterType("fvh");
-    field.matchedFields(
-      Stream.concat(
-        Stream.of(FIELD_NAME),
-        Arrays
-          .stream(NAME_ANALYZERS)
-          .map(a -> a.subField(FIELD_NAME)))
-        .toArray(String[]::new));
-    return field;
-  }
-
-  private static FiltersAggregationBuilder createAggregation(SuggestionQuery query) {
-    return AggregationBuilders.filters(
-      FILTERS_AGGREGATION_NAME,
-      query.getQualifiers().stream().map(q -> new KeyedFilter(q, termQuery(FIELD_QUALIFIER, q))).toArray(KeyedFilter[]::new))
-      .subAggregation(createSubAggregation(query));
-  }
-
-  private static TopHitsAggregationBuilder createSubAggregation(SuggestionQuery query) {
-    return AggregationBuilders.topHits(DOCS_AGGREGATION_NAME)
-      .highlighter(new HighlightBuilder()
-        .encoder("html")
-        .preTags("<mark>")
-        .postTags("</mark>")
-        .field(createHighlighterField()))
-      .from(query.getSkip())
-      .size(query.getLimit())
-      .sort(new ScoreSortBuilder())
-      .sort(new FieldSortBuilder(ComponentIndexDefinition.FIELD_NAME))
-      .fetchSource(false);
-  }
-
-  private QueryBuilder createQuery(SuggestionQuery query, ComponentTextSearchFeature... features) {
-    BoolQueryBuilder esQuery = boolQuery();
-    esQuery.filter(authorizationTypeSupport.createQueryFilter());
-    ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQuery.builder()
-      .setQueryText(query.getQuery())
-      .setFieldKey(FIELD_KEY)
-      .setFieldName(FIELD_NAME)
-      .setRecentlyBrowsedKeys(query.getRecentlyBrowsedKeys())
-      .setFavoriteKeys(query.getFavoriteKeys())
-      .build();
-    return esQuery.must(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, features));
-  }
-
-  private static ComponentIndexResults aggregationsToQualifiers(SearchResponse response) {
-    InternalFilters filtersAgg = response.getAggregations().get(FILTERS_AGGREGATION_NAME);
-    List<InternalBucket> buckets = filtersAgg.getBuckets();
-    return ComponentIndexResults.newBuilder()
-      .setQualifiers(
-        buckets.stream().map(ComponentIndex::bucketToQualifier))
-      .build();
-  }
-
-  private static ComponentHitsPerQualifier bucketToQualifier(InternalBucket bucket) {
-    InternalTopHits docs = bucket.getAggregations().get(DOCS_AGGREGATION_NAME);
-
-    SearchHits hitList = docs.getHits();
-    SearchHit[] hits = hitList.getHits();
-
-    return new ComponentHitsPerQualifier(bucket.getKey(), ComponentHit.fromSearchHits(hits), hitList.getTotalHits());
-  }
-
-  private static <T> void setNullable(@Nullable T parameter, Consumer<T> consumer) {
-    if (parameter != null) {
-      consumer.accept(parameter);
-    }
-  }
-
-  private static <T> void setEmptiable(Collection<T> parameter, Consumer<Collection<T>> consumer) {
-    if (!parameter.isEmpty()) {
-      consumer.accept(parameter);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java b/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java
deleted file mode 100644 (file)
index 9154f3b..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.util.Collections.emptyList;
-import static java.util.Objects.requireNonNull;
-
-public class ComponentIndexResults {
-
-  private final List<ComponentHitsPerQualifier> qualifiers;
-
-  private ComponentIndexResults(Builder builder) {
-    this.qualifiers = requireNonNull(builder.qualifiers);
-  }
-
-  public Stream<ComponentHitsPerQualifier> getQualifiers() {
-    return qualifiers.stream();
-  }
-
-  public boolean isEmpty() {
-    return qualifiers.isEmpty();
-  }
-
-  public static Builder newBuilder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-
-    private List<ComponentHitsPerQualifier> qualifiers = emptyList();
-
-    private Builder() {
-    }
-
-    public Builder setQualifiers(Stream<ComponentHitsPerQualifier> qualifiers) {
-      this.qualifiers = qualifiers.collect(Collectors.toList());
-      return this;
-    }
-
-    public ComponentIndexResults build() {
-      return new ComponentIndexResults(this);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentQuery.java b/server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentQuery.java
deleted file mode 100644 (file)
index 8093620..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Collection;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import static java.util.Collections.emptySet;
-import static java.util.Collections.unmodifiableCollection;
-
-public class ComponentQuery {
-  private final String organizationUuid;
-  private final String query;
-  private final Collection<String> qualifiers;
-  private final String language;
-
-  private ComponentQuery(Builder builder) {
-    this.organizationUuid = builder.organizationUuid;
-    this.query = builder.query;
-    this.qualifiers = builder.qualifiers;
-    this.language = builder.language;
-  }
-
-  @CheckForNull
-  public String getOrganizationUuid() {
-    return organizationUuid;
-  }
-
-  @CheckForNull
-  public String getQuery() {
-    return query;
-  }
-
-  public Collection<String> getQualifiers() {
-    return qualifiers;
-  }
-
-  @CheckForNull
-  public String getLanguage() {
-    return language;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private String organizationUuid;
-    private String query;
-    private Collection<String> qualifiers = emptySet();
-    private String language;
-
-    private Builder() {
-      // enforce static factory method
-    }
-
-    public Builder setOrganization(@Nullable String organizationUuid) {
-      this.organizationUuid = organizationUuid;
-      return this;
-    }
-
-    public Builder setQuery(@Nullable String query) {
-      this.query = query;
-      return this;
-    }
-
-    public Builder setQualifiers(Collection<String> qualifiers) {
-      this.qualifiers = unmodifiableCollection(qualifiers);
-      return this;
-    }
-
-    public Builder setLanguage(@Nullable String language) {
-      this.language = language;
-      return this;
-    }
-
-    public ComponentQuery build() {
-      return new ComponentQuery(this);
-    }
-  }
-}
index bb306dc55fcb5d9de879ee0bae8ff52bc7328060..a742daeef4c8bbb1db6d9caf5bcb3ea42125ffe2 100644 (file)
 package org.sonar.server.es;
 
 import org.sonar.core.platform.Module;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 
 public class EsModule extends Module {
   @Override
   protected void configureModule() {
-    add(AuthorizationTypeSupport.class);
     add(new EsClientProvider());
     add(EsClientStopper.class);
   }
index 9365bee84f0c6f2daa8a080c93f0cfd9ca87f3a7..f80b2311635c279be62c3806b691da3aa145fda7 100644 (file)
@@ -32,7 +32,6 @@ import org.apache.commons.lang.StringUtils;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.settings.Settings;
 import org.sonar.api.config.Configuration;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
@@ -53,6 +52,10 @@ import static org.sonar.server.es.DefaultIndexSettings.NORMS;
 import static org.sonar.server.es.DefaultIndexSettings.STORE;
 import static org.sonar.server.es.DefaultIndexSettings.TYPE;
 import static org.sonar.server.es.DefaultIndexSettingsElement.UUID_MODULE_ANALYZER;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_ALLOW_ANYONE;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_GROUP_IDS;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_USER_IDS;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 public class NewIndex {
 
@@ -181,10 +184,32 @@ public class NewIndex {
     }
 
     public NewIndexType requireProjectAuthorization() {
-      AuthorizationTypeSupport.enableProjectAuthorization(this);
+      enableProjectAuthorization(this);
       return this;
     }
 
+    /**
+     * Creates a type that requires to verify that user has the read permission
+     * when searching for documents.
+     *
+     * Both types {@code typeName} and "authorization" are created. Documents
+     * must be created with _parent and _routing having the parent uuid as values.
+     *
+     * @see NewIndex.NewIndexType#requireProjectAuthorization()
+     */
+    private static NewIndex.NewIndexType enableProjectAuthorization(NewIndex.NewIndexType type) {
+      type.setAttribute("_parent", ImmutableMap.of("type", TYPE_AUTHORIZATION));
+      type.setAttribute("_routing", ImmutableMap.of("required", true));
+
+      NewIndex.NewIndexType authType = type.getIndex().createType(TYPE_AUTHORIZATION);
+      authType.setAttribute("_routing", ImmutableMap.of("required", true));
+      authType.createLongField(FIELD_GROUP_IDS);
+      authType.createLongField(FIELD_USER_IDS);
+      authType.createBooleanField(FIELD_ALLOW_ANYONE);
+      authType.setEnableSource(false);
+      return type;
+    }
+
     public NewIndex getIndex() {
       return index;
     }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQuery.java
deleted file mode 100644 (file)
index 23e28bf..0000000
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.builder.ReflectionToStringBuilder;
-import org.sonar.db.rule.RuleDefinitionDto;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
-
-/**
- * @since 3.6
- */
-public class IssueQuery {
-
-  public static final String SORT_BY_CREATION_DATE = "CREATION_DATE";
-  public static final String SORT_BY_UPDATE_DATE = "UPDATE_DATE";
-  public static final String SORT_BY_CLOSE_DATE = "CLOSE_DATE";
-  /**
-   * @deprecated since 7.2, it's no more possible to sort by assignee
-   */
-  @Deprecated
-  public static final String SORT_BY_ASSIGNEE = "ASSIGNEE";
-  public static final String SORT_BY_SEVERITY = "SEVERITY";
-  public static final String SORT_BY_STATUS = "STATUS";
-
-  /**
-   * Sort by project, file path then line id
-   */
-  public static final String SORT_BY_FILE_LINE = "FILE_LINE";
-
-  public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY,
-    SORT_BY_STATUS, SORT_BY_FILE_LINE);
-
-  public static final String UNKNOWN_STANDARD = "unknown";
-  public static final String SANS_TOP_25_INSECURE_INTERACTION = "insecure-interaction";
-  public static final String SANS_TOP_25_RISKY_RESOURCE = "risky-resource";
-  public static final String SANS_TOP_25_POROUS_DEFENSES = "porous-defenses";
-
-  private final Collection<String> issueKeys;
-  private final Collection<String> severities;
-  private final Collection<String> statuses;
-  private final Collection<String> resolutions;
-  private final Collection<String> components;
-  private final Collection<String> modules;
-  private final Collection<String> moduleRoots;
-  private final Collection<String> projects;
-  private final Collection<String> directories;
-  private final Collection<String> files;
-  private final Collection<String> views;
-  private final Collection<RuleDefinitionDto> rules;
-  private final Collection<String> assignees;
-  private final Collection<String> authors;
-  private final Collection<String> languages;
-  private final Collection<String> tags;
-  private final Collection<String> types;
-  private final Collection<String> owaspTop10;
-  private final Collection<String> sansTop25;
-  private final Collection<String> cwe;
-  private final Map<String, PeriodStart> createdAfterByProjectUuids;
-  private final Boolean onComponentOnly;
-  private final Boolean assigned;
-  private final Boolean resolved;
-  private final Date createdAt;
-  private final PeriodStart createdAfter;
-  private final Date createdBefore;
-  private final String sort;
-  private final Boolean asc;
-  private final String facetMode;
-  private final String organizationUuid;
-  private final String branchUuid;
-  private final boolean mainBranch;
-  private final boolean checkAuthorization;
-
-  private IssueQuery(Builder builder) {
-    this.issueKeys = defaultCollection(builder.issueKeys);
-    this.severities = defaultCollection(builder.severities);
-    this.statuses = defaultCollection(builder.statuses);
-    this.resolutions = defaultCollection(builder.resolutions);
-    this.components = defaultCollection(builder.components);
-    this.modules = defaultCollection(builder.modules);
-    this.moduleRoots = defaultCollection(builder.moduleRoots);
-    this.projects = defaultCollection(builder.projects);
-    this.directories = defaultCollection(builder.directories);
-    this.files = defaultCollection(builder.files);
-    this.views = defaultCollection(builder.views);
-    this.rules = defaultCollection(builder.rules);
-    this.assignees = defaultCollection(builder.assigneeUuids);
-    this.authors = defaultCollection(builder.authors);
-    this.languages = defaultCollection(builder.languages);
-    this.tags = defaultCollection(builder.tags);
-    this.types = defaultCollection(builder.types);
-    this.owaspTop10 = defaultCollection(builder.owaspTop10);
-    this.sansTop25 = defaultCollection(builder.sansTop25);
-    this.cwe = defaultCollection(builder.cwe);
-    this.createdAfterByProjectUuids = defaultMap(builder.createdAfterByProjectUuids);
-    this.onComponentOnly = builder.onComponentOnly;
-    this.assigned = builder.assigned;
-    this.resolved = builder.resolved;
-    this.createdAt = builder.createdAt;
-    this.createdAfter = builder.createdAfter;
-    this.createdBefore = builder.createdBefore;
-    this.sort = builder.sort;
-    this.asc = builder.asc;
-    this.checkAuthorization = builder.checkAuthorization;
-    this.facetMode = builder.facetMode;
-    this.organizationUuid = builder.organizationUuid;
-    this.branchUuid = builder.branchUuid;
-    this.mainBranch = builder.mainBranch;
-  }
-
-  public Collection<String> issueKeys() {
-    return issueKeys;
-  }
-
-  public Collection<String> severities() {
-    return severities;
-  }
-
-  public Collection<String> statuses() {
-    return statuses;
-  }
-
-  public Collection<String> resolutions() {
-    return resolutions;
-  }
-
-  public Collection<String> componentUuids() {
-    return components;
-  }
-
-  public Collection<String> moduleUuids() {
-    return modules;
-  }
-
-  public Collection<String> moduleRootUuids() {
-    return moduleRoots;
-  }
-
-  public Collection<String> projectUuids() {
-    return projects;
-  }
-
-  public Collection<String> directories() {
-    return directories;
-  }
-
-  public Collection<String> fileUuids() {
-    return files;
-  }
-
-  public Collection<String> viewUuids() {
-    return views;
-  }
-
-  public Collection<RuleDefinitionDto> rules() {
-    return rules;
-  }
-
-  public Collection<String> assignees() {
-    return assignees;
-  }
-
-  public Collection<String> authors() {
-    return authors;
-  }
-
-  public Collection<String> languages() {
-    return languages;
-  }
-
-  public Collection<String> tags() {
-    return tags;
-  }
-
-  public Collection<String> types() {
-    return types;
-  }
-
-  public Collection<String> owaspTop10() {
-    return owaspTop10;
-  }
-
-  public Collection<String> sansTop25() {
-    return sansTop25;
-  }
-
-  public Collection<String> cwe() {
-    return cwe;
-  }
-
-  public Map<String, PeriodStart> createdAfterByProjectUuids() {
-    return createdAfterByProjectUuids;
-  }
-
-  @CheckForNull
-  public Boolean onComponentOnly() {
-    return onComponentOnly;
-  }
-
-  @CheckForNull
-  public Boolean assigned() {
-    return assigned;
-  }
-
-  @CheckForNull
-  public Boolean resolved() {
-    return resolved;
-  }
-
-  @CheckForNull
-  public PeriodStart createdAfter() {
-    return createdAfter;
-  }
-
-  @CheckForNull
-  public Date createdAt() {
-    return createdAt == null ? null : new Date(createdAt.getTime());
-  }
-
-  @CheckForNull
-  public Date createdBefore() {
-    return createdBefore == null ? null : new Date(createdBefore.getTime());
-  }
-
-  @CheckForNull
-  public String sort() {
-    return sort;
-  }
-
-  @CheckForNull
-  public Boolean asc() {
-    return asc;
-  }
-
-  public boolean checkAuthorization() {
-    return checkAuthorization;
-  }
-
-  @CheckForNull
-  public String organizationUuid() {
-    return organizationUuid;
-  }
-
-  @CheckForNull
-  public String branchUuid() {
-    return branchUuid;
-  }
-
-  public boolean isMainBranch() {
-    return mainBranch;
-  }
-
-  public String facetMode() {
-    return facetMode;
-  }
-
-  @Override
-  public String toString() {
-    return ReflectionToStringBuilder.toString(this);
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-    private Collection<String> issueKeys;
-    private Collection<String> severities;
-    private Collection<String> statuses;
-    private Collection<String> resolutions;
-    private Collection<String> components;
-    private Collection<String> modules;
-    private Collection<String> moduleRoots;
-    private Collection<String> projects;
-    private Collection<String> directories;
-    private Collection<String> files;
-    private Collection<String> views;
-    private Collection<RuleDefinitionDto> rules;
-    private Collection<String> assigneeUuids;
-    private Collection<String> authors;
-    private Collection<String> languages;
-    private Collection<String> tags;
-    private Collection<String> types;
-    private Collection<String> owaspTop10;
-    private Collection<String> sansTop25;
-    private Collection<String> cwe;
-    private Map<String, PeriodStart> createdAfterByProjectUuids;
-    private Boolean onComponentOnly = false;
-    private Boolean assigned = null;
-    private Boolean resolved = null;
-    private Date createdAt;
-    private PeriodStart createdAfter;
-    private Date createdBefore;
-    private String sort;
-    private Boolean asc = false;
-    private boolean checkAuthorization = true;
-    private String facetMode;
-    private String organizationUuid;
-    private String branchUuid;
-    private boolean mainBranch = true;
-
-    private Builder() {
-
-    }
-
-    public Builder issueKeys(@Nullable Collection<String> l) {
-      this.issueKeys = l;
-      return this;
-    }
-
-    public Builder severities(@Nullable Collection<String> l) {
-      this.severities = l;
-      return this;
-    }
-
-    public Builder statuses(@Nullable Collection<String> l) {
-      this.statuses = l;
-      return this;
-    }
-
-    public Builder resolutions(@Nullable Collection<String> l) {
-      this.resolutions = l;
-      return this;
-    }
-
-    public Builder componentUuids(@Nullable Collection<String> l) {
-      this.components = l;
-      return this;
-    }
-
-    public Builder moduleUuids(@Nullable Collection<String> l) {
-      this.modules = l;
-      return this;
-    }
-
-    public Builder moduleRootUuids(@Nullable Collection<String> l) {
-      this.moduleRoots = l;
-      return this;
-    }
-
-    public Builder projectUuids(@Nullable Collection<String> l) {
-      this.projects = l;
-      return this;
-    }
-
-    public Builder directories(@Nullable Collection<String> l) {
-      this.directories = l;
-      return this;
-    }
-
-    public Builder fileUuids(@Nullable Collection<String> l) {
-      this.files = l;
-      return this;
-    }
-
-    public Builder viewUuids(@Nullable Collection<String> l) {
-      this.views = l;
-      return this;
-    }
-
-    public Builder rules(@Nullable Collection<RuleDefinitionDto> rules) {
-      this.rules = rules;
-      return this;
-    }
-
-    public Builder assigneeUuids(@Nullable Collection<String> l) {
-      this.assigneeUuids = l;
-      return this;
-    }
-
-    public Builder authors(@Nullable Collection<String> l) {
-      this.authors = l;
-      return this;
-    }
-
-    public Builder languages(@Nullable Collection<String> l) {
-      this.languages = l;
-      return this;
-    }
-
-    public Builder tags(@Nullable Collection<String> t) {
-      this.tags = t;
-      return this;
-    }
-
-    public Builder types(@Nullable Collection<String> t) {
-      this.types = t;
-      return this;
-    }
-
-    public Builder owaspTop10(@Nullable Collection<String> o) {
-      this.owaspTop10 = o;
-      return this;
-    }
-
-    public Builder sansTop25(@Nullable Collection<String> s) {
-      this.sansTop25 = s;
-      return this;
-    }
-
-    public Builder cwe(@Nullable Collection<String> cwe) {
-      this.cwe = cwe;
-      return this;
-    }
-
-    public Builder createdAfterByProjectUuids(@Nullable Map<String, PeriodStart> createdAfterByProjectUuids) {
-      this.createdAfterByProjectUuids = createdAfterByProjectUuids;
-      return this;
-    }
-
-    /**
-     * If true, it will return only issues on the passed component(s)
-     * If false, it will return all issues on the passed component(s) and their descendants
-     */
-    public Builder onComponentOnly(@Nullable Boolean b) {
-      this.onComponentOnly = b;
-      return this;
-    }
-
-    /**
-     * If true, it will return all issues assigned to someone
-     * If false, it will return all issues not assigned to someone
-     */
-    public Builder assigned(@Nullable Boolean b) {
-      this.assigned = b;
-      return this;
-    }
-
-    /**
-     * If true, it will return all resolved issues
-     * If false, it will return all none resolved issues
-     */
-    public Builder resolved(@Nullable Boolean resolved) {
-      this.resolved = resolved;
-      return this;
-    }
-
-    public Builder createdAt(@Nullable Date d) {
-      this.createdAt = d == null ? null : new Date(d.getTime());
-      return this;
-    }
-
-    public Builder createdAfter(@Nullable Date d) {
-      this.createdAfter(d, true);
-      return this;
-    }
-
-    public Builder createdAfter(@Nullable Date d, boolean inclusive) {
-      this.createdAfter = d == null ? null : new PeriodStart(new Date(d.getTime()), inclusive);
-      return this;
-    }
-
-    public Builder createdBefore(@Nullable Date d) {
-      this.createdBefore = d == null ? null : new Date(d.getTime());
-      return this;
-    }
-
-    public Builder sort(@Nullable String s) {
-      if (s != null && !SORTS.contains(s)) {
-        throw new IllegalArgumentException("Bad sort field: " + s);
-      }
-      this.sort = s;
-      return this;
-    }
-
-    public Builder asc(@Nullable Boolean asc) {
-      this.asc = asc;
-      return this;
-    }
-
-    public IssueQuery build() {
-      if (issueKeys != null) {
-        checkArgument(issueKeys.size() <= MAX_LIMIT, "Number of issue keys must be less than " + MAX_LIMIT + " (got " + issueKeys.size() + ")");
-      }
-      return new IssueQuery(this);
-    }
-
-    public Builder checkAuthorization(boolean checkAuthorization) {
-      this.checkAuthorization = checkAuthorization;
-      return this;
-    }
-
-    public Builder facetMode(String facetMode) {
-      this.facetMode = facetMode;
-      return this;
-    }
-
-    public Builder organizationUuid(String s) {
-      this.organizationUuid = s;
-      return this;
-    }
-
-    public Builder branchUuid(@Nullable String s) {
-      this.branchUuid = s;
-      return this;
-    }
-
-    public Builder mainBranch(boolean mainBranch) {
-      this.mainBranch = mainBranch;
-      return this;
-    }
-  }
-
-  private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
-    return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c);
-  }
-
-  private static <K, V> Map<K, V> defaultMap(@Nullable Map<K, V> map) {
-    return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
-  }
-
-  public static class PeriodStart {
-    private final Date date;
-    private final boolean inclusive;
-
-    public PeriodStart(Date date, boolean inclusive) {
-      this.date = date;
-      this.inclusive = inclusive;
-
-    }
-
-    public Date date() {
-      return date;
-    }
-
-    public boolean inclusive() {
-      return inclusive;
-    }
-
-  }
-
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQueryFactory.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueQueryFactory.java
deleted file mode 100644 (file)
index e28607e..0000000
+++ /dev/null
@@ -1,372 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import java.time.Clock;
-import java.time.OffsetDateTime;
-import java.time.Period;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.BooleanUtils;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.web.UserRole;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.issue.IssueQuery.PeriodStart;
-import org.sonar.server.user.UserSession;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Collections2.transform;
-import static java.lang.String.format;
-import static java.util.Collections.singleton;
-import static java.util.Collections.singletonList;
-import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
-import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
-import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
-
-/**
- * This component is used to create an IssueQuery, in order to transform the component and component roots keys into uuid.
- */
-@ServerSide
-public class IssueQueryFactory {
-
-  public static final String UNKNOWN = "<UNKNOWN>";
-  private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN);
-
-  private final DbClient dbClient;
-  private final Clock clock;
-  private final UserSession userSession;
-
-  public IssueQueryFactory(DbClient dbClient, Clock clock, UserSession userSession) {
-    this.dbClient = dbClient;
-    this.clock = clock;
-    this.userSession = userSession;
-  }
-
-  public IssueQuery create(SearchRequest request) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      IssueQuery.Builder builder = IssueQuery.builder()
-        .issueKeys(request.getIssues())
-        .severities(request.getSeverities())
-        .statuses(request.getStatuses())
-        .resolutions(request.getResolutions())
-        .resolved(request.getResolved())
-        .rules(ruleKeysToRuleId(dbSession, request.getRules()))
-        .assigneeUuids(request.getAssigneeUuids())
-        .languages(request.getLanguages())
-        .tags(request.getTags())
-        .types(request.getTypes())
-        .owaspTop10(request.getOwaspTop10())
-        .sansTop25(request.getSansTop25())
-        .cwe(request.getCwe())
-        .assigned(request.getAssigned())
-        .createdAt(parseDateOrDateTime(request.getCreatedAt()))
-        .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore()))
-        .facetMode(request.getFacetMode())
-        .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));
-
-      List<ComponentDto> allComponents = new ArrayList<>();
-      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
-      addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);
-
-      setCreatedAfterFromRequest(dbSession, builder, request, allComponents);
-      String sort = request.getSort();
-      if (!Strings.isNullOrEmpty(sort)) {
-        builder.sort(sort);
-        builder.asc(request.getAsc());
-      }
-      return builder.build();
-    }
-  }
-
-  private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
-    checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
-
-    Date actualCreatedAfter = createdAfter;
-    if (createdInLast != null) {
-      actualCreatedAfter = Date.from(
-        OffsetDateTime.now(clock)
-          .minus(Period.parse("P" + createdInLast.toUpperCase(Locale.ENGLISH)))
-          .toInstant());
-    }
-    builder.createdAfter(actualCreatedAfter, createdAfterInclusive);
-  }
-
-  @CheckForNull
-  private String convertOrganizationKeyToUuid(DbSession dbSession, @Nullable String organizationKey) {
-    if (organizationKey == null) {
-      return null;
-    }
-    Optional<OrganizationDto> organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
-    return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
-  }
-
-  private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids) {
-    Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
-    String createdInLast = request.getCreatedInLast();
-
-    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
-      setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
-    } else {
-      checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
-      checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
-      ComponentDto component = componentUuids.iterator().next();
-      Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
-      setCreatedAfterFromDates(builder, createdAfterFromSnapshot, createdInLast, false);
-    }
-  }
-
-  @CheckForNull
-  private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) {
-    Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
-    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
-  }
-
-  private boolean mergeDeprecatedComponentParameters(DbSession session, SearchRequest request, List<ComponentDto> allComponents) {
-    Boolean onComponentOnly = request.getOnComponentOnly();
-    Collection<String> components = request.getComponents();
-    Collection<String> componentUuids = request.getComponentUuids();
-    Collection<String> componentKeys = request.getComponentKeys();
-    Collection<String> componentRootUuids = request.getComponentRootUuids();
-    Collection<String> componentRoots = request.getComponentRoots();
-    String branch = request.getBranch();
-    String pullRequest = request.getPullRequest();
-
-    boolean effectiveOnComponentOnly = false;
-
-    checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
-      "At most one of the following parameters can be provided: %s, %s, %s, %s, %s",
-      PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS, PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_COMPONENT_UUIDS);
-
-    if (componentRootUuids != null) {
-      allComponents.addAll(getComponentsFromUuids(session, componentRootUuids));
-    } else if (componentRoots != null) {
-      allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch, pullRequest));
-    } else if (components != null) {
-      allComponents.addAll(getComponentsFromKeys(session, components, branch, pullRequest));
-      effectiveOnComponentOnly = true;
-    } else if (componentUuids != null) {
-      allComponents.addAll(getComponentsFromUuids(session, componentUuids));
-      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
-    } else if (componentKeys != null) {
-      allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch, pullRequest));
-      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
-    }
-
-    return effectiveOnComponentOnly;
-  }
-
-  private static boolean atMostOneNonNullElement(Object... objects) {
-    return Arrays.stream(objects)
-      .filter(Objects::nonNull)
-      .count() <= 1;
-  }
-
-  private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly, List<ComponentDto> components, SearchRequest request) {
-    builder.onComponentOnly(onComponentOnly);
-    if (onComponentOnly) {
-      builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
-      setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
-      return;
-    }
-
-    builder.authors(request.getAuthors());
-    List<String> projectUuids = request.getProjectUuids();
-    List<String> projectKeys = request.getProjectKeys();
-    checkArgument(projectUuids == null || projectKeys == null, "Parameters projects and projectUuids cannot be set simultaneously");
-    if (projectUuids != null) {
-      builder.projectUuids(projectUuids);
-    } else if (projectKeys != null) {
-      List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch(), request.getPullRequest());
-      builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList()));
-      setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest());
-    }
-    builder.moduleUuids(request.getModuleUuids());
-    builder.directories(request.getDirectories());
-    builder.fileUuids(request.getFileUuids());
-
-    addComponentsBasedOnQualifier(builder, session, components, request);
-  }
-
-  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, SearchRequest request) {
-    if (components.isEmpty()) {
-      return;
-    }
-    if (components.stream().map(ComponentDto::uuid).anyMatch(uuid -> uuid.equals(UNKNOWN))) {
-      builder.componentUuids(singleton(UNKNOWN));
-      return;
-    }
-
-    Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet());
-    checkArgument(qualifiers.size() == 1, "All components must have the same qualifier, found %s", String.join(",", qualifiers));
-
-    setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
-    String qualifier = qualifiers.iterator().next();
-    switch (qualifier) {
-      case Qualifiers.VIEW:
-      case Qualifiers.SUBVIEW:
-        addViewsOrSubViews(builder, components);
-        break;
-      case Qualifiers.APP:
-        addApplications(builder, dbSession, components, request);
-        break;
-      case Qualifiers.PROJECT:
-        builder.projectUuids(components.stream().map(IssueQueryFactory::toProjectUuid).collect(toList()));
-        break;
-      case Qualifiers.MODULE:
-        builder.moduleRootUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
-        break;
-      case Qualifiers.DIRECTORY:
-        addDirectories(builder, components);
-        break;
-      case Qualifiers.FILE:
-      case Qualifiers.UNIT_TEST_FILE:
-        builder.fileUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
-        break;
-      default:
-        throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(components));
-    }
-  }
-
-  private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<ComponentDto> viewOrSubViewUuids) {
-    List<String> filteredViewUuids = viewOrSubViewUuids.stream()
-      .filter(uuid -> userSession.hasComponentPermission(UserRole.USER, uuid))
-      .map(ComponentDto::uuid)
-      .collect(Collectors.toList());
-    if (filteredViewUuids.isEmpty()) {
-      filteredViewUuids.add(UNKNOWN);
-    }
-    builder.viewUuids(filteredViewUuids);
-  }
-
-  private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> applications, SearchRequest request) {
-    Set<String> authorizedApplicationUuids = applications.stream()
-      .filter(app -> userSession.hasComponentPermission(UserRole.USER, app))
-      .map(ComponentDto::uuid)
-      .collect(toSet());
-
-    builder.viewUuids(authorizedApplicationUuids.isEmpty() ? singleton(UNKNOWN) : authorizedApplicationUuids);
-    addCreatedAfterByProjects(builder, dbSession, request, authorizedApplicationUuids);
-  }
-
-  private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> applicationUuids) {
-    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
-      return;
-    }
-
-    Set<String> projectUuids = applicationUuids.stream()
-      .flatMap(app -> dbClient.componentDao().selectProjectsFromView(dbSession, app, app).stream())
-      .collect(toSet());
-
-    Map<String, PeriodStart> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
-      .stream()
-      .filter(s -> s.getPeriodDate() != null)
-      .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false)));
-    builder.createdAfterByProjectUuids(leakByProjects);
-  }
-
-  private static void addDirectories(IssueQuery.Builder builder, List<ComponentDto> directories) {
-    Collection<String> directoryModuleUuids = new HashSet<>();
-    Collection<String> directoryPaths = new HashSet<>();
-    for (ComponentDto directory : directories) {
-      directoryModuleUuids.add(directory.moduleUuid());
-      directoryPaths.add(directory.path());
-    }
-    builder.moduleUuids(directoryModuleUuids);
-    builder.directories(directoryPaths);
-  }
-
-  private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) {
-    List<ComponentDto> componentDtos;
-    if (branch != null) {
-      componentDtos = dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch);
-    } else if (pullRequest != null) {
-      componentDtos = dbClient.componentDao().selectByKeysAndPullRequest(dbSession, componentKeys, pullRequest);
-    } else {
-      componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys);
-    }
-    if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
-      return singletonList(UNKNOWN_COMPONENT);
-    }
-    return componentDtos;
-  }
-
-  private List<ComponentDto> getComponentsFromUuids(DbSession dbSession, Collection<String> componentUuids) {
-    List<ComponentDto> componentDtos = dbClient.componentDao().selectByUuids(dbSession, componentUuids);
-    if (!componentUuids.isEmpty() && componentDtos.isEmpty()) {
-      return singletonList(UNKNOWN_COMPONENT);
-    }
-    return componentDtos;
-  }
-
-  @CheckForNull
-  private Collection<RuleDefinitionDto> ruleKeysToRuleId(DbSession dbSession, @Nullable Collection<String> rules) {
-    if (rules != null) {
-      return dbClient.ruleDao().selectDefinitionByKeys(dbSession, transform(rules, RuleKey::parse));
-    }
-    return Collections.emptyList();
-  }
-
-  private static String toProjectUuid(ComponentDto componentDto) {
-    String mainBranchProjectUuid = componentDto.getMainBranchProjectUuid();
-    return mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid;
-  }
-
-  private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch, @Nullable String pullRequest) {
-    builder.branchUuid(branch == null && pullRequest == null ? null : component.projectUuid());
-    builder.mainBranch(UNKNOWN_COMPONENT.equals(component)
-      || (branch == null && pullRequest == null)
-      || (branch != null && !branch.equals(component.getBranch()))
-      || (pullRequest != null && !pullRequest.equals(component.getPullRequest())));
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndex.java
deleted file mode 100644 (file)
index 5c0522d..0000000
+++ /dev/null
@@ -1,919 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.OptionalInt;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.BooleanUtils;
-import org.apache.commons.lang.StringUtils;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.indices.TermsLookup;
-import org.elasticsearch.search.aggregations.AggregationBuilder;
-import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.HasAggregations;
-import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
-import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
-import org.elasticsearch.search.aggregations.bucket.histogram.ExtendedBounds;
-import org.elasticsearch.search.aggregations.bucket.terms.InternalTerms;
-import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
-import org.elasticsearch.search.aggregations.bucket.terms.Terms;
-import org.elasticsearch.search.aggregations.bucket.terms.Terms.Order;
-import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude;
-import org.elasticsearch.search.aggregations.metrics.max.InternalMax;
-import org.elasticsearch.search.aggregations.metrics.min.Min;
-import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.valuecount.InternalValueCount;
-import org.elasticsearch.search.sort.FieldSortBuilder;
-import org.joda.time.DateTimeZone;
-import org.joda.time.Duration;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.es.BaseDoc;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.EsUtils;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.Sorting;
-import org.sonar.server.es.StickyFacetBuilder;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQuery.PeriodStart;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.user.UserSession;
-import org.sonar.server.view.index.ViewIndexDefinition;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds;
-import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
-import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID;
-import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_ACTION_PLANS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_ASSIGNED_TO_ME;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHORS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILE_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
-
-/**
- * The unique entry-point to interact with Elasticsearch index "issues".
- * All the requests are listed here.
- */
-public class IssueIndex {
-
-  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
-    PARAM_SEVERITIES,
-    PARAM_STATUSES,
-    PARAM_RESOLUTIONS,
-    DEPRECATED_PARAM_ACTION_PLANS,
-    PARAM_PROJECT_UUIDS,
-    PARAM_RULES,
-    PARAM_ASSIGNEES,
-    FACET_ASSIGNED_TO_ME,
-    PARAM_REPORTERS,
-    PARAM_AUTHORS,
-    PARAM_MODULE_UUIDS,
-    PARAM_FILE_UUIDS,
-    PARAM_DIRECTORIES,
-    PARAM_LANGUAGES,
-    PARAM_TAGS,
-    PARAM_TYPES,
-    PARAM_OWASP_TOP_10,
-    PARAM_SANS_TOP_25,
-    PARAM_CWE,
-    PARAM_CREATED_AT);
-  public static final String AGGREGATION_NAME_FOR_TAGS = "tags__issues";
-  private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*";
-  // TODO to be documented
-  // TODO move to Facets ?
-  private static final String FACET_SUFFIX_MISSING = "_missing";
-  private static final String IS_ASSIGNED_FILTER = "__isAssigned";
-  private static final SumAggregationBuilder EFFORT_AGGREGATION = AggregationBuilders.sum(FACET_MODE_EFFORT).field(IssueIndexDefinition.FIELD_ISSUE_EFFORT);
-  private static final Order EFFORT_AGGREGATION_ORDER = Order.aggregation(FACET_MODE_EFFORT, false);
-  private static final int DEFAULT_FACET_SIZE = 15;
-  private static final Duration TWENTY_DAYS = Duration.standardDays(20L);
-  private static final Duration TWENTY_WEEKS = Duration.standardDays(20L * 7L);
-  private static final Duration TWENTY_MONTHS = Duration.standardDays(20L * 30L);
-  private static final String COUNT = "count";
-  private final Sorting sorting;
-  private final EsClient client;
-  private final System2 system;
-  private final UserSession userSession;
-  private final AuthorizationTypeSupport authorizationTypeSupport;
-
-  public IssueIndex(EsClient client, System2 system, UserSession userSession, AuthorizationTypeSupport authorizationTypeSupport) {
-    this.client = client;
-    this.system = system;
-    this.userSession = userSession;
-    this.authorizationTypeSupport = authorizationTypeSupport;
-
-    this.sorting = new Sorting();
-    this.sorting.add(IssueQuery.SORT_BY_STATUS, IssueIndexDefinition.FIELD_ISSUE_STATUS);
-    this.sorting.add(IssueQuery.SORT_BY_SEVERITY, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE);
-    this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT);
-    this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT);
-    this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT);
-    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID);
-    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_FILE_PATH);
-    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_LINE);
-    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE).reverse();
-    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_KEY);
-
-    // by default order by created date, project, file, line and issue key (in order to be deterministic when same ms)
-    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).reverse();
-    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID);
-    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FILE_PATH);
-    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_LINE);
-    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_KEY);
-  }
-
-  public SearchResponse search(IssueQuery query, SearchOptions options) {
-    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_TYPE_ISSUE);
-
-    configureSorting(query, requestBuilder);
-    configurePagination(options, requestBuilder);
-    configureRouting(query, options, requestBuilder);
-
-    QueryBuilder esQuery = matchAllQuery();
-    BoolQueryBuilder esFilter = boolQuery();
-    Map<String, QueryBuilder> filters = createFilters(query);
-    for (QueryBuilder filter : filters.values()) {
-      if (filter != null) {
-        esFilter.must(filter);
-      }
-    }
-    if (esFilter.hasClauses()) {
-      requestBuilder.setQuery(boolQuery().must(esQuery).filter(esFilter));
-    } else {
-      requestBuilder.setQuery(esQuery);
-    }
-
-    configureStickyFacets(query, options, filters, esQuery, requestBuilder);
-    requestBuilder.setFetchSource(false);
-    return requestBuilder.get();
-  }
-
-  /**
-   * Optimization - do not send ES request to all shards when scope is restricted
-   * to a set of projects. Because project UUID is used for routing, the request
-   * can be sent to only the shards containing the specified projects.
-   * Note that sticky facets may involve all projects, so this optimization must be
-   * disabled when facets are enabled.
-   */
-  private static void configureRouting(IssueQuery query, SearchOptions options, SearchRequestBuilder requestBuilder) {
-    Collection<String> uuids = query.projectUuids();
-    if (!uuids.isEmpty() && options.getFacets().isEmpty()) {
-      requestBuilder.setRouting(uuids.toArray(new String[uuids.size()]));
-    }
-  }
-
-  private static void configurePagination(SearchOptions options, SearchRequestBuilder esSearch) {
-    esSearch.setFrom(options.getOffset()).setSize(options.getLimit());
-  }
-
-  private Map<String, QueryBuilder> createFilters(IssueQuery query) {
-    Map<String, QueryBuilder> filters = new HashMap<>();
-    filters.put("__authorization", createAuthorizationFilter(query.checkAuthorization()));
-
-    // Issue is assigned Filter
-    if (BooleanUtils.isTrue(query.assigned())) {
-      filters.put(IS_ASSIGNED_FILTER, existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID));
-    } else if (BooleanUtils.isFalse(query.assigned())) {
-      filters.put(IS_ASSIGNED_FILTER, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID)));
-    }
-
-    // Issue is Resolved Filter
-    String isResolved = "__isResolved";
-    if (BooleanUtils.isTrue(query.resolved())) {
-      filters.put(isResolved, existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION));
-    } else if (BooleanUtils.isFalse(query.resolved())) {
-      filters.put(isResolved, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)));
-    }
-
-    // Field Filters
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_TYPE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_ID, createTermsFilter(
-      IssueIndexDefinition.FIELD_ISSUE_RULE_ID,
-      query.rules().stream().map(RuleDefinitionDto::getId).collect(Collectors.toList())));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, query.sansTop25()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_CWE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_CWE, query.cwe()));
-
-    addComponentRelatedFilters(query, filters);
-
-    addDatesFilter(filters, query);
-    addCreatedAfterByProjectsFilter(filters, query);
-    return filters;
-  }
-
-  private static void addComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
-    addCommonComponentRelatedFilters(query, filters);
-    if (query.viewUuids().isEmpty()) {
-      addBranchComponentRelatedFilters(query, filters);
-    } else {
-      addViewRelatedFilters(query, filters);
-    }
-  }
-
-  private static void addCommonComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
-    QueryBuilder componentFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.componentUuids());
-    QueryBuilder projectFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids());
-    QueryBuilder moduleRootFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, query.moduleRootUuids());
-    QueryBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids());
-    QueryBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories());
-    QueryBuilder fileFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids());
-
-    if (BooleanUtils.isTrue(query.onComponentOnly())) {
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentFilter);
-    } else {
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectFilter);
-      filters.put("__module", moduleRootFilter);
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleFilter);
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryFilter);
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, fileFilter != null ? fileFilter : componentFilter);
-    }
-  }
-
-  private static void addBranchComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
-    if (BooleanUtils.isTrue(query.onComponentOnly())) {
-      return;
-    }
-    QueryBuilder branchFilter = createTermFilter(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, query.branchUuid());
-    filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchFilter);
-  }
-
-  private static void addViewRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
-    if (BooleanUtils.isTrue(query.onComponentOnly())) {
-      return;
-    }
-    Collection<String> viewUuids = query.viewUuids();
-    String branchUuid = query.branchUuid();
-    boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
-    if (onApplicationBranch) {
-      filters.put("__view", createViewFilter(singletonList(query.branchUuid())));
-    } else {
-      filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(true)));
-      filters.put("__view", createViewFilter(viewUuids));
-    }
-  }
-
-  @CheckForNull
-  private static QueryBuilder createViewFilter(Collection<String> viewUuids) {
-    if (viewUuids.isEmpty()) {
-      return null;
-    }
-
-    BoolQueryBuilder viewsFilter = boolQuery();
-    for (String viewUuid : viewUuids) {
-      viewsFilter.should(QueryBuilders.termsLookupQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID,
-        new TermsLookup(
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
-          viewUuid,
-          ViewIndexDefinition.FIELD_PROJECTS)));
-    }
-    return viewsFilter;
-  }
-
-  private static StickyFacetBuilder newStickyFacetBuilder(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
-    if (hasQueryEffortFacet(query)) {
-      return new StickyFacetBuilder(esQuery, filters, EFFORT_AGGREGATION, EFFORT_AGGREGATION_ORDER);
-    }
-    return new StickyFacetBuilder(esQuery, filters);
-  }
-
-  private static void addSimpleStickyFacetIfNeeded(SearchOptions options, StickyFacetBuilder stickyFacetBuilder, SearchRequestBuilder esSearch,
-    String facetName, String fieldName, Object... selectedValues) {
-    if (options.getFacets().contains(facetName)) {
-      esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldName, facetName, DEFAULT_FACET_SIZE, selectedValues));
-    }
-  }
-
-  private static AggregationBuilder addEffortAggregationIfNeeded(IssueQuery query, AggregationBuilder aggregation) {
-    if (hasQueryEffortFacet(query)) {
-      aggregation.subAggregation(EFFORT_AGGREGATION);
-    }
-    return aggregation;
-  }
-
-  private static boolean hasQueryEffortFacet(IssueQuery query) {
-    return FACET_MODE_EFFORT.equals(query.facetMode()) || DEPRECATED_FACET_MODE_DEBT.equals(query.facetMode());
-  }
-
-  private static AggregationBuilder createAssigneesFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) {
-    String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
-    String facetName = PARAM_ASSIGNEES;
-
-    // Same as in super.stickyFacetBuilder
-    Map<String, QueryBuilder> assigneeFilters = Maps.newHashMap(filters);
-    assigneeFilters.remove(IS_ASSIGNED_FILTER);
-    assigneeFilters.remove(fieldName);
-    StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, assigneeFilters, queryBuilder);
-    BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName);
-    FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE);
-
-    Collection<String> assigneesEscaped = escapeValuesForFacetInclusion(query.assignees());
-    if (!assigneesEscaped.isEmpty()) {
-      facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t, assigneesEscaped.toArray());
-    }
-
-    // Add missing facet for unassigned issues
-    facetTopAggregation.subAggregation(
-      addEffortAggregationIfNeeded(query, AggregationBuilders
-        .missing(facetName + FACET_SUFFIX_MISSING)
-        .field(fieldName)));
-
-    return AggregationBuilders
-      .global(facetName)
-      .subAggregation(facetTopAggregation);
-  }
-
-  private static Collection<String> escapeValuesForFacetInclusion(@Nullable Collection<String> values) {
-    if (values == null) {
-      return Collections.emptyList();
-    }
-    return values.stream().map(Pattern::quote).collect(MoreCollectors.toArrayList(values.size()));
-  }
-
-  private static AggregationBuilder createResolutionFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
-    String fieldName = IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
-    String facetName = PARAM_RESOLUTIONS;
-
-    // Same as in super.stickyFacetBuilder
-    Map<String, QueryBuilder> resolutionFilters = Maps.newHashMap(filters);
-    resolutionFilters.remove("__isResolved");
-    resolutionFilters.remove(fieldName);
-    StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, resolutionFilters, esQuery);
-    BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName);
-    FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE);
-    facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t);
-
-    // Add missing facet for unresolved issues
-    facetTopAggregation.subAggregation(
-      addEffortAggregationIfNeeded(query, AggregationBuilders
-        .missing(facetName + FACET_SUFFIX_MISSING)
-        .field(fieldName)));
-
-    return AggregationBuilders
-      .global(facetName)
-      .subAggregation(facetTopAggregation);
-  }
-
-  @CheckForNull
-  private static QueryBuilder createTermsFilter(String field, Collection<?> values) {
-    return values.isEmpty() ? null : termsQuery(field, values);
-  }
-
-  @CheckForNull
-  private static QueryBuilder createTermFilter(String field, @Nullable String value) {
-    return value == null ? null : termQuery(field, value);
-  }
-
-  private void configureSorting(IssueQuery query, SearchRequestBuilder esRequest) {
-    createSortBuilders(query).forEach(esRequest::addSort);
-  }
-
-  private List<FieldSortBuilder> createSortBuilders(IssueQuery query) {
-    String sortField = query.sort();
-    if (sortField != null) {
-      boolean asc = BooleanUtils.isTrue(query.asc());
-      return sorting.fill(sortField, asc);
-    }
-    return sorting.fillDefault();
-  }
-
-  private QueryBuilder createAuthorizationFilter(boolean checkAuthorization) {
-    if (checkAuthorization) {
-      return authorizationTypeSupport.createQueryFilter();
-    }
-    return matchAllQuery();
-  }
-
-  private void addDatesFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
-    PeriodStart createdAfter = query.createdAfter();
-    Date createdBefore = query.createdBefore();
-
-    validateCreationDateBounds(createdBefore, createdAfter != null ? createdAfter.date() : null);
-
-    if (createdAfter != null) {
-      filters.put("__createdAfter", QueryBuilders
-        .rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
-        .from(BaseDoc.dateToEpochSeconds(createdAfter.date()), createdAfter.inclusive()));
-    }
-    if (createdBefore != null) {
-      filters.put("__createdBefore", QueryBuilders
-        .rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
-        .lt(BaseDoc.dateToEpochSeconds(createdBefore)));
-    }
-    Date createdAt = query.createdAt();
-    if (createdAt != null) {
-      filters.put("__createdAt", termQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT, BaseDoc.dateToEpochSeconds(createdAt)));
-    }
-  }
-
-  private static void addCreatedAfterByProjectsFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
-    Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
-    BoolQueryBuilder boolQueryBuilder = boolQuery();
-    createdAfterByProjectUuids.forEach((projectUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
-      .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
-      .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).from(BaseDoc.dateToEpochSeconds(createdAfterDate.date()), createdAfterDate.inclusive()))));
-    filters.put("createdAfterByProjectUuids", boolQueryBuilder);
-  }
-
-  private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
-    Preconditions.checkArgument(createdAfter == null || createdAfter.before(new Date(system.now())),
-      "Start bound cannot be in the future");
-    Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore),
-      "Start bound cannot be larger or equal to end bound");
-  }
-
-  private void configureStickyFacets(IssueQuery query, SearchOptions options, Map<String, QueryBuilder> filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) {
-    if (!options.getFacets().isEmpty()) {
-      StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, filters, esQuery);
-      // Execute Term aggregations
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_SEVERITIES, IssueIndexDefinition.FIELD_ISSUE_SEVERITY);
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_STATUSES, IssueIndexDefinition.FIELD_ISSUE_STATUS);
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_PROJECT_UUIDS, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids().toArray());
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_MODULE_UUIDS, IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids().toArray());
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_DIRECTORIES, IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories().toArray());
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_FILE_UUIDS, IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids().toArray());
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_LANGUAGES, IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages().toArray());
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_RULES, IssueIndexDefinition.FIELD_ISSUE_RULE_ID, query.rules().stream().map(RuleDefinitionDto::getId).toArray());
-
-      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
-        PARAM_AUTHORS, IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors().toArray());
-
-      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_TAGS, IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags());
-      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_TYPES, IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types());
-      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_OWASP_TOP_10, IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10());
-      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_SANS_TOP_25, IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, query.sansTop25());
-      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_CWE, IssueIndexDefinition.FIELD_ISSUE_CWE, query.cwe());
-      if (options.getFacets().contains(PARAM_RESOLUTIONS)) {
-        esSearch.addAggregation(createResolutionFacet(query, filters, esQuery));
-      }
-      if (options.getFacets().contains(PARAM_ASSIGNEES)) {
-        esSearch.addAggregation(createAssigneesFacet(query, filters, esQuery));
-      }
-      addAssignedToMeFacetIfNeeded(esSearch, options, query, filters, esQuery);
-      if (options.getFacets().contains(PARAM_CREATED_AT)) {
-        getCreatedAtFacet(query, filters, esQuery).ifPresent(esSearch::addAggregation);
-      }
-    }
-
-    if (hasQueryEffortFacet(query)) {
-      esSearch.addAggregation(EFFORT_AGGREGATION);
-    }
-  }
-
-  private static void addStickyFacetIfNeeded(SearchOptions options, SearchRequestBuilder esSearch, StickyFacetBuilder stickyFacetBuilder, String paramTags, String fieldIssueTags,
-    Collection<String> tags) {
-    if (options.getFacets().contains(paramTags)) {
-      esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldIssueTags, paramTags, tags.toArray()));
-    }
-  }
-
-  private Optional<AggregationBuilder> getCreatedAtFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
-    long startTime;
-    boolean startInclusive;
-    PeriodStart createdAfter = query.createdAfter();
-    if (createdAfter == null) {
-      Optional<Long> minDate = getMinCreatedAt(filters, esQuery);
-      if (!minDate.isPresent()) {
-        return Optional.empty();
-      }
-      startTime = minDate.get();
-      startInclusive = true;
-    } else {
-      startTime = createdAfter.date().getTime();
-      startInclusive = createdAfter.inclusive();
-    }
-    Date createdBefore = query.createdBefore();
-    long endTime = createdBefore == null ? system.now() : createdBefore.getTime();
-
-    Duration timeSpan = new Duration(startTime, endTime);
-    DateHistogramInterval bucketSize = DateHistogramInterval.YEAR;
-    if (timeSpan.isShorterThan(TWENTY_DAYS)) {
-      bucketSize = DateHistogramInterval.DAY;
-    } else if (timeSpan.isShorterThan(TWENTY_WEEKS)) {
-      bucketSize = DateHistogramInterval.WEEK;
-    } else if (timeSpan.isShorterThan(TWENTY_MONTHS)) {
-      bucketSize = DateHistogramInterval.MONTH;
-    }
-
-    AggregationBuilder dateHistogram = AggregationBuilders.dateHistogram(PARAM_CREATED_AT)
-      .field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
-      .dateHistogramInterval(bucketSize)
-      .minDocCount(0L)
-      .format(DateUtils.DATETIME_FORMAT)
-      .timeZone(DateTimeZone.forOffsetMillis(system.getDefaultTimeZone().getRawOffset()))
-      // ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
-      .extendedBounds(new ExtendedBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
-    addEffortAggregationIfNeeded(query, dateHistogram);
-    return Optional.of(dateHistogram);
-  }
-
-  private Optional<Long> getMinCreatedAt(Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
-    String facetNameAndField = IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
-    SearchRequestBuilder esRequest = client
-      .prepareSearch(INDEX_TYPE_ISSUE)
-      .setSize(0);
-    BoolQueryBuilder esFilter = boolQuery();
-    filters.values().stream().filter(Objects::nonNull).forEach(esFilter::must);
-    if (esFilter.hasClauses()) {
-      esRequest.setQuery(QueryBuilders.boolQuery().must(esQuery).filter(esFilter));
-    } else {
-      esRequest.setQuery(esQuery);
-    }
-    esRequest.addAggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField));
-    Min minValue = esRequest.get().getAggregations().get(facetNameAndField);
-
-    Double actualValue = minValue.getValue();
-    if (actualValue.isInfinite()) {
-      return Optional.empty();
-    }
-    return Optional.of(actualValue.longValue());
-  }
-
-  private void addAssignedToMeFacetIfNeeded(SearchRequestBuilder builder, SearchOptions options, IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) {
-    String uuid = userSession.getUuid();
-
-    if (!options.getFacets().contains(FACET_ASSIGNED_TO_ME) || StringUtils.isEmpty(uuid)) {
-      return;
-    }
-
-    String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
-    String facetName = FACET_ASSIGNED_TO_ME;
-
-    // Same as in super.stickyFacetBuilder
-    StickyFacetBuilder assignedToMeFacetBuilder = newStickyFacetBuilder(query, filters, queryBuilder);
-    BoolQueryBuilder facetFilter = assignedToMeFacetBuilder.getStickyFacetFilter(IS_ASSIGNED_FILTER, fieldName);
-
-    FilterAggregationBuilder facetTopAggregation = AggregationBuilders
-      .filter(facetName + "__filter", facetFilter)
-      .subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders.terms(facetName + "__terms")
-        .field(fieldName)
-        .includeExclude(new IncludeExclude(escapeSpecialRegexChars(uuid), null))));
-
-    builder.addAggregation(
-      AggregationBuilders.global(facetName)
-        .subAggregation(facetTopAggregation));
-  }
-
-  public List<String> listTags(@Nullable OrganizationDto organization, @Nullable String textQuery, int size) {
-    int maxPageSize = 500;
-    checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize);
-    if (size <= 0) {
-      return emptyList();
-    }
-
-    BoolQueryBuilder esQuery = boolQuery()
-      .filter(createAuthorizationFilter(true));
-    if (organization != null) {
-      esQuery.filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid()));
-    }
-
-    SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_ISSUE)
-      .setQuery(esQuery)
-      .setSize(0);
-
-    TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME_FOR_TAGS)
-      .field(IssueIndexDefinition.FIELD_ISSUE_TAGS)
-      .size(size)
-      .order(Terms.Order.term(true))
-      .minDocCount(1L);
-    if (textQuery != null) {
-      String escapedTextQuery = escapeSpecialRegexChars(textQuery);
-      termsAggregation.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapedTextQuery), null));
-    }
-    requestBuilder.addAggregation(termsAggregation);
-
-    SearchResponse searchResponse = requestBuilder.get();
-    Terms issuesResult = searchResponse.getAggregations().get(AGGREGATION_NAME_FOR_TAGS);
-    return EsUtils.termsKeys(issuesResult);
-  }
-
-  public Map<String, Long> countTags(IssueQuery query, int maxNumberOfTags) {
-    Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, null, Terms.Order.count(false), maxNumberOfTags);
-    return EsUtils.termsToMap(terms);
-  }
-
-  public List<String> listAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) {
-    Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, Terms.Order.term(true), maxNumberOfAuthors);
-    return EsUtils.termsKeys(terms);
-  }
-
-  private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int maxNumberOfTags) {
-    SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_ISSUE)
-      // Avoids returning search hits
-      .setSize(0);
-
-    requestBuilder.setQuery(boolQuery().must(QueryBuilders.matchAllQuery()).filter(createBoolFilter(query)));
-
-    TermsAggregationBuilder aggreg = AggregationBuilders.terms("_ref")
-      .field(fieldName)
-      .size(maxNumberOfTags)
-      .order(termsOrder)
-      .minDocCount(1L);
-    if (textQuery != null) {
-      aggreg.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapeSpecialRegexChars(textQuery)), null));
-    }
-
-    SearchResponse searchResponse = requestBuilder.addAggregation(aggreg).get();
-    return searchResponse.getAggregations().get("_ref");
-  }
-
-  private BoolQueryBuilder createBoolFilter(IssueQuery query) {
-    BoolQueryBuilder boolQuery = boolQuery();
-    for (QueryBuilder filter : createFilters(query).values()) {
-      if (filter != null) {
-        boolQuery.must(filter);
-      }
-    }
-    return boolQuery;
-  }
-
-  public List<ProjectStatistics> searchProjectStatistics(List<String> projectUuids, List<Long> froms, @Nullable String assigneeUuid) {
-    checkState(projectUuids.size() == froms.size(),
-      "Expected same size for projectUuids (had size %s) and froms (had size %s)", projectUuids.size(), froms.size());
-    if (projectUuids.isEmpty()) {
-      return Collections.emptyList();
-    }
-    SearchRequestBuilder request = client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
-      .setQuery(
-        boolQuery()
-          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION))
-          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, assigneeUuid))
-          .mustNot(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name())))
-      .setSize(0);
-    IntStream.range(0, projectUuids.size()).forEach(i -> {
-      String projectUuid = projectUuids.get(i);
-      long from = froms.get(i);
-      request
-        .addAggregation(AggregationBuilders
-          .filter(projectUuid, boolQuery()
-            .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
-            .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).gte(epochMillisToEpochSeconds(from))))
-          .subAggregation(
-            AggregationBuilders.terms("branchUuid").field(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID)
-              .subAggregation(
-                AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY))
-              .subAggregation(
-                AggregationBuilders.max("maxFuncCreatedAt").field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT))));
-    });
-    SearchResponse response = request.get();
-    return response.getAggregations().asList().stream()
-      .map(x -> (InternalFilter) x)
-      .flatMap(projectBucket -> ((StringTerms) projectBucket.getAggregations().get("branchUuid")).getBuckets().stream()
-        .flatMap(branchBucket -> {
-          long count = ((InternalValueCount) branchBucket.getAggregations().get(COUNT)).getValue();
-          if (count < 1L) {
-            return Stream.empty();
-          }
-          long lastIssueDate = (long) ((InternalMax) branchBucket.getAggregations().get("maxFuncCreatedAt")).getValue();
-          return Stream.of(new ProjectStatistics(branchBucket.getKeyAsString(), count, lastIssueDate));
-        }))
-      .collect(MoreCollectors.toList(projectUuids.size()));
-  }
-
-  public List<BranchStatistics> searchBranchStatistics(String projectUuid, List<String> branchUuids) {
-    if (branchUuids.isEmpty()) {
-      return Collections.emptyList();
-    }
-
-    SearchRequestBuilder request = client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
-      .setRouting(projectUuid)
-      .setQuery(
-        boolQuery()
-          .must(termsQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchUuids))
-          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION))
-          .must(termQuery(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(false))))
-      .setSize(0)
-      .addAggregation(AggregationBuilders.terms("branchUuids")
-        .field(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID)
-        .size(branchUuids.size())
-        .subAggregation(AggregationBuilders.terms("types")
-          .field(IssueIndexDefinition.FIELD_ISSUE_TYPE)));
-    SearchResponse response = request.get();
-    return ((StringTerms) response.getAggregations().get("branchUuids")).getBuckets().stream()
-      .map(bucket -> new BranchStatistics(bucket.getKeyAsString(),
-        ((StringTerms) bucket.getAggregations().get("types")).getBuckets()
-          .stream()
-          .collect(uniqueIndex(StringTerms.Bucket::getKeyAsString, InternalTerms.Bucket::getDocCount))))
-      .collect(MoreCollectors.toList(branchUuids.size()));
-  }
-
-  public List<SecurityStandardCategoryStatistics> getSansTop25Report(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
-    SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
-    Stream.of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES).forEach(sansCategory -> {
-      AggregationBuilder sansCategoryAggs = AggregationBuilders
-        .filter(sansCategory, boolQuery()
-          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, sansCategory)));
-      request.addAggregation(addSecurityReportSubAggregations(sansCategoryAggs, includeCwe));
-    });
-    return processSecurityReportSearchResults(request, includeCwe);
-  }
-
-  public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
-    SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
-    Stream.concat(IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i), Stream.of(UNKNOWN_STANDARD)).forEach(owaspCategory -> {
-      AggregationBuilder owaspCategoryAggs = AggregationBuilders
-        .filter(owaspCategory, boolQuery()
-          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, owaspCategory)));
-      request.addAggregation(addSecurityReportSubAggregations(owaspCategoryAggs, includeCwe));
-    });
-    return processSecurityReportSearchResults(request, includeCwe);
-  }
-
-  private static List<SecurityStandardCategoryStatistics> processSecurityReportSearchResults(SearchRequestBuilder request, boolean includeCwe) {
-    SearchResponse response = request.get();
-    return response.getAggregations().asList().stream()
-      .map(c -> processSecurityReportIssueSearchResults((InternalFilter) c, includeCwe))
-      .collect(MoreCollectors.toList());
-  }
-
-  private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(InternalFilter categoryBucket, boolean includeCwe) {
-    List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
-
-    if (includeCwe) {
-      ((StringTerms) categoryBucket.getAggregations().get("cwe")).getBuckets()
-        .forEach(cweBucket -> children.add(processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null)));
-    }
-
-    return processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getName(), children);
-  }
-
-  private static SecurityStandardCategoryStatistics processSecurityReportCategorySearchResults(HasAggregations categoryBucket, String categoryName,
-    @Nullable List<SecurityStandardCategoryStatistics> children) {
-    List<StringTerms.Bucket> severityBuckets = ((StringTerms) ((InternalFilter) categoryBucket.getAggregations().get("vulnerabilities")).getAggregations().get("severity"))
-      .getBuckets();
-    long vulnerabilities = severityBuckets.stream().mapToLong(b -> ((InternalValueCount) b.getAggregations().get(COUNT)).getValue()).sum();
-    // Worst severity having at least one issue
-    OptionalInt severityRating = severityBuckets.stream()
-      .filter(b -> ((InternalValueCount) b.getAggregations().get(COUNT)).getValue() != 0)
-      .mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
-      .max();
-
-    long openSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("openSecurityHotspots")).getAggregations().get(COUNT))
-      .getValue();
-    long toReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("toReviewSecurityHotspots")).getAggregations().get(COUNT))
-      .getValue();
-    long wontFixSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("wontFixSecurityHotspots")).getAggregations().get(COUNT))
-      .getValue();
-
-    return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots, openSecurityHotspots,
-      wontFixSecurityHotspots, children);
-  }
-
-  private static AggregationBuilder addSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe) {
-    AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
-    if (includeCwe) {
-      categoriesAggs
-        .subAggregation(addSecurityReportIssueCountAggregations(AggregationBuilders.terms("cwe").field(IssueIndexDefinition.FIELD_ISSUE_CWE)));
-    }
-    return aggregationBuilder;
-  }
-
-  private static AggregationBuilder addSecurityReportIssueCountAggregations(AggregationBuilder categoryAggs) {
-    return categoryAggs
-      .subAggregation(
-        AggregationBuilders.filter("vulnerabilities", boolQuery()
-          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.VULNERABILITY.name()))
-          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
-          .subAggregation(
-            AggregationBuilders.terms("severity").field(IssueIndexDefinition.FIELD_ISSUE_SEVERITY)
-              .subAggregation(
-                AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY))))
-      .subAggregation(AggregationBuilders.filter("openSecurityHotspots", boolQuery()
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
-        .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
-        .subAggregation(
-          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
-      .subAggregation(AggregationBuilders.filter("toReviewSecurityHotspots", boolQuery()
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED)))
-        .subAggregation(
-          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
-      .subAggregation(AggregationBuilders.filter("wontFixSecurityHotspots", boolQuery()
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
-        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_WONT_FIX)))
-        .subAggregation(
-          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)));
-  }
-
-  private SearchRequestBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid, boolean isViewOrApp) {
-    BoolQueryBuilder componentFilter = boolQuery();
-    if (isViewOrApp) {
-      componentFilter.filter(QueryBuilders.termsLookupQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID,
-        new TermsLookup(
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
-          projectUuid,
-          ViewIndexDefinition.FIELD_PROJECTS)));
-    } else {
-      componentFilter.filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, projectUuid));
-    }
-    return client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
-      .setQuery(
-        componentFilter
-          .filter(termsQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name(), RuleType.VULNERABILITY.name()))
-          .mustNot(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_CLOSED)))
-      .setSize(0);
-  }
-
-}
index dd13a01395a0098946d5b708f14b15eb3c9edfe4..d6e49f49a511f29539353afb6d8e706e132a790b 100644 (file)
  */
 package org.sonar.server.issue.index;
 
+import com.google.common.collect.ImmutableMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
 import org.sonar.server.es.NewIndex;
 
+import static java.util.Arrays.asList;
 import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.es.NewIndex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
 import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
@@ -95,6 +100,18 @@ public class IssueIndexDefinition implements IndexDefinition {
   public static final String FIELD_ISSUE_OWASP_TOP_10 = "owaspTop10";
   public static final String FIELD_ISSUE_SANS_TOP_25 = "sansTop25";
   public static final String FIELD_ISSUE_CWE = "cwe";
+  public static final String UNKNOWN_STANDARD = "unknown";
+  public static final String SANS_TOP_25_INSECURE_INTERACTION = "insecure-interaction";
+  public static final String SANS_TOP_25_RISKY_RESOURCE = "risky-resource";
+  public static final String SANS_TOP_25_POROUS_DEFENSES = "porous-defenses";
+  // See https://www.sans.org/top25-software-errors
+  private static final Set<String> INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601"));
+  private static final Set<String> RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190"));
+  private static final Set<String> POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759"));
+  static final Map<String, Set<String>> SANS_TOP_25_CWE_MAPPING = ImmutableMap.of(
+    SANS_TOP_25_INSECURE_INTERACTION, INSECURE_CWE,
+    SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE,
+    SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE);
 
   private final Configuration config;
   private final boolean enableSource;
index 549974561f83ea75766d8f37750b0259a966fcd2..eafd2aed3b00a083e31168f06d7bf9657a825c98 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.issue.index;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Maps;
 import java.sql.PreparedStatement;
@@ -29,10 +28,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import javax.annotation.CheckForNull;
@@ -46,14 +42,11 @@ import org.sonar.db.DbSession;
 import org.sonar.db.ResultSetIterator;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Arrays.asList;
 import static java.util.stream.Collectors.toList;
 import static org.sonar.api.utils.DateUtils.longToDate;
 import static org.sonar.db.DatabaseUtils.getLong;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_CWE_MAPPING;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
 
 /**
  * Scrolls over table ISSUES and reads documents to populate
@@ -106,15 +99,6 @@ class IssueIteratorForSingleChunk implements IssueIterator {
   private static final String OWASP_TOP10_PREFIX = "owaspTop10:";
   private static final String CWE_PREFIX = "cwe:";
 
-  // See https://www.sans.org/top25-software-errors
-  private static final Set<String> INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601"));
-  private static final Set<String> RISKY_CWE = new HashSet<>(asList("120", "22", "494", "829", "676", "131", "134", "190"));
-  private static final Set<String> POROUS_CWE = new HashSet<>(asList("306", "862", "798", "311", "807", "250", "863", "732", "327", "307", "759"));
-  private static final Map<String, Set<String>> SANS_TOP_25_CWE_MAPPING = ImmutableMap.of(
-    SANS_TOP_25_INSECURE_INTERACTION, INSECURE_CWE,
-    SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE,
-    SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE);
-
   private final DbSession session;
 
   @CheckForNull
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
deleted file mode 100644 (file)
index a759e03..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Multimap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.apache.lucene.search.join.ScoreMode;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
-import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
-import org.elasticsearch.search.aggregations.bucket.filter.Filter;
-import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregator.KeyedFilter;
-import org.elasticsearch.search.aggregations.bucket.nested.Nested;
-import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.terms.Terms;
-import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude;
-import org.elasticsearch.search.aggregations.metrics.sum.Sum;
-import org.elasticsearch.search.sort.FieldSortBuilder;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.server.es.DefaultIndexSettingsElement;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.StickyFacetBuilder;
-import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Collections.emptyList;
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
-import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
-import static org.elasticsearch.search.aggregations.AggregationBuilders.sum;
-import static org.elasticsearch.search.sort.SortOrder.ASC;
-import static org.elasticsearch.search.sort.SortOrder.DESC;
-import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
-import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
-import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
-import static org.sonar.server.es.EsUtils.termsToMap;
-import static org.sonar.server.measure.index.ProjectMeasuresDoc.QUALITY_GATE_STATUS;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NCLOC_LANGUAGE_DISTRIBUTION;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ORGANIZATION_UUID;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE_STATUS;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
-import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
-import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
-import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
-
-@ServerSide
-@ComputeEngineSide
-public class ProjectMeasuresIndex {
-
-  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
-    NCLOC_KEY,
-    NEW_LINES_KEY,
-    DUPLICATED_LINES_DENSITY_KEY,
-    NEW_DUPLICATED_LINES_DENSITY_KEY,
-    COVERAGE_KEY,
-    NEW_COVERAGE_KEY,
-    SQALE_RATING_KEY,
-    NEW_MAINTAINABILITY_RATING_KEY,
-    RELIABILITY_RATING_KEY,
-    NEW_RELIABILITY_RATING_KEY,
-    SECURITY_RATING_KEY,
-    NEW_SECURITY_RATING_KEY,
-    ALERT_STATUS_KEY,
-    FILTER_LANGUAGES,
-    FILTER_TAGS);
-
-  private static final Double[] LINES_THRESHOLDS = new Double[] {1_000d, 10_000d, 100_000d, 500_000d};
-  private static final Double[] COVERAGE_THRESHOLDS = new Double[] {30d, 50d, 70d, 80d};
-  private static final Double[] DUPLICATIONS_THRESHOLDS = new Double[] {3d, 5d, 10d, 20d};
-
-  private static final String FIELD_MEASURES_KEY = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
-  private static final String FIELD_MEASURES_VALUE = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
-  private static final String FIELD_DISTRIB_LANGUAGE = FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "." + ProjectMeasuresIndexDefinition.FIELD_DISTRIB_LANGUAGE;
-  private static final String FIELD_DISTRIB_NCLOC = FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "." + ProjectMeasuresIndexDefinition.FIELD_DISTRIB_NCLOC;
-
-  private static final Map<String, FacetSetter> FACET_FACTORIES = ImmutableMap.<String, FacetSetter>builder()
-    .put(NCLOC_KEY, (esSearch, query, facetBuilder) -> addRangeFacet(esSearch, NCLOC_KEY, facetBuilder, LINES_THRESHOLDS))
-    .put(NEW_LINES_KEY, (esSearch, query, facetBuilder) -> addRangeFacet(esSearch, NEW_LINES_KEY, facetBuilder, LINES_THRESHOLDS))
-    .put(DUPLICATED_LINES_DENSITY_KEY,
-      (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, DUPLICATED_LINES_DENSITY_KEY, facetBuilder, DUPLICATIONS_THRESHOLDS))
-    .put(NEW_DUPLICATED_LINES_DENSITY_KEY,
-      (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, NEW_DUPLICATED_LINES_DENSITY_KEY, facetBuilder, DUPLICATIONS_THRESHOLDS))
-    .put(COVERAGE_KEY, (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, COVERAGE_KEY, facetBuilder, COVERAGE_THRESHOLDS))
-    .put(NEW_COVERAGE_KEY, (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, NEW_COVERAGE_KEY, facetBuilder, COVERAGE_THRESHOLDS))
-    .put(SQALE_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, SQALE_RATING_KEY, facetBuilder))
-    .put(NEW_MAINTAINABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_MAINTAINABILITY_RATING_KEY, facetBuilder))
-    .put(RELIABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, RELIABILITY_RATING_KEY, facetBuilder))
-    .put(NEW_RELIABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_RELIABILITY_RATING_KEY, facetBuilder))
-    .put(SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, SECURITY_RATING_KEY, facetBuilder))
-    .put(NEW_SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_SECURITY_RATING_KEY, facetBuilder))
-    .put(ALERT_STATUS_KEY, (esSearch, query, facetBuilder) -> esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, facetBuilder, createQualityGateFacet())))
-    .put(FILTER_LANGUAGES, ProjectMeasuresIndex::addLanguagesFacet)
-    .put(FIELD_TAGS, ProjectMeasuresIndex::addTagsFacet)
-    .build();
-
-  private final EsClient client;
-  private final AuthorizationTypeSupport authorizationTypeSupport;
-  private final System2 system2;
-
-  public ProjectMeasuresIndex(EsClient client, AuthorizationTypeSupport authorizationTypeSupport, System2 system2) {
-    this.client = client;
-    this.authorizationTypeSupport = authorizationTypeSupport;
-    this.system2 = system2;
-  }
-
-  public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
-    SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
-      .setFetchSource(false)
-      .setFrom(searchOptions.getOffset())
-      .setSize(searchOptions.getLimit());
-
-    BoolQueryBuilder esFilter = boolQuery();
-    Map<String, QueryBuilder> filters = createFilters(query);
-    filters.values().forEach(esFilter::must);
-    requestBuilder.setQuery(esFilter);
-
-    addFacets(requestBuilder, searchOptions, filters, query);
-    addSort(query, requestBuilder);
-    return new SearchIdResult<>(requestBuilder.get(), id -> id, system2.getDefaultTimeZone());
-  }
-
-  public ProjectMeasuresStatistics searchTelemetryStatistics() {
-    SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
-      .setFetchSource(false)
-      .setSize(0);
-
-    BoolQueryBuilder esFilter = boolQuery();
-    request.setQuery(esFilter);
-    request.addAggregation(AggregationBuilders.terms(FIELD_LANGUAGES)
-      .field(FIELD_LANGUAGES)
-      .size(MAX_PAGE_SIZE)
-      .minDocCount(1)
-      .order(Terms.Order.count(false)));
-    request.addAggregation(AggregationBuilders.nested(FIELD_NCLOC_LANGUAGE_DISTRIBUTION, FIELD_NCLOC_LANGUAGE_DISTRIBUTION)
-      .subAggregation(AggregationBuilders.terms(FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "_terms")
-        .field(FIELD_DISTRIB_LANGUAGE)
-        .size(MAX_PAGE_SIZE)
-        .minDocCount(1)
-        .order(Terms.Order.count(false))
-        .subAggregation(sum(FIELD_DISTRIB_NCLOC).field(FIELD_DISTRIB_NCLOC))));
-
-    request.addAggregation(AggregationBuilders.nested(NCLOC_KEY, FIELD_MEASURES)
-      .subAggregation(AggregationBuilders.filter(NCLOC_KEY + "_filter", termQuery(FIELD_MEASURES_KEY, NCLOC_KEY))
-        .subAggregation(sum(NCLOC_KEY + "_filter_sum").field(FIELD_MEASURES_VALUE))));
-
-    ProjectMeasuresStatistics.Builder statistics = ProjectMeasuresStatistics.builder();
-
-    SearchResponse response = request.get();
-    statistics.setProjectCount(response.getHits().getTotalHits());
-    Stream.of(NCLOC_KEY)
-      .map(metric -> (Nested) response.getAggregations().get(metric))
-      .map(nested -> (Filter) nested.getAggregations().get(nested.getName() + "_filter"))
-      .map(filter -> (Sum) filter.getAggregations().get(filter.getName() + "_sum"))
-      .forEach(sum -> {
-        String metric = sum.getName().replace("_filter_sum", "");
-        long value = Math.round(sum.getValue());
-        statistics.setSum(metric, value);
-      });
-    statistics.setProjectCountByLanguage(termsToMap(response.getAggregations().get(FIELD_LANGUAGES)));
-    Function<Terms.Bucket, Long> bucketToNcloc = bucket -> Math.round(((Sum) bucket.getAggregations().get(FIELD_DISTRIB_NCLOC)).getValue());
-    Map<String, Long> nclocByLanguage = Stream.of((Nested) response.getAggregations().get(FIELD_NCLOC_LANGUAGE_DISTRIBUTION))
-      .map(nested -> (Terms) nested.getAggregations().get(nested.getName() + "_terms"))
-      .flatMap(terms -> terms.getBuckets().stream())
-      .collect(MoreCollectors.uniqueIndex(Bucket::getKeyAsString, bucketToNcloc));
-    statistics.setNclocByLanguage(nclocByLanguage);
-
-    return statistics.build();
-  }
-
-  private static void addSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder) {
-    String sort = query.getSort();
-    if (SORT_BY_NAME.equals(sort)) {
-      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), query.isAsc() ? ASC : DESC);
-    } else if (SORT_BY_LAST_ANALYSIS_DATE.equals(sort)) {
-      requestBuilder.addSort(FIELD_ANALYSED_AT, query.isAsc() ? ASC : DESC);
-    } else if (ALERT_STATUS_KEY.equals(sort)) {
-      requestBuilder.addSort(FIELD_QUALITY_GATE_STATUS, query.isAsc() ? ASC : DESC);
-      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
-    } else {
-      addMetricSort(query, requestBuilder, sort);
-      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
-    }
-    // last sort is by key in order to be deterministic when same value
-    requestBuilder.addSort(FIELD_KEY, ASC);
-  }
-
-  private static void addMetricSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder, String sort) {
-    requestBuilder.addSort(
-      new FieldSortBuilder(FIELD_MEASURES_VALUE)
-        .setNestedPath(FIELD_MEASURES)
-        .setNestedFilter(termQuery(FIELD_MEASURES_KEY, sort))
-        .order(query.isAsc() ? ASC : DESC));
-  }
-
-  private static void addRangeFacet(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder, Double... thresholds) {
-    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder, createRangeFacet(metricKey, thresholds)));
-  }
-
-  private static void addRangeFacetIncludingNoData(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder, Double... thresholds) {
-    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder,
-      AggregationBuilders.filter("combined_" + metricKey, matchAllQuery())
-        .subAggregation(createRangeFacet(metricKey, thresholds))
-        .subAggregation(createNoDataFacet(metricKey))));
-  }
-
-  private static void addRatingFacet(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder) {
-    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder, createRatingFacet(metricKey)));
-  }
-
-  private static void addLanguagesFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder) {
-    esSearch.addAggregation(facetBuilder.buildStickyFacet(FIELD_LANGUAGES, FILTER_LANGUAGES, query.getLanguages().map(Set::toArray).orElseGet(() -> new Object[] {})));
-  }
-
-  private static void addTagsFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder) {
-    esSearch.addAggregation(facetBuilder.buildStickyFacet(FIELD_TAGS, FILTER_TAGS, query.getTags().map(Set::toArray).orElseGet(() -> new Object[] {})));
-  }
-
-  private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options, Map<String, QueryBuilder> filters, ProjectMeasuresQuery query) {
-    StickyFacetBuilder facetBuilder = new StickyFacetBuilder(matchAllQuery(), filters);
-    options.getFacets().stream()
-      .filter(FACET_FACTORIES::containsKey)
-      .map(FACET_FACTORIES::get)
-      .forEach(factory -> factory.addFacet(esSearch, query, facetBuilder));
-  }
-
-  private static AbstractAggregationBuilder createStickyFacet(String facetKey, StickyFacetBuilder facetBuilder, AbstractAggregationBuilder aggregationBuilder) {
-    BoolQueryBuilder facetFilter = facetBuilder.getStickyFacetFilter(facetKey);
-    return AggregationBuilders
-      .global(facetKey)
-      .subAggregation(
-        AggregationBuilders
-          .filter("facet_filter_" + facetKey, facetFilter)
-          .subAggregation(aggregationBuilder));
-  }
-
-  private static AbstractAggregationBuilder createRangeFacet(String metricKey, Double... thresholds) {
-    RangeAggregationBuilder rangeAgg = AggregationBuilders.range(metricKey)
-      .field(FIELD_MEASURES_VALUE);
-    final int lastIndex = thresholds.length - 1;
-    IntStream.range(0, thresholds.length)
-      .forEach(i -> {
-        if (i == 0) {
-          rangeAgg.addUnboundedTo(thresholds[0]);
-          rangeAgg.addRange(thresholds[0], thresholds[1]);
-        } else if (i == lastIndex) {
-          rangeAgg.addUnboundedFrom(thresholds[lastIndex]);
-        } else {
-          rangeAgg.addRange(thresholds[i], thresholds[i + 1]);
-        }
-      });
-
-    return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES)
-      .subAggregation(
-        AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_KEY, metricKey))
-          .subAggregation(rangeAgg));
-  }
-
-  private static AbstractAggregationBuilder createNoDataFacet(String metricKey) {
-    return AggregationBuilders.filter(
-      "no_data_" + metricKey,
-      boolQuery().mustNot(nestedQuery(FIELD_MEASURES, termQuery(FIELD_MEASURES_KEY, metricKey), ScoreMode.Avg)));
-  }
-
-  private static AbstractAggregationBuilder createRatingFacet(String metricKey) {
-    return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES)
-      .subAggregation(
-        AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_KEY, metricKey))
-          .subAggregation(filters(metricKey,
-            new KeyedFilter("1", termQuery(FIELD_MEASURES_VALUE, 1d)),
-            new KeyedFilter("2", termQuery(FIELD_MEASURES_VALUE, 2d)),
-            new KeyedFilter("3", termQuery(FIELD_MEASURES_VALUE, 3d)),
-            new KeyedFilter("4", termQuery(FIELD_MEASURES_VALUE, 4d)),
-            new KeyedFilter("5", termQuery(FIELD_MEASURES_VALUE, 5d)))));
-  }
-
-  private static AbstractAggregationBuilder createQualityGateFacet() {
-    return AggregationBuilders.filters(
-      ALERT_STATUS_KEY,
-      QUALITY_GATE_STATUS.entrySet().stream()
-        .map(entry -> new KeyedFilter(entry.getKey(), termQuery(FIELD_QUALITY_GATE_STATUS, entry.getValue())))
-        .toArray(KeyedFilter[]::new));
-  }
-
-  private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
-    Map<String, QueryBuilder> filters = new HashMap<>();
-    filters.put("__authorization", authorizationTypeSupport.createQueryFilter());
-    Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
-    query.getMetricCriteria().forEach(metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
-    metricCriterionMultimap.asMap().forEach((key, value) -> {
-      BoolQueryBuilder metricFilters = boolQuery();
-      value
-        .stream()
-        .map(ProjectMeasuresIndex::toQuery)
-        .forEach(metricFilters::must);
-      filters.put(key, metricFilters);
-    });
-
-    query.getQualityGateStatus()
-      .ifPresent(qualityGateStatus -> filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE_STATUS, QUALITY_GATE_STATUS.get(qualityGateStatus.name()))));
-
-    query.getProjectUuids()
-      .ifPresent(projectUuids -> filters.put("ids", termsQuery("_id", projectUuids)));
-
-    query.getLanguages()
-      .ifPresent(languages -> filters.put(FILTER_LANGUAGES, termsQuery(FIELD_LANGUAGES, languages)));
-
-    query.getOrganizationUuid()
-      .ifPresent(organizationUuid -> filters.put(FIELD_ORGANIZATION_UUID, termQuery(FIELD_ORGANIZATION_UUID, organizationUuid)));
-
-    query.getTags()
-      .ifPresent(tags -> filters.put(FIELD_TAGS, termsQuery(FIELD_TAGS, tags)));
-
-    query.getQueryText()
-      .map(ProjectsTextSearchQueryFactory::createQuery)
-      .ifPresent(queryBuilder -> filters.put("textQuery", queryBuilder));
-    return filters;
-  }
-
-  private static QueryBuilder toQuery(MetricCriterion criterion) {
-    if (criterion.isNoData()) {
-      return boolQuery().mustNot(
-        nestedQuery(
-          FIELD_MEASURES,
-          termQuery(FIELD_MEASURES_KEY, criterion.getMetricKey()),
-          ScoreMode.Avg));
-    }
-    return nestedQuery(
-      FIELD_MEASURES,
-      boolQuery()
-        .filter(termQuery(FIELD_MEASURES_KEY, criterion.getMetricKey()))
-        .filter(toValueQuery(criterion)),
-      ScoreMode.Avg);
-  }
-
-  private static QueryBuilder toValueQuery(MetricCriterion criterion) {
-    String fieldName = FIELD_MEASURES_VALUE;
-
-    switch (criterion.getOperator()) {
-      case GT:
-        return rangeQuery(fieldName).gt(criterion.getValue());
-      case GTE:
-        return rangeQuery(fieldName).gte(criterion.getValue());
-      case LT:
-        return rangeQuery(fieldName).lt(criterion.getValue());
-      case LTE:
-        return rangeQuery(fieldName).lte(criterion.getValue());
-      case EQ:
-        return termQuery(fieldName, criterion.getValue());
-      default:
-        throw new IllegalStateException("Metric criteria non supported: " + criterion.getOperator().name());
-    }
-  }
-
-  public List<String> searchTags(@Nullable String textQuery, int size) {
-    int maxPageSize = 500;
-    checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize);
-    if (size <= 0) {
-      return emptyList();
-    }
-
-    TermsAggregationBuilder tagFacet = AggregationBuilders.terms(FIELD_TAGS)
-      .field(FIELD_TAGS)
-      .size(size)
-      .minDocCount(1)
-      .order(Terms.Order.term(true));
-    if (textQuery != null) {
-      tagFacet.includeExclude(new IncludeExclude(".*" + escapeSpecialRegexChars(textQuery) + ".*", null));
-    }
-
-    SearchRequestBuilder searchQuery = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
-      .setQuery(authorizationTypeSupport.createQueryFilter())
-      .setFetchSource(false)
-      .setSize(0)
-      .addAggregation(tagFacet);
-
-    Terms aggregation = searchQuery.get().getAggregations().get(FIELD_TAGS);
-
-    return aggregation.getBuckets().stream()
-      .map(Bucket::getKeyAsString)
-      .collect(MoreCollectors.toList());
-  }
-
-  @FunctionalInterface
-  private interface FacetSetter {
-    void addFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder);
-  }
-
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
deleted file mode 100644 (file)
index 17494bc..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.sonar.api.measures.Metric;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.util.Arrays.stream;
-import static java.util.Objects.requireNonNull;
-
-public class ProjectMeasuresQuery {
-
-  public static final String SORT_BY_NAME = "name";
-  public static final String SORT_BY_LAST_ANALYSIS_DATE = "analysisDate";
-
-  private List<MetricCriterion> metricCriteria = new ArrayList<>();
-  private Metric.Level qualityGateStatus;
-  private String organizationUuid;
-  private Set<String> projectUuids;
-  private Set<String> languages;
-  private Set<String> tags;
-  private String sort = SORT_BY_NAME;
-  private boolean asc = true;
-  private String queryText;
-
-  public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
-    this.metricCriteria.add(metricCriterion);
-    return this;
-  }
-
-  public List<MetricCriterion> getMetricCriteria() {
-    return metricCriteria;
-  }
-
-  public ProjectMeasuresQuery setQualityGateStatus(Metric.Level qualityGateStatus) {
-    this.qualityGateStatus = requireNonNull(qualityGateStatus);
-    return this;
-  }
-
-  public Optional<Metric.Level> getQualityGateStatus() {
-    return Optional.ofNullable(qualityGateStatus);
-  }
-
-  public ProjectMeasuresQuery setOrganizationUuid(@Nullable String organizationUuid) {
-    this.organizationUuid = organizationUuid;
-    return this;
-  }
-
-  public Optional<String> getOrganizationUuid() {
-    return Optional.ofNullable(organizationUuid);
-  }
-
-  public ProjectMeasuresQuery setProjectUuids(@Nullable Set<String> projectUuids) {
-    this.projectUuids = projectUuids;
-    return this;
-  }
-
-  public Optional<Set<String>> getProjectUuids() {
-    return Optional.ofNullable(projectUuids);
-  }
-
-  public ProjectMeasuresQuery setLanguages(@Nullable Set<String> languages) {
-    this.languages = languages;
-    return this;
-  }
-
-  public Optional<Set<String>> getLanguages() {
-    return Optional.ofNullable(languages);
-  }
-
-  public ProjectMeasuresQuery setTags(@Nullable Set<String> tags) {
-    this.tags = tags;
-    return this;
-  }
-
-  public Optional<Set<String>> getTags() {
-    return Optional.ofNullable(tags);
-  }
-
-  public Optional<String> getQueryText() {
-    return Optional.ofNullable(queryText);
-  }
-
-  public ProjectMeasuresQuery setQueryText(@Nullable String queryText) {
-    this.queryText = queryText;
-    return this;
-  }
-
-  public String getSort() {
-    return sort;
-  }
-
-  public ProjectMeasuresQuery setSort(String sort) {
-    this.sort = requireNonNull(sort, "Sort cannot be null");
-    return this;
-  }
-
-  public boolean isAsc() {
-    return asc;
-  }
-
-  public ProjectMeasuresQuery setAsc(boolean asc) {
-    this.asc = asc;
-    return this;
-  }
-
-  public static class MetricCriterion {
-    private final String metricKey;
-    private final Operator operator;
-    @Nullable
-    private final Double value;
-
-    private MetricCriterion(String metricKey, @Nullable Operator operator, @Nullable Double value) {
-      this.metricKey = metricKey;
-      this.operator = operator;
-      this.value = value;
-    }
-
-    public String getMetricKey() {
-      return metricKey;
-    }
-
-    public Operator getOperator() {
-      checkDataAvailable();
-      return operator;
-    }
-
-    public double getValue() {
-      checkDataAvailable();
-      return value;
-    }
-
-    public boolean isNoData() {
-      return value == null;
-    }
-
-    public static MetricCriterion createNoData(String metricKey) {
-      return new MetricCriterion(requireNonNull(metricKey), null, null);
-    }
-
-    public static MetricCriterion create(String metricKey, Operator operator, double value) {
-      return new MetricCriterion(requireNonNull(metricKey), requireNonNull(operator), value);
-    }
-
-    private void checkDataAvailable() {
-      checkState(!isNoData(), "The criterion for metric %s has no data", metricKey);
-    }
-  }
-
-  public enum Operator {
-    LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("="), IN("in");
-
-    String value;
-
-    Operator(String value) {
-      this.value = value;
-    }
-
-    public String getValue() {
-      return value;
-    }
-
-    public static Operator getByValue(String value) {
-      return stream(Operator.values())
-        .filter(operator -> operator.getValue().equalsIgnoreCase(value))
-        .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException(format("Unknown operator '%s'", value)));
-    }
-  }
-
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java
deleted file mode 100644 (file)
index bf6cc6c..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import org.sonar.core.platform.Module;
-
-public class ProjectsEsModule extends Module {
-  @Override
-  protected void configureModule() {
-    add(
-      ProjectMeasuresIndexDefinition.class,
-      ProjectMeasuresIndex.class,
-      ProjectMeasuresIndexer.class);
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java
deleted file mode 100644 (file)
index 7145679..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Stream;
-import org.apache.commons.lang.StringUtils;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.MatchQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.sonar.server.es.DefaultIndexSettings;
-
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
-import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
-
-/**
- * This class is used in order to do some advanced full text search on projects key and name
- */
-class ProjectsTextSearchQueryFactory {
-
-  private ProjectsTextSearchQueryFactory() {
-    // Only static methods
-  }
-
-  static QueryBuilder createQuery(String queryText) {
-    BoolQueryBuilder featureQuery = boolQuery();
-    Arrays.stream(ComponentTextSearchFeature.values())
-      .map(f -> f.getQuery(queryText))
-      .forEach(featureQuery::should);
-    return featureQuery;
-  }
-
-  private enum ComponentTextSearchFeature {
-
-    EXACT_IGNORE_CASE {
-      @Override
-      QueryBuilder getQuery(String queryText) {
-        return matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), queryText)
-          .boost(2.5f);
-      }
-    },
-    PREFIX {
-      @Override
-      QueryBuilder getQuery(String queryText) {
-        return prefixAndPartialQuery(queryText, FIELD_NAME, FIELD_NAME)
-          .boost(2f);
-      }
-    },
-    PREFIX_IGNORE_CASE {
-      @Override
-      QueryBuilder getQuery(String queryText) {
-        String lowerCaseQueryText = queryText.toLowerCase(Locale.ENGLISH);
-        return prefixAndPartialQuery(lowerCaseQueryText, SORTABLE_ANALYZER.subField(FIELD_NAME), FIELD_NAME)
-          .boost(3f);
-      }
-    },
-    PARTIAL {
-      @Override
-      QueryBuilder getQuery(String queryText) {
-        BoolQueryBuilder queryBuilder = boolQuery();
-        split(queryText)
-          .map(text -> partialTermQuery(text, FIELD_NAME))
-          .forEach(queryBuilder::must);
-        return queryBuilder
-          .boost(0.5f);
-      }
-    },
-    KEY {
-      @Override
-      QueryBuilder getQuery(String queryText) {
-        return matchQuery(SORTABLE_ANALYZER.subField(FIELD_KEY), queryText)
-          .boost(50f);
-      }
-    };
-
-    abstract QueryBuilder getQuery(String queryText);
-
-    protected Stream<String> split(String queryText) {
-      return Arrays.stream(
-        queryText.split(DefaultIndexSettings.SEARCH_TERM_TOKENIZER_PATTERN))
-        .filter(StringUtils::isNotEmpty);
-    }
-
-    protected BoolQueryBuilder prefixAndPartialQuery(String queryText, String fieldName, String originalFieldName) {
-      BoolQueryBuilder queryBuilder = boolQuery();
-      AtomicBoolean first = new AtomicBoolean(true);
-      split(queryText)
-        .map(queryTerm -> {
-          if (first.getAndSet(false)) {
-            return prefixQuery(fieldName, queryTerm);
-          }
-          return partialTermQuery(queryTerm, originalFieldName);
-        })
-        .forEach(queryBuilder::must);
-      return queryBuilder;
-    }
-
-    protected MatchQueryBuilder partialTermQuery(String queryTerm, String fieldName) {
-      // We will truncate the search to the maximum length of nGrams in the index.
-      // Otherwise the search would for sure not find any results.
-      String truncatedQuery = StringUtils.left(queryTerm, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH);
-      return matchQuery(SEARCH_GRAMS_ANALYZER.subField(fieldName), truncatedQuery);
-    }
-  }
-}
index a4f28e6a3324651f61fbf0b32280c1147bb57fb9..efec4949f82045b2e04b1eac572ff0d0de0325a7 100644 (file)
@@ -23,18 +23,30 @@ import java.util.function.Predicate;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.server.es.IndexType;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 @Immutable
 public final class AuthorizationScope {
   private final IndexType indexType;
-  private final Predicate<PermissionIndexerDao.Dto> projectPredicate;
+  private final Predicate<IndexPermissions> projectPredicate;
 
-  public AuthorizationScope(IndexType indexType, Predicate<PermissionIndexerDao.Dto> projectPredicate) {
-    this.indexType = AuthorizationTypeSupport.getAuthorizationIndexType(indexType);
+  public AuthorizationScope(IndexType indexType, Predicate<IndexPermissions> projectPredicate) {
+    this.indexType = getAuthorizationIndexType(indexType);
     this.projectPredicate = requireNonNull(projectPredicate);
   }
 
+  /**
+   * @return the identifier of the ElasticSearch type (including it's index name), that corresponds to a certain document type
+   */
+  private static IndexType getAuthorizationIndexType(IndexType indexType) {
+    requireNonNull(indexType);
+    requireNonNull(indexType.getIndex());
+    checkArgument(!TYPE_AUTHORIZATION.equals(indexType.getType()), "Authorization types do not have authorization on their own.");
+    return new IndexType(indexType.getIndex(), TYPE_AUTHORIZATION);
+  }
+
   /**
    * Identifier of the authorization type (in the same index than the original IndexType, passed into the constructor).
    */
@@ -43,10 +55,9 @@ public final class AuthorizationScope {
   }
 
   /**
-   * Predicates that filters the projects to be involved in
-   * authorization.
+   * Predicates that filters the projects to be involved in authorization.
    */
-  public Predicate<PermissionIndexerDao.Dto> getProjectPredicate() {
+  public Predicate<IndexPermissions> getProjectPredicate() {
     return projectPredicate;
   }
 }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java
deleted file mode 100644 (file)
index 3abdfae..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Optional;
-import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.join.query.JoinQueryBuilders;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.user.GroupDto;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
-import org.sonar.server.user.UserSession;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Objects.requireNonNull;
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-
-@ServerSide
-@ComputeEngineSide
-public class AuthorizationTypeSupport {
-
-  public static final String TYPE_AUTHORIZATION = "authorization";
-  public static final String FIELD_GROUP_IDS = "groupIds";
-  public static final String FIELD_USER_IDS = "userIds";
-
-  /**
-   * When true, then anybody can access to the project. In that case
-   * it's useless to store granted groups and users. The related
-   * fields are empty.
-   */
-  public static final String FIELD_ALLOW_ANYONE = "allowAnyone";
-
-  private final UserSession userSession;
-
-  public AuthorizationTypeSupport(UserSession userSession) {
-    this.userSession = userSession;
-  }
-
-  /**
-   * @return the identifier of the ElasticSearch type (including it's index name), that corresponds to a certain document type
-   */
-  public static IndexType getAuthorizationIndexType(IndexType indexType) {
-    requireNonNull(indexType);
-    requireNonNull(indexType.getIndex());
-    checkArgument(!AuthorizationTypeSupport.TYPE_AUTHORIZATION.equals(indexType.getType()), "Authorization types do not have authorization on their own.");
-    return new IndexType(indexType.getIndex(), AuthorizationTypeSupport.TYPE_AUTHORIZATION);
-  }
-
-  /**
-   * Creates a type that requires to verify that user has the read permission
-   * when searching for documents.
-   * It relies on a parent type named "authorization" that is automatically
-   * populated by {@link org.sonar.server.permission.index.PermissionIndexer}.
-   *
-   * Both types {@code typeName} and "authorization" are created. Documents
-   * must be created with _parent and _routing having the parent uuid as values.
-   *
-   * @see NewIndex.NewIndexType#requireProjectAuthorization()
-   */
-  public static NewIndex.NewIndexType enableProjectAuthorization(NewIndex.NewIndexType type) {
-    type.setAttribute("_parent", ImmutableMap.of("type", TYPE_AUTHORIZATION));
-    type.setAttribute("_routing", ImmutableMap.of("required", true));
-
-    NewIndex.NewIndexType authType = type.getIndex().createType(TYPE_AUTHORIZATION);
-    authType.setAttribute("_routing", ImmutableMap.of("required", true));
-    authType.createLongField(FIELD_GROUP_IDS);
-    authType.createLongField(FIELD_USER_IDS);
-    authType.createBooleanField(FIELD_ALLOW_ANYONE);
-    authType.setEnableSource(false);
-    return type;
-  }
-
-  /**
-   * Build a filter to restrict query to the documents on which
-   * user has read access.
-   */
-  public QueryBuilder createQueryFilter() {
-    if (userSession.isRoot()) {
-      return QueryBuilders.matchAllQuery();
-    }
-
-    Integer userId = userSession.getUserId();
-    BoolQueryBuilder filter = boolQuery();
-
-    // anyone
-    filter.should(QueryBuilders.termQuery(FIELD_ALLOW_ANYONE, true));
-
-    // users
-    Optional.ofNullable(userId)
-      .map(Integer::longValue)
-      .ifPresent(id -> filter.should(termQuery(FIELD_USER_IDS, id)));
-
-    // groups
-    userSession.getGroups()
-      .stream()
-      .map(GroupDto::getId)
-      .forEach(groupId -> filter.should(termQuery(FIELD_GROUP_IDS, groupId)));
-
-    return JoinQueryBuilders.hasParentQuery(
-      TYPE_AUTHORIZATION,
-      QueryBuilders.boolQuery().filter(filter),
-      false);
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexAuthorizationConstants.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexAuthorizationConstants.java
new file mode 100644 (file)
index 0000000..416f66f
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+public final class IndexAuthorizationConstants {
+  public static final String TYPE_AUTHORIZATION = "authorization";
+  public static final String FIELD_GROUP_IDS = "groupIds";
+  public static final String FIELD_USER_IDS = "userIds";
+  /**
+   * When true, then anybody can access to the project. In that case
+   * it's useless to store granted groups and users. The related
+   * fields are empty.
+   */
+  public static final String FIELD_ALLOW_ANYONE = "allowAnyone";
+
+  private IndexAuthorizationConstants() {
+    // prevents instantiation
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexPermissions.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/IndexPermissions.java
new file mode 100644 (file)
index 0000000..effa29b
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class IndexPermissions {
+  private final String projectUuid;
+  private final String qualifier;
+  private final List<Integer> userIds = new ArrayList<>();
+  private final List<Integer> groupIds = new ArrayList<>();
+  private boolean allowAnyone = false;
+
+  public IndexPermissions(String projectUuid, String qualifier) {
+    this.projectUuid = projectUuid;
+    this.qualifier = qualifier;
+  }
+
+  public String getProjectUuid() {
+    return projectUuid;
+  }
+
+  public String getQualifier() {
+    return qualifier;
+  }
+
+  public List<Integer> getUserIds() {
+    return userIds;
+  }
+
+  public IndexPermissions addUserId(int l) {
+    userIds.add(l);
+    return this;
+  }
+
+  public IndexPermissions addGroupId(int id) {
+    groupIds.add(id);
+    return this;
+  }
+
+  public List<Integer> getGroupIds() {
+    return groupIds;
+  }
+
+  public void allowAnyone() {
+    this.allowAnyone = true;
+  }
+
+  public boolean isAllowAnyone() {
+    return allowAnyone;
+  }
+}
index a240fcc3580a41509e63215172e07da1793d28e2..0ce996c34f5097818c9d0bf5581ca7e708943072 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.server.permission.index;
 /**
  * An {@link NeedAuthorizationIndexer} defines how
  * a {@link org.sonar.server.es.ProjectIndexer} populates
- * the type named {@link AuthorizationTypeSupport#TYPE_AUTHORIZATION}, which
+ * the type named {@link WebAuthorizationTypeSupport#TYPE_AUTHORIZATION}, which
  * is used to verify that a user can access to projects.
  */
 public interface NeedAuthorizationIndexer {
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
deleted file mode 100644 (file)
index 1a9ab35..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.elasticsearch.action.index.IndexRequest;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.server.es.BulkIndexer;
-import org.sonar.server.es.BulkIndexer.Size;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.OneToOneResilientIndexingListener;
-import org.sonar.server.es.ProjectIndexer;
-import org.sonar.server.permission.index.PermissionIndexerDao.Dto;
-
-import static java.util.Collections.emptyList;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-
-/**
- * Populates the types "authorization" of each index requiring project
- * authorization.
- */
-public class PermissionIndexer implements ProjectIndexer {
-
-  private final DbClient dbClient;
-  private final EsClient esClient;
-  private final Collection<AuthorizationScope> authorizationScopes;
-  private final Set<IndexType> indexTypes;
-
-  public PermissionIndexer(DbClient dbClient, EsClient esClient, NeedAuthorizationIndexer... needAuthorizationIndexers) {
-    this(dbClient, esClient, Arrays.stream(needAuthorizationIndexers)
-      .map(NeedAuthorizationIndexer::getAuthorizationScope)
-      .collect(MoreCollectors.toList(needAuthorizationIndexers.length)));
-  }
-
-  @VisibleForTesting
-  public PermissionIndexer(DbClient dbClient, EsClient esClient, Collection<AuthorizationScope> authorizationScopes) {
-    this.dbClient = dbClient;
-    this.esClient = esClient;
-    this.authorizationScopes = authorizationScopes;
-    this.indexTypes = authorizationScopes.stream()
-      .map(AuthorizationScope::getIndexType)
-      .collect(toSet(authorizationScopes.size()));
-  }
-
-  @Override
-  public Set<IndexType> getIndexTypes() {
-    return indexTypes;
-  }
-
-  @Override
-  public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
-    // TODO do not load everything in memory. Db rows should be scrolled.
-    List<Dto> authorizations = getAllAuthorizations();
-    Stream<AuthorizationScope> scopes = getScopes(uninitializedIndexTypes);
-    index(authorizations, scopes, Size.LARGE);
-  }
-
-  @VisibleForTesting
-  void index(List<Dto> authorizations) {
-    index(authorizations, authorizationScopes.stream(), Size.REGULAR);
-  }
-
-  @Override
-  public void indexOnAnalysis(String branchUuid) {
-    // nothing to do, permissions don't change during an analysis
-  }
-
-  @Override
-  public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
-    switch (cause) {
-      case MEASURE_CHANGE:
-      case PROJECT_KEY_UPDATE:
-      case PROJECT_TAGS_UPDATE:
-        // nothing to change. Measures, project key and tags are not part of this index
-        return emptyList();
-
-      case PROJECT_CREATION:
-      case PROJECT_DELETION:
-      case PERMISSION_CHANGE:
-        return insertIntoEsQueue(dbSession, projectUuids);
-
-      default:
-        // defensive case
-        throw new IllegalStateException("Unsupported cause: " + cause);
-    }
-  }
-
-  private Collection<EsQueueDto> insertIntoEsQueue(DbSession dbSession, Collection<String> projectUuids) {
-    List<EsQueueDto> items = indexTypes.stream()
-      .flatMap(indexType -> projectUuids.stream().map(projectUuid -> EsQueueDto.create(indexType.format(), projectUuid, null, projectUuid)))
-      .collect(toArrayList());
-
-    dbClient.esQueueDao().insert(dbSession, items);
-    return items;
-  }
-
-  private void index(Collection<PermissionIndexerDao.Dto> authorizations, Stream<AuthorizationScope> scopes, Size bulkSize) {
-    if (authorizations.isEmpty()) {
-      return;
-    }
-
-    // index each authorization in each scope
-    scopes.forEach(scope -> {
-      IndexType indexType = scope.getIndexType();
-
-      BulkIndexer bulkIndexer = new BulkIndexer(esClient, indexType, bulkSize);
-      bulkIndexer.start();
-
-      authorizations.stream()
-        .filter(scope.getProjectPredicate())
-        .map(dto -> newIndexRequest(dto, indexType))
-        .forEach(bulkIndexer::add);
-
-      bulkIndexer.stop();
-    });
-  }
-
-  @Override
-  public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
-    IndexingResult result = new IndexingResult();
-
-    List<BulkIndexer> bulkIndexers = items.stream()
-      .map(EsQueueDto::getDocType)
-      .distinct()
-      .map(IndexType::parse)
-      .filter(indexTypes::contains)
-      .map(indexType -> new BulkIndexer(esClient, indexType, Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items)))
-      .collect(Collectors.toList());
-
-    if (bulkIndexers.isEmpty()) {
-      return result;
-    }
-
-    bulkIndexers.forEach(BulkIndexer::start);
-
-    PermissionIndexerDao permissionIndexerDao = new PermissionIndexerDao();
-    Set<String> remainingProjectUuids = items.stream().map(EsQueueDto::getDocId).collect(MoreCollectors.toHashSet());
-    permissionIndexerDao.selectByUuids(dbClient, dbSession, remainingProjectUuids).forEach(p -> {
-      remainingProjectUuids.remove(p.getProjectUuid());
-      bulkIndexers.forEach(bi -> bi.add(newIndexRequest(p, bi.getIndexType())));
-    });
-
-    // the remaining references on projects that don't exist in db. They must
-    // be deleted from index.
-    remainingProjectUuids.forEach(projectUuid -> bulkIndexers.forEach(bi -> bi.addDeletion(bi.getIndexType(), projectUuid, projectUuid)));
-
-    bulkIndexers.forEach(b -> result.add(b.stop()));
-
-    return result;
-  }
-
-  private static IndexRequest newIndexRequest(PermissionIndexerDao.Dto dto, IndexType indexType) {
-    Map<String, Object> doc = new HashMap<>();
-    if (dto.isAllowAnyone()) {
-      doc.put(AuthorizationTypeSupport.FIELD_ALLOW_ANYONE, true);
-      // no need to feed users and groups
-    } else {
-      doc.put(AuthorizationTypeSupport.FIELD_ALLOW_ANYONE, false);
-      doc.put(AuthorizationTypeSupport.FIELD_GROUP_IDS, dto.getGroupIds());
-      doc.put(AuthorizationTypeSupport.FIELD_USER_IDS, dto.getUserIds());
-    }
-    return new IndexRequest(indexType.getIndex(), indexType.getType())
-      .id(dto.getProjectUuid())
-      .routing(dto.getProjectUuid())
-      .source(doc);
-  }
-
-  private Stream<AuthorizationScope> getScopes(Set<IndexType> indexTypes) {
-    return authorizationScopes.stream()
-      .filter(scope -> indexTypes.contains(scope.getIndexType()));
-  }
-
-  private List<Dto> getAllAuthorizations() {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      return new PermissionIndexerDao().selectAll(dbClient, dbSession);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java
deleted file mode 100644 (file)
index 5f73a4a..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import com.google.common.collect.ImmutableList;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-
-import static org.apache.commons.lang.StringUtils.repeat;
-import static org.sonar.db.DatabaseUtils.executeLargeInputs;
-
-/**
- * No streaming because of union of joins -> no need to use ResultSetIterator
- */
-public class PermissionIndexerDao {
-
-  public static final class Dto {
-    private final String projectUuid;
-    private final String qualifier;
-    private final List<Integer> userIds = new ArrayList<>();
-    private final List<Integer> groupIds = new ArrayList<>();
-    private boolean allowAnyone = false;
-
-    public Dto(String projectUuid, String qualifier) {
-      this.projectUuid = projectUuid;
-      this.qualifier = qualifier;
-    }
-
-    public String getProjectUuid() {
-      return projectUuid;
-    }
-
-    public String getQualifier() {
-      return qualifier;
-    }
-
-    public List<Integer> getUserIds() {
-      return userIds;
-    }
-
-    public Dto addUserId(int l) {
-      userIds.add(l);
-      return this;
-    }
-
-    public Dto addGroupId(int id) {
-      groupIds.add(id);
-      return this;
-    }
-
-    public List<Integer> getGroupIds() {
-      return groupIds;
-    }
-
-    public void allowAnyone() {
-      this.allowAnyone = true;
-    }
-
-    public boolean isAllowAnyone() {
-      return allowAnyone;
-    }
-  }
-
-  private enum RowKind {
-    USER, GROUP, ANYONE, NONE
-  }
-
-  private static final String SQL_TEMPLATE = "SELECT " +
-    "  project_authorization.kind as kind, " +
-    "  project_authorization.project as project, " +
-    "  project_authorization.user_id as user_id, " +
-    "  project_authorization.group_id as group_id, " +
-    "  project_authorization.qualifier as qualifier " +
-    "FROM ( " +
-
-    // users
-
-    "      SELECT '" + RowKind.USER + "' as kind," +
-    "      projects.uuid AS project, " +
-    "      projects.qualifier AS qualifier, " +
-    "      user_roles.user_id  AS user_id, " +
-    "      NULL  AS group_id " +
-    "      FROM projects " +
-    "      INNER JOIN user_roles ON user_roles.resource_id = projects.id AND user_roles.role = 'user' " +
-    "      WHERE " +
-    "        (projects.qualifier = 'TRK' " +
-    "         or  projects.qualifier = 'VW' " +
-    "         or  projects.qualifier = 'APP') " +
-    "        AND projects.copy_component_uuid is NULL " +
-    "        {projectsCondition} " +
-    "      UNION " +
-
-    // groups
-
-    "      SELECT '" + RowKind.GROUP + "' as kind," +
-    "      projects.uuid AS project, " +
-    "      projects.qualifier AS qualifier, " +
-    "      NULL  AS user_id, " +
-    "      groups.id  AS group_id " +
-    "      FROM projects " +
-    "      INNER JOIN group_roles ON group_roles.resource_id = projects.id AND group_roles.role = 'user' " +
-    "      INNER JOIN groups ON groups.id = group_roles.group_id " +
-    "      WHERE " +
-    "        (projects.qualifier = 'TRK' " +
-    "         or  projects.qualifier = 'VW' " +
-    "         or  projects.qualifier = 'APP') " +
-    "        AND projects.copy_component_uuid is NULL " +
-    "        {projectsCondition} " +
-    "        AND group_id IS NOT NULL " +
-    "      UNION " +
-
-    // public projects are accessible to any one
-
-    "      SELECT '" + RowKind.ANYONE + "' as kind," +
-    "      projects.uuid AS project, " +
-    "      projects.qualifier AS qualifier, " +
-    "      NULL         AS user_id, " +
-    "      NULL     AS group_id " +
-    "      FROM projects " +
-    "      WHERE " +
-    "        (projects.qualifier = 'TRK' " +
-    "         or  projects.qualifier = 'VW' " +
-    "         or  projects.qualifier = 'APP') " +
-    "        AND projects.copy_component_uuid is NULL " +
-    "        AND projects.private = ? " +
-    "        {projectsCondition} " +
-    "      UNION " +
-
-    // private project is returned when no authorization
-    "      SELECT '" + RowKind.NONE + "' as kind," +
-    "      projects.uuid AS project, " +
-    "      projects.qualifier AS qualifier, " +
-    "      NULL AS user_id, " +
-    "      NULL  AS group_id " +
-    "      FROM projects " +
-    "      WHERE " +
-    "        (projects.qualifier = 'TRK' " +
-    "         or  projects.qualifier = 'VW' " +
-    "         or  projects.qualifier = 'APP') " +
-    "        AND projects.copy_component_uuid is NULL " +
-    "        AND projects.private = ? " +
-    "        {projectsCondition} " +
-
-    "    ) project_authorization";
-
-  List<Dto> selectAll(DbClient dbClient, DbSession session) {
-    return doSelectByProjects(dbClient, session, Collections.emptyList());
-  }
-
-  List<Dto> selectByUuids(DbClient dbClient, DbSession session, Collection<String> projectOrViewUuids) {
-    return executeLargeInputs(projectOrViewUuids, subProjectOrViewUuids -> doSelectByProjects(dbClient, session, subProjectOrViewUuids));
-  }
-
-  private static List<Dto> doSelectByProjects(DbClient dbClient, DbSession session, List<String> projectUuids) {
-    try {
-      Map<String, Dto> dtosByProjectUuid = new HashMap<>();
-      try (PreparedStatement stmt = createStatement(dbClient, session, projectUuids);
-        ResultSet rs = stmt.executeQuery()) {
-        while (rs.next()) {
-          processRow(rs, dtosByProjectUuid);
-        }
-        return ImmutableList.copyOf(dtosByProjectUuid.values());
-      }
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to select authorizations", e);
-    }
-  }
-
-  private static PreparedStatement createStatement(DbClient dbClient, DbSession session, List<String> projectUuids) throws SQLException {
-    String sql;
-    if (projectUuids.isEmpty()) {
-      sql = StringUtils.replace(SQL_TEMPLATE, "{projectsCondition}", "");
-    } else {
-      sql = StringUtils.replace(SQL_TEMPLATE, "{projectsCondition}", " AND projects.uuid in (" + repeat("?", ", ", projectUuids.size()) + ")");
-    }
-    PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
-    int index = 1;
-    // query for RowKind.USER
-    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
-    // query for RowKind.GROUP
-    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
-    // query for RowKind.ANYONE
-    index = setPrivateProjectPlaceHolder(stmt, index, false);
-    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
-    // query for RowKind.NONE
-    index = setPrivateProjectPlaceHolder(stmt, index, true);
-    populateProjectUuidPlaceholders(stmt, projectUuids, index);
-    return stmt;
-  }
-
-  private static int populateProjectUuidPlaceholders(PreparedStatement stmt, List<String> projectUuids, int index) throws SQLException {
-    int newIndex = index;
-    for (String projectUuid : projectUuids) {
-      stmt.setString(newIndex, projectUuid);
-      newIndex++;
-    }
-    return newIndex;
-  }
-
-  private static int setPrivateProjectPlaceHolder(PreparedStatement stmt, int index, boolean isPrivate) throws SQLException {
-    int newIndex = index;
-    stmt.setBoolean(newIndex, isPrivate);
-    newIndex++;
-    return newIndex;
-  }
-
-  private static void processRow(ResultSet rs, Map<String, Dto> dtosByProjectUuid) throws SQLException {
-    RowKind rowKind = RowKind.valueOf(rs.getString(1));
-    String projectUuid = rs.getString(2);
-
-    Dto dto = dtosByProjectUuid.get(projectUuid);
-    if (dto == null) {
-      String qualifier = rs.getString(5);
-      dto = new Dto(projectUuid, qualifier);
-      dtosByProjectUuid.put(projectUuid, dto);
-    }
-    switch (rowKind) {
-      case NONE:
-        break;
-      case USER:
-        dto.addUserId(rs.getInt(3));
-        break;
-      case GROUP:
-        dto.addGroupId(rs.getInt(4));
-        break;
-      case ANYONE:
-        dto.allowAnyone();
-        break;
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/user/BaseUserSession.java b/server/sonar-server-common/src/main/java/org/sonar/server/user/BaseUserSession.java
deleted file mode 100644 (file)
index 51c591c..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.user;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import org.sonar.core.permission.ProjectPermissions;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
-
-import static org.apache.commons.lang.StringUtils.defaultString;
-
-public abstract class BaseUserSession implements UserSession {
-  @Override
-  public final boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) {
-    return hasPermission(permission, organization.getUuid());
-  }
-
-  @Override
-  public final boolean hasPermission(OrganizationPermission permission, String organizationUuid) {
-    return isRoot() || hasPermissionImpl(permission, organizationUuid);
-  }
-
-  protected abstract boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid);
-
-  @Override
-  public final boolean hasComponentPermission(String permission, ComponentDto component) {
-    if (isRoot()) {
-      return true;
-    }
-    String projectUuid = defaultString(component.getMainBranchProjectUuid(), component.projectUuid());
-    return hasProjectUuidPermission(permission, projectUuid);
-  }
-
-  @Override
-  public final boolean hasComponentUuidPermission(String permission, String componentUuid) {
-    if (isRoot()) {
-      return true;
-    }
-    Optional<String> projectUuid = componentUuidToProjectUuid(componentUuid);
-    return projectUuid
-      .map(s -> hasProjectUuidPermission(permission, s))
-      .orElse(false);
-  }
-
-  protected abstract Optional<String> componentUuidToProjectUuid(String componentUuid);
-
-  protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid);
-
-  @Override
-  public final boolean hasMembership(OrganizationDto organization) {
-    return isRoot() || hasMembershipImpl(organization);
-  }
-
-  protected abstract boolean hasMembershipImpl(OrganizationDto organization);
-
-  @Override
-  public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    if (isRoot()) {
-      return new ArrayList<>(components);
-    }
-    return doKeepAuthorizedComponents(permission, components);
-  }
-
-  /**
-   * Naive implementation, to be overridden if needed
-   */
-  protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    boolean allowPublicComponent = ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission);
-    return components.stream()
-      .filter(c -> (allowPublicComponent && !c.isPrivate()) || hasComponentPermission(permission, c))
-      .collect(MoreCollectors.toList());
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server-common/src/main/java/org/sonar/server/user/UserSession.java
deleted file mode 100644 (file)
index 645254a..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.user;
-
-import java.util.Collection;
-import java.util.List;
-import javax.annotation.CheckForNull;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.db.user.GroupDto;
-
-public interface UserSession {
-
-  /**
-   * Login of the authenticated user. Returns {@code null}
-   * if {@link #isLoggedIn()} is {@code false}.
-   */
-  @CheckForNull
-  String getLogin();
-
-  /**
-   * Uuid of the authenticated user. Returns {@code null}
-   * if {@link #isLoggedIn()} is {@code false}.
-   */
-  @CheckForNull
-  String getUuid();
-
-  /**
-   * Name of the authenticated user. Returns {@code null}
-   * if {@link #isLoggedIn()} is {@code false}.
-   */
-  @CheckForNull
-  String getName();
-
-  /**
-   * Database ID of the authenticated user. Returns {@code null}
-   * if {@link #isLoggedIn()} is {@code false}.
-   */
-  @CheckForNull
-  Integer getUserId();
-
-  /**
-   * The groups that the logged-in user is member of. An empty
-   * collection is returned if {@link #isLoggedIn()} is {@code false}.
-   */
-  Collection<GroupDto> getGroups();
-
-  /**
-   * Whether the user is logged-in or anonymous.
-   */
-  boolean isLoggedIn();
-
-  /**
-   * Whether the user has root privileges. If {@code true}, then user automatically
-   * benefits from all the permissions on all organizations and projects.
-   */
-  boolean isRoot();
-
-  /**
-   * Ensures that {@link #isRoot()} returns {@code true} otherwise throws a
-   * {@link org.sonar.server.exceptions.ForbiddenException}.
-   */
-  UserSession checkIsRoot();
-
-  /**
-   * Ensures that user is logged in otherwise throws {@link org.sonar.server.exceptions.UnauthorizedException}.
-   */
-  UserSession checkLoggedIn();
-
-  /**
-   * Returns {@code true} if the permission is granted on the organization, otherwise {@code false}.
-   *
-   * If the organization does not exist, then returns {@code false}.
-   *
-   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
-   * organization does not exist.
-   */
-  boolean hasPermission(OrganizationPermission permission, OrganizationDto organization);
-
-  boolean hasPermission(OrganizationPermission permission, String organizationUuid);
-
-  /**
-   * Ensures that {@link #hasPermission(OrganizationPermission, OrganizationDto)} is {@code true},
-   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
-   */
-  UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization);
-
-  UserSession checkPermission(OrganizationPermission permission, String organizationUuid);
-
-  /**
-   * Returns {@code true} if the permission is granted to user on the component,
-   * otherwise {@code false}.
-   *
-   * If the component does not exist, then returns {@code false}.
-   *
-   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
-   * component does not exist.
-   *
-   * If the permission is not granted, then the organization permission is _not_ checked.
-   *
-   * @param component non-null component.
-   * @param permission project permission as defined by {@link org.sonar.core.permission.ProjectPermissions}
-   */
-  boolean hasComponentPermission(String permission, ComponentDto component);
-
-  /**
-   * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended
-   * because it does not have to load project if the referenced component
-   * is not a project.
-   *
-   * @deprecated use {@link #hasComponentPermission(String, ComponentDto)} instead
-   */
-  @Deprecated
-  boolean hasComponentUuidPermission(String permission, String componentUuid);
-
-  /**
-   * Return the subset of specified components which the user has granted permission.
-   * An empty list is returned if input is empty or if no components are allowed to be
-   * accessed.
-   * If the input is ordered, then the returned components are in the same order.
-   * The duplicated components are returned duplicated too.
-   */
-  List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components);
-
-  /**
-   * Ensures that {@link #hasComponentPermission(String, ComponentDto)} is {@code true},
-   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
-   */
-  UserSession checkComponentPermission(String projectPermission, ComponentDto component);
-
-  /**
-   * Ensures that {@link #hasComponentUuidPermission(String, String)} is {@code true},
-   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
-   *
-   * @deprecated use {@link #checkComponentPermission(String, ComponentDto)} instead
-   */
-  @Deprecated
-  UserSession checkComponentUuidPermission(String permission, String componentUuid);
-
-  /**
-   * Whether user can administrate system, for example for using cross-organizations services
-   * like update center, system info or management of users.
-   *
-   * Returns {@code true} if:
-   * <ul>
-   *   <li>{@link #isRoot()} is {@code true}</li>
-   *   <li>organization feature is disabled and user is administrator of the (single) default organization</li>
-   * </ul>
-   */
-  boolean isSystemAdministrator();
-
-  /**
-   * Ensures that {@link #isSystemAdministrator()} is {@code true},
-   * otherwise throws {@link org.sonar.server.exceptions.ForbiddenException}.
-   */
-  UserSession checkIsSystemAdministrator();
-
-  /**
-   * Returns {@code true} if the user is member of the organization, otherwise {@code false}.
-   *
-   * If the organization does not exist, then returns {@code false}.
-   *
-   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
-   * organization does not exist.
-   */
-  boolean hasMembership(OrganizationDto organization);
-
-  /**
-   * Ensures that {@link #hasMembership(OrganizationDto)} is {@code true},
-   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
-   */
-  UserSession checkMembership(OrganizationDto organization);
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java
deleted file mode 100644 (file)
index e96e0e3..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.stream.IntStream;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.db.component.ComponentDto;
-
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ComponentIndexCombinationTest extends ComponentIndexTest {
-
-  @Test
-  public void return_empty_list_if_no_fields_match_query() {
-    indexProject("struts", "Apache Struts");
-
-    assertThat(index.searchSuggestions(SuggestionQuery.builder().setQuery("missing").build()).isEmpty()).isTrue();
-  }
-
-  @Test
-  public void should_not_return_components_that_do_not_match_at_all() {
-    indexProject("banana", "Banana Project 1");
-
-    assertNoSearchResults("Apple");
-  }
-
-  @Test
-  public void filter_results_by_qualifier() {
-    ComponentDto project = indexProject("struts", "Apache Struts");
-    indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
-
-    assertSearchResults(SuggestionQuery.builder().setQuery("struts").setQualifiers(singletonList(Qualifiers.PROJECT)).build(), project);
-  }
-
-  @Test
-  public void should_limit_the_number_of_results() {
-    IntStream.rangeClosed(0, 10).forEach(i -> indexProject("sonarqube" + i, "SonarQube" + i));
-
-    assertSearch(SuggestionQuery.builder().setQuery("sonarqube").setLimit(5).setQualifiers(singletonList(Qualifiers.PROJECT)).build()).hasSize(5);
-  }
-
-  @Test
-  public void should_not_support_wildcards() {
-    indexProject("theKey", "the name");
-
-    assertNoSearchResults("*t*");
-    assertNoSearchResults("th?Key");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java
deleted file mode 100644 (file)
index 7f781bf..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Collections;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-
-public class ComponentIndexFeatureExactTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(query -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.EXACT_IGNORE_CASE);
-  }
-
-  @Test
-  public void scoring_cares_about_exact_matches() {
-    ComponentDto project1 = indexProject("project1", "LongNameLongNameLongNameLongNameSonarQube");
-    ComponentDto project2 = indexProject("project2", "LongNameLongNameLongNameLongNameSonarQubeX");
-
-    SuggestionQuery query1 = SuggestionQuery.builder()
-      .setQuery("LongNameLongNameLongNameLongNameSonarQube")
-      .setQualifiers(Collections.singletonList(PROJECT))
-      .build();
-    assertSearch(query1).containsExactly(uuids(project1, project2));
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java
deleted file mode 100644 (file)
index 7028889..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-import static com.google.common.collect.ImmutableSet.of;
-import static java.util.Collections.singletonList;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
-
-public class ComponentIndexFeatureFavoriteTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(q -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.FAVORITE);
-  }
-
-  @Test
-  public void scoring_cares_about_favorites() {
-    ComponentDto project1 = indexProject("sonarqube", "SonarQube");
-    ComponentDto project2 = indexProject("recent", "SonarQube Recently");
-
-    SuggestionQuery query1 = SuggestionQuery.builder()
-      .setQuery("SonarQube")
-      .setQualifiers(singletonList(PROJECT))
-      .setFavoriteKeys(of(project1.getDbKey()))
-      .build();
-    assertSearch(query1).containsExactly(uuids(project1, project2));
-
-    SuggestionQuery query2 = SuggestionQuery.builder()
-      .setQuery("SonarQube")
-      .setQualifiers(singletonList(PROJECT))
-      .setFavoriteKeys(of(project2.getDbKey()))
-      .build();
-    assertSearch(query2).containsExactly(uuids(project2, project1));
-  }
-
-  @Test
-  public void irrelevant_favorites_are_not_returned() {
-    features.set(q -> termQuery(FIELD_KEY, "non-existing-value"), ComponentTextSearchFeatureRepertoire.FAVORITE);
-    ComponentDto project1 = indexProject("foo", "foo");
-
-    SuggestionQuery query1 = SuggestionQuery.builder()
-      .setQuery("bar")
-      .setQualifiers(singletonList(PROJECT))
-      .setFavoriteKeys(of(project1.getDbKey()))
-      .build();
-    assertSearch(query1).isEmpty();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java
deleted file mode 100644 (file)
index 79c1ba8..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-public class ComponentIndexFeatureKeyTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(ComponentTextSearchFeatureRepertoire.KEY);
-  }
-
-  @Test
-  public void should_search_projects_by_exact_case_insensitive_key() {
-    ComponentDto project1 = indexProject("keyOne", "Project One");
-    indexProject("keyTwo", "Project Two");
-
-    assertSearchResults("keyOne", project1);
-    assertSearchResults("keyone", project1);
-    assertSearchResults("KEYone", project1);
-  }
-
-  @Test
-  public void should_search_project_with_dot_in_key() {
-    ComponentDto project = indexProject("org.sonarqube", "SonarQube");
-
-    assertSearchResults("org.sonarqube", project);
-    assertNoSearchResults("orgsonarqube");
-  }
-
-  @Test
-  public void should_search_project_with_dash_in_key() {
-    ComponentDto project = indexProject("org-sonarqube", "SonarQube");
-
-    assertSearchResults("org-sonarqube", project);
-    assertNoSearchResults("orgsonarqube");
-  }
-
-  @Test
-  public void should_search_project_with_colon_in_key() {
-    ComponentDto project = indexProject("org:sonarqube", "Quality Product");
-
-    assertSearchResults("org:sonarqube", project);
-    assertNoSearchResults("orgsonarqube");
-    assertNoSearchResults("org-sonarqube");
-    assertNoSearchResults("org_sonarqube");
-  }
-
-  @Test
-  public void should_search_project_with_all_special_characters_in_key() {
-    ComponentDto project = indexProject("org.sonarqube:sonar-sérvèr_ç", "SonarQube");
-
-    assertSearchResults("org.sonarqube:sonar-sérvèr_ç", project);
-  }
-
-  @Test
-  public void should_not_return_results_when_searching_by_partial_key() {
-    indexProject("theKey", "some name");
-
-    assertNoSearchResults("theke");
-    assertNoSearchResults("hekey");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java
deleted file mode 100644 (file)
index ee01483..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-public class ComponentIndexFeaturePartialTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
-  }
-
-  @Test
-  public void search_projects_by_exact_name() {
-    ComponentDto struts = indexProject("struts", "Apache Struts");
-    indexProject("sonarqube", "SonarQube");
-
-    assertSearchResults("Apache Struts", struts);
-    assertSearchResults("APACHE STRUTS", struts);
-    assertSearchResults("APACHE struTS", struts);
-  }
-
-  @Test
-  public void search_file_with_long_name() {
-    ComponentDto project = indexProject("struts", "Apache Struts");
-    ComponentDto file1 = indexFile(project, "src/main/java/DefaultRubyComponentServiceTestManagerFactory.java", "DefaultRubyComponentServiceTestManagerFactory.java");
-
-    assertSearchResults("DefaultRubyComponentServiceTestManagerFactory", file1);
-    assertSearchResults("DefaultRubyComponentServiceTestManagerFactory.java", file1);
-    assertSearchResults("RubyComponentServiceTestManager", file1);
-    assertSearchResults("te", file1);
-  }
-
-  @Test
-  public void should_search_by_name_with_two_characters() {
-    ComponentDto project = indexProject("struts", "Apache Struts");
-
-    assertSearchResults("st", project);
-    assertSearchResults("tr", project);
-  }
-
-  @Test
-  public void search_projects_by_partial_name() {
-    ComponentDto struts = indexProject("struts", "Apache Struts");
-
-    assertSearchResults("truts", struts);
-    assertSearchResults("pache", struts);
-    assertSearchResults("apach", struts);
-    assertSearchResults("che stru", struts);
-  }
-
-  @Test
-  public void search_projects_and_files_by_partial_name() {
-    ComponentDto project = indexProject("struts", "Apache Struts");
-    ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
-    indexFile(project, "src/main/java/Foo.java", "Foo.java");
-
-    assertSearchResults("struts", project, file1);
-    assertSearchResults("Struts", project, file1);
-    assertSearchResults("StrutsManager", file1);
-    assertSearchResults("STRUTSMAN", file1);
-    assertSearchResults("utsManag", file1);
-  }
-
-  @Test
-  public void should_find_file_by_file_extension() {
-    ComponentDto project = indexProject("struts", "Apache Struts");
-    ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
-    ComponentDto file2 = indexFile(project, "src/main/java/Foo.java", "Foo.java");
-
-    assertSearchResults(".java", file1, file2);
-    assertSearchResults("manager.java", file1);
-
-    // do not match
-    assertNoSearchResults("somethingStrutsManager.java");
-  }
-
-  @Test
-  public void should_search_for_word_and_suffix() {
-    assertFileMatches("plugin java", "AbstractPluginFactory.java");
-  }
-
-  @Test
-  public void should_search_for_word_and_suffix_in_any_order() {
-    assertFileMatches("java plugin", "AbstractPluginFactory.java");
-  }
-
-  @Test
-  public void should_search_for_two_words() {
-    assertFileMatches("abstract factory", "AbstractPluginFactory.java");
-  }
-
-  @Test
-  public void should_search_for_two_words_in_any_order() {
-    assertFileMatches("factory abstract", "AbstractPluginFactory.java");
-  }
-
-  @Test
-  public void should_require_at_least_one_matching_word() {
-    assertNoFileMatches("monitor object", "AbstractPluginFactory.java");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java
deleted file mode 100644 (file)
index e247352..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-public class ComponentIndexFeaturePrefixTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(ComponentTextSearchFeatureRepertoire.PREFIX, ComponentTextSearchFeatureRepertoire.PREFIX_IGNORE_CASE);
-  }
-
-  @Test
-  public void should_find_prefix() {
-    assertResultOrder("comp", "component");
-  }
-
-  @Test
-  public void should_find_exact_match() {
-    assertResultOrder("component.js", "component.js");
-  }
-
-  @Test
-  public void should_not_find_partially() {
-    assertNoFileMatches("component.js", "my_component.js");
-  }
-
-  @Test
-  public void should_be_able_to_ignore_case() {
-    features.set(ComponentTextSearchFeatureRepertoire.PREFIX_IGNORE_CASE);
-    assertResultOrder("cOmPoNeNt.Js", "CoMpOnEnT.jS");
-  }
-
-  @Test
-  public void should_prefer_matching_case() {
-    assertResultOrder("cOmPoNeNt.Js", "cOmPoNeNt.Js", "CoMpOnEnT.jS");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java
deleted file mode 100644 (file)
index 54fa659..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Collections;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-import static com.google.common.collect.ImmutableSet.of;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-
-public class ComponentIndexFeatureRecentlyBrowsedTest extends ComponentIndexTest {
-
-  @Before
-  public void before() {
-    features.set(query -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.RECENTLY_BROWSED);
-  }
-
-  @Test
-  public void scoring_cares_about_recently_browsed() {
-    ComponentDto project1 = indexProject("sonarqube", "SonarQube");
-    ComponentDto project2 = indexProject("recent", "SonarQube Recently");
-
-    SuggestionQuery query1 = SuggestionQuery.builder()
-      .setQuery("SonarQube")
-      .setQualifiers(Collections.singletonList(PROJECT))
-      .setRecentlyBrowsedKeys(of(project1.getDbKey()))
-      .build();
-    assertSearch(query1).containsExactly(uuids(project1, project2));
-
-    SuggestionQuery query2 = SuggestionQuery.builder()
-      .setQuery("SonarQube")
-      .setQualifiers(Collections.singletonList(PROJECT))
-      .setRecentlyBrowsedKeys(of(project2.getDbKey()))
-      .build();
-    assertSearch(query2).containsExactly(uuids(project2, project1));
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
deleted file mode 100644 (file)
index d0ff940..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Collections;
-import java.util.Optional;
-import java.util.stream.Stream;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ComponentIndexHighlightTest extends ComponentIndexTest {
-
-  @Test
-  public void should_escape_html() {
-    assertHighlighting("quick< brown fox", "brown", "quick&lt; <mark>brown</mark> fox");
-  }
-
-  @Test
-  public void should_highlight_partial_name() {
-    assertHighlighting("quickbrownfox", "brown", "quick<mark>brown</mark>fox");
-  }
-
-  @Test
-  public void should_highlight_prefix() {
-    assertHighlighting("quickbrownfox", "quick", "<mark>quick</mark>brownfox");
-  }
-
-  @Test
-  public void should_highlight_suffix() {
-    assertHighlighting("quickbrownfox", "fox", "quickbrown<mark>fox</mark>");
-  }
-
-  @Test
-  public void should_highlight_multiple_words() {
-    assertHighlighting("quickbrownfox", "fox bro", "quick<mark>bro</mark>wn<mark>fox</mark>");
-  }
-
-  @Test
-  public void should_highlight_multiple_connected_words() {
-    assertHighlighting("quickbrownfox", "fox brown", "quick<mark>brownfox</mark>");
-  }
-
-  private void assertHighlighting(String fileName, String search, String expectedHighlighting) {
-    indexFile(fileName);
-
-    SuggestionQuery query = SuggestionQuery.builder()
-      .setQuery(search)
-      .setQualifiers(Collections.singletonList(Qualifiers.FILE))
-      .build();
-    Stream<ComponentHitsPerQualifier> results = index.searchSuggestions(query, features.get()).getQualifiers();
-
-    assertThat(results).flatExtracting(ComponentHitsPerQualifier::getHits)
-      .extracting(ComponentHit::getHighlightedText)
-      .extracting(Optional::get)
-      .containsExactly(expectedHighlighting);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java
deleted file mode 100644 (file)
index 24597b6..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-
-import static org.sonar.db.user.GroupTesting.newGroupDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
-
-public class ComponentIndexLoginTest extends ComponentIndexTest {
-
-  @Test
-  public void should_filter_unauthorized_results() {
-    indexer.index(newProject("sonarqube", "Quality Product"));
-
-    // do not give any permissions to that project
-
-    assertNoSearchResults("sonarqube");
-    assertNoSearchResults("Quality Product");
-  }
-
-  @Test
-  public void should_find_project_for_which_the_user_has_direct_permission() {
-    UserDto user = newUserDto();
-    userSession.logIn(user);
-
-    ComponentDto project = newProject("sonarqube", "Quality Product");
-    indexer.index(project);
-
-    assertNoSearchResults("sonarqube");
-
-    // give the user explicit access
-    authorizationIndexerTester.allowOnlyUser(project, user);
-    assertSearchResults("sonarqube", project);
-  }
-
-  @Test
-  public void should_find_project_for_which_the_user_has_indirect_permission_through_group() {
-    GroupDto group = newGroupDto();
-    userSession.logIn().setGroups(group);
-
-    ComponentDto project = newProject("sonarqube", "Quality Product");
-    indexer.index(project);
-
-    assertNoSearchResults("sonarqube");
-
-    // give the user implicit access (though group)
-    authorizationIndexerTester.allowOnlyGroup(project, group);
-    assertSearchResults("sonarqube", project);
-  }
-
-  @Test
-  public void do_not_check_permissions_when_logged_in_user_is_root() {
-    userSession.logIn().setRoot();
-    ComponentDto project = newProject("sonarqube", "Quality Product");
-    indexer.index(project);
-    // do not give any permissions to that project
-
-    assertSearchResults("sonarqube", project);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java
deleted file mode 100644 (file)
index 6eda6e3..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import org.junit.Test;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-public class ComponentIndexMultipleWordsTest extends ComponentIndexTest {
-
-  @Test
-  public void should_find_perfect_match() {
-    assertResultOrder("struts java",
-      "Struts.java");
-  }
-
-  @Test
-  public void should_find_partial_match() {
-    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
-    assertResultOrder("struts java",
-      "Xstrutsx.Xjavax");
-  }
-
-  @Test
-  public void should_find_partial_match_prefix_word1() {
-    assertResultOrder("struts java",
-      "MyStruts.java");
-  }
-
-  @Test
-  public void should_find_partial_match_suffix_word1() {
-    assertResultOrder("struts java",
-      "StrutsObject.java");
-  }
-
-  @Test
-  public void should_find_partial_match_prefix_word2() {
-    assertResultOrder("struts java",
-      "MyStruts.xjava");
-  }
-
-  @Test
-  public void should_find_partial_match_suffix_word2() {
-    assertResultOrder("struts java",
-      "MyStruts.javax");
-  }
-
-  @Test
-  public void should_find_partial_match_prefix_and_suffix_everywhere() {
-    assertResultOrder("struts java",
-      "MyStrutsObject.xjavax");
-  }
-
-  @Test
-  public void should_find_subset_of_document_terms() {
-    assertResultOrder("struts java",
-      "Some.Struts.Class.java.old");
-  }
-
-  @Test
-  public void should_require_all_words_to_match() {
-    assertNoFileMatches("struts java",
-      "Struts");
-  }
-
-  @Test
-  public void should_ignore_empty_words() {
-    assertFileMatches("            struts   \n     \n\n",
-      "Struts");
-  }
-
-  @Test
-  public void should_require_all_words_to_match_for_partial() {
-    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
-    assertNoFileMatches("struts java",
-      "Struts");
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java
deleted file mode 100644 (file)
index de165d9..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import com.google.common.collect.ImmutableSet;
-import org.junit.Test;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
-
-import static java.util.Arrays.asList;
-import static org.sonar.api.resources.Qualifiers.FILE;
-import static org.sonar.api.resources.Qualifiers.MODULE;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-
-public class ComponentIndexScoreTest extends ComponentIndexTest {
-
-  @Test
-  public void should_prefer_components_without_prefix() {
-    assertResultOrder("File.java",
-      "File.java",
-      "MyFile.java");
-  }
-
-  @Test
-  public void should_prefer_components_without_suffix() {
-    assertResultOrder("File",
-      "File",
-      "Filex");
-  }
-
-  @Test
-  public void should_prefer_key_matching_over_name_matching() {
-    ComponentDto project1 = indexProject("quality", "SonarQube");
-    ComponentDto project2 = indexProject("sonarqube", "Quality Product");
-
-    assertExactResults("sonarqube", project2, project1);
-  }
-
-  @Test
-  public void should_prefer_prefix_matching_over_partial_matching() {
-    assertResultOrder("corem",
-      "CoreMetrics.java",
-      "ScoreMatrix.java");
-  }
-
-  @Test
-  public void should_prefer_case_sensitive_prefix() {
-    assertResultOrder("caSe",
-      "caSeBla.java",
-      "CaseBla.java");
-  }
-
-  @Test
-  public void scoring_prefix_with_multiple_words() {
-    assertResultOrder("index java",
-      "IndexSomething.java",
-      "MyIndex.java");
-  }
-
-  @Test
-  public void scoring_prefix_with_multiple_words_and_case() {
-    assertResultOrder("Index JAVA",
-      "IndexSomething.java",
-      "index_java.js");
-  }
-
-  @Test
-  public void scoring_long_items() {
-    assertResultOrder("ThisIsAVeryLongNameToSearchForAndItExceeds15Characters.java",
-      "ThisIsAVeryLongNameToSearchForAndItExceeds15Characters.java",
-      "ThisIsAVeryLongNameToSearchForAndItEndsDifferently.java");
-  }
-
-  @Test
-  public void scoring_perfect_match() {
-    assertResultOrder("SonarQube",
-      "SonarQube",
-      "SonarQube SCM Git");
-  }
-
-  @Test
-  public void scoring_perfect_match_dispite_case_changes() {
-    assertResultOrder("sonarqube",
-      "SonarQube",
-      "SonarQube SCM Git");
-  }
-
-  @Test
-  public void scoring_perfect_match_with_matching_case_higher_than_without_matching_case() {
-    assertResultOrder("sonarqube",
-      "sonarqube",
-      "SonarQube");
-  }
-
-  @Test
-  public void should_prefer_favorite_over_recently_browsed() {
-    ComponentDto file1 = db.components().insertPrivateProject(c -> c.setName("File1"));
-    index(file1);
-
-    ComponentDto file2 = db.components().insertPrivateProject(c -> c.setName("File2"));
-    index(file2);
-
-    assertSearch(SuggestionQuery.builder()
-      .setQuery("File")
-      .setQualifiers(asList(PROJECT, MODULE, FILE))
-      .setRecentlyBrowsedKeys(ImmutableSet.of(file1.getDbKey()))
-      .setFavoriteKeys(ImmutableSet.of(file2.getDbKey()))
-      .build()).containsExactly(uuids(file2, file1));
-
-    assertSearch(SuggestionQuery.builder()
-      .setQuery("File")
-      .setQualifiers(asList(PROJECT, MODULE, FILE))
-      .setRecentlyBrowsedKeys(ImmutableSet.of(file2.getDbKey()))
-      .setFavoriteKeys(ImmutableSet.of(file1.getDbKey()))
-      .build()).containsExactly(uuids(file1, file2));
-  }
-
-  @Test
-  public void do_not_match_wrong_file_extension() {
-    ComponentDto file1 = indexFile("MyClass.java");
-    ComponentDto file2 = indexFile("ClassExample.java");
-    ComponentDto file3 = indexFile("Class.java");
-    indexFile("Class.cs");
-    indexFile("Class.js");
-    indexFile("Class.rb");
-
-    assertExactResults("Class java", file3, file2, file1);
-  }
-
-  @Test
-  public void if_relevancy_is_equal_fall_back_to_alphabetical_ordering() {
-    assertResultOrder("sonarqube",
-      "sonarqubeA",
-      "sonarqubeB");
-  }
-
-  @Test
-  public void scoring_test_DbTester() {
-    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
-
-    ComponentDto project = indexProject("key-1", "Quality Product");
-
-    index(ComponentTesting.newFileDto(project)
-      .setName("DbTester.java")
-      .setDbKey("java/org/example/DbTester.java")
-      .setUuid("UUID-DbTester"));
-
-    index(ComponentTesting.newFileDto(project)
-      .setName("WebhookDbTesting.java")
-      .setDbKey("java/org/example/WebhookDbTesting.java")
-      .setUuid("UUID-WebhookDbTesting"));
-
-    assertSearch("dbt").containsExactly(
-
-      "UUID-DbTester",
-      "UUID-WebhookDbTesting"
-
-    );
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java
deleted file mode 100644 (file)
index e22e83f..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRule;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singleton;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-public class ComponentIndexSearchTest {
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-  @Rule
-  public ComponentTextSearchFeatureRule features = new ComponentTextSearchFeatureRule();
-
-  private ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
-
-  private ComponentIndex underTest = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
-
-  @Test
-  public void filter_by_language() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto javaFile = db.components().insertComponent(newFileDto(project).setLanguage("java"));
-    ComponentDto jsFile1 = db.components().insertComponent(newFileDto(project).setLanguage("js"));
-    ComponentDto jsFile2 = db.components().insertComponent(newFileDto(project).setLanguage("js"));
-    index(project);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setLanguage("js").build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(jsFile1.uuid(), jsFile2.uuid());
-  }
-
-  @Test
-  public void filter_by_name() {
-    ComponentDto ignoredProject = db.components().insertPrivateProject(p -> p.setName("ignored project"));
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setName("Project Shiny name"));
-    index(ignoredProject, project);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQuery("shiny").build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void filter_by_key_with_exact_match() {
-    ComponentDto ignoredProject = db.components().insertPrivateProject(p -> p.setDbKey("ignored-project"));
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("shiny-project"));
-    ComponentDto anotherIgnoreProject = db.components().insertPrivateProject(p -> p.setDbKey("another-shiny-project"));
-    index(ignoredProject, project);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQuery("shiny-project").build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void filter_by_qualifier() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    index(project);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQualifiers(singleton(Qualifiers.FILE)).build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(file.uuid());
-  }
-
-  @Test
-  public void filter_by_organization() {
-    OrganizationDto organization = db.organizations().insert();
-    OrganizationDto anotherOrganization = db.organizations().insert();
-    ComponentDto project = db.components().insertPrivateProject(organization);
-    ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
-    index(project, anotherProject);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setOrganization(organization.getUuid()).build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void order_by_name_case_insensitive() {
-    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setName("PROJECT 2"));
-    ComponentDto project3 = db.components().insertPrivateProject(p -> p.setName("project 3"));
-    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setName("Project 1"));
-    index(project1, project2, project3);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactly(project1.uuid(), project2.uuid(), project3.uuid());
-  }
-
-  @Test
-  public void paginate_results() {
-    List<ComponentDto> projects = IntStream.range(0, 9)
-      .mapToObj(i -> db.components().insertPrivateProject(p -> p.setName("project " + i)))
-      .collect(Collectors.toList());
-    index(projects.toArray(new ComponentDto[0]));
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions().setPage(2, 3));
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(projects.get(3).uuid(), projects.get(4).uuid(), projects.get(5).uuid());
-  }
-
-  @Test
-  public void filter_unauthorized_components() {
-    ComponentDto unauthorizedProject = db.components().insertPrivateProject();
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-    indexer.indexOnStartup(emptySet());
-    authorizationIndexerTester.allowOnlyAnyone(project1);
-    authorizationIndexerTester.allowOnlyAnyone(project2);
-
-    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions());
-
-    assertThat(result.getIds()).containsExactlyInAnyOrder(project1.uuid(), project2.uuid())
-      .doesNotContain(unauthorizedProject.uuid());
-  }
-
-  private void index(ComponentDto... components) {
-    indexer.indexOnStartup(emptySet());
-    Arrays.stream(components).forEach(c -> authorizationIndexerTester.allowOnlyAnyone(c));
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java
deleted file mode 100644 (file)
index 9079c9c..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.assertj.core.api.ListAssert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.organization.OrganizationTesting;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRule;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.resources.Qualifiers.FILE;
-import static org.sonar.api.resources.Qualifiers.MODULE;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-
-public abstract class ComponentIndexTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-
-  @Rule
-  public ComponentTextSearchFeatureRule features = new ComponentTextSearchFeatureRule();
-
-  protected ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
-  protected ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
-  protected PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
-  private OrganizationDto organization;
-
-  @Before
-  public void setUp() {
-    organization = OrganizationTesting.newOrganizationDto();
-  }
-
-  protected void assertFileMatches(String query, String... fileNames) {
-    ComponentDto[] files = Arrays.stream(fileNames)
-      .map(this::indexFile)
-      .toArray(ComponentDto[]::new);
-    assertSearch(query).containsExactlyInAnyOrder(uuids(files));
-  }
-
-  protected void assertNoFileMatches(String query, String... fileNames) {
-    Arrays.stream(fileNames)
-      .forEach(this::indexFile);
-    assertSearch(query).isEmpty();
-  }
-
-  protected void assertResultOrder(String query, String... resultsInOrder) {
-    ComponentDto project = indexProject("key-1", "Quality Product");
-    List<ComponentDto> files = Arrays.stream(resultsInOrder)
-      .map(r -> ComponentTesting.newFileDto(project).setName(r))
-      .peek(f -> f.setUuid(f.uuid() + "_" + f.name().replaceAll("[^a-zA-Z0-9]", "")))
-      .collect(Collectors.toList());
-
-    // index them, but not in the expected order
-    files.stream()
-      .sorted(Comparator.comparing(ComponentDto::uuid).reversed())
-      .forEach(this::index);
-
-    assertExactResults(query, files.toArray(new ComponentDto[0]));
-  }
-
-  protected ListAssert<String> assertSearch(String query) {
-    return assertSearch(SuggestionQuery.builder().setQuery(query).setQualifiers(asList(PROJECT, MODULE, FILE)).build());
-  }
-
-  protected ListAssert<String> assertSearch(SuggestionQuery query) {
-    return (ListAssert<String>)assertThat(index.searchSuggestions(query, features.get()).getQualifiers())
-      .flatExtracting(ComponentHitsPerQualifier::getHits)
-      .extracting(ComponentHit::getUuid);
-  }
-
-  protected void assertSearchResults(String query, ComponentDto... expectedComponents) {
-    assertSearchResults(SuggestionQuery.builder().setQuery(query).setQualifiers(asList(PROJECT, MODULE, FILE)).build(), expectedComponents);
-  }
-
-  protected void assertSearchResults(SuggestionQuery query, ComponentDto... expectedComponents) {
-    assertSearch(query).containsOnly(uuids(expectedComponents));
-  }
-
-  protected void assertExactResults(String query, ComponentDto... expectedComponents) {
-    assertSearch(query).containsExactly(uuids(expectedComponents));
-  }
-
-  protected void assertNoSearchResults(String query) {
-    assertSearchResults(query);
-  }
-
-  protected ComponentDto indexProject(String key, String name) {
-    return index(
-      ComponentTesting.newPrivateProjectDto(organization, "UUID_" + key)
-        .setDbKey(key)
-        .setName(name));
-  }
-
-  protected ComponentDto newProject(String key, String name) {
-    return ComponentTesting.newPrivateProjectDto(organization, "UUID_" + key)
-      .setDbKey(key)
-      .setName(name);
-  }
-
-  protected ComponentDto indexFile(String fileName) {
-    ComponentDto project = indexProject("key-1", "SonarQube");
-    return indexFile(project, "src/main/java/" + fileName, fileName);
-  }
-
-  protected ComponentDto indexFile(ComponentDto project, String fileKey, String fileName) {
-    return index(
-      ComponentTesting.newFileDto(project)
-        .setDbKey(fileKey)
-        .setName(fileName));
-  }
-
-  protected ComponentDto index(ComponentDto dto) {
-    indexer.index(dto);
-    authorizationIndexerTester.allowOnlyAnyone(dto);
-    return dto;
-  }
-
-  protected static String[] uuids(ComponentDto... expectedComponents) {
-    return Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new);
-  }
-}
index 972f8ad3ee9fb6bf3f315758627b2984dde12693..ed1931efa9c9f793a36ec231d9aee7d73b75fad3 100644 (file)
@@ -23,13 +23,14 @@ import org.junit.Test;
 import org.sonar.core.platform.ComponentContainer;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
 
 public class EsModuleTest {
   @Test
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new EsModule().configure(container);
-    assertThat(container.size()).isEqualTo(3 + 2);
+    assertThat(container.size()).isEqualTo(2 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
   }
 
 }
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java
deleted file mode 100644 (file)
index b5894ff..0000000
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDbTester;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.utils.DateUtils.addDays;
-import static org.sonar.db.component.ComponentTesting.newDirectory;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.component.ComponentTesting.newModuleDto;
-import static org.sonar.db.component.ComponentTesting.newProjectCopy;
-import static org.sonar.db.component.ComponentTesting.newSubView;
-import static org.sonar.db.rule.RuleTesting.newRule;
-
-public class IssueQueryFactoryTest {
-
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private RuleDbTester ruleDbTester = new RuleDbTester(db);
-
-  private Clock clock = mock(Clock.class);
-  private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), clock, userSession);
-
-  @Test
-  public void create_from_parameters() {
-    UserDto user = db.users().insertUser(u -> u.setLogin("joanna"));
-    OrganizationDto organization = db.organizations().insert();
-    ComponentDto project = db.components().insertPrivateProject(organization);
-    ComponentDto module = db.components().insertComponent(newModuleDto(project));
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-
-    RuleDefinitionDto rule1 = ruleDbTester.insert();
-    RuleDefinitionDto rule2 = ruleDbTester.insert();
-    newRule(RuleKey.of("findbugs", "NullReference"));
-    SearchRequest request = new SearchRequest()
-      .setIssues(asList("anIssueKey"))
-      .setSeverities(asList("MAJOR", "MINOR"))
-      .setStatuses(asList("CLOSED"))
-      .setResolutions(asList("FALSE-POSITIVE"))
-      .setResolved(true)
-      .setProjectKeys(asList(project.getDbKey()))
-      .setModuleUuids(asList(module.uuid()))
-      .setDirectories(asList("aDirPath"))
-      .setFileUuids(asList(file.uuid()))
-      .setAssigneesUuid(asList(user.getUuid()))
-      .setLanguages(asList("xoo"))
-      .setTags(asList("tag1", "tag2"))
-      .setOrganization(organization.getKey())
-      .setAssigned(true)
-      .setCreatedAfter("2013-04-16T09:08:24+0200")
-      .setCreatedBefore("2013-04-17T09:08:24+0200")
-      .setRules(asList(rule1.getKey().toString(), rule2.getKey().toString()))
-      .setSort("CREATION_DATE")
-      .setAsc(true);
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.issueKeys()).containsOnly("anIssueKey");
-    assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
-    assertThat(query.statuses()).containsOnly("CLOSED");
-    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
-    assertThat(query.resolved()).isTrue();
-    assertThat(query.projectUuids()).containsOnly(project.uuid());
-    assertThat(query.moduleUuids()).containsOnly(module.uuid());
-    assertThat(query.fileUuids()).containsOnly(file.uuid());
-    assertThat(query.assignees()).containsOnly(user.getUuid());
-    assertThat(query.languages()).containsOnly("xoo");
-    assertThat(query.tags()).containsOnly("tag1", "tag2");
-    assertThat(query.organizationUuid()).isEqualTo(organization.getUuid());
-    assertThat(query.onComponentOnly()).isFalse();
-    assertThat(query.assigned()).isTrue();
-    assertThat(query.rules()).hasSize(2);
-    assertThat(query.directories()).containsOnly("aDirPath");
-    assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
-    assertThat(query.createdAfter().inclusive()).isTrue();
-    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
-    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
-    assertThat(query.asc()).isTrue();
-  }
-
-  @Test
-  public void leak_period_start_date_is_exclusive() {
-    long leakPeriodStart = addDays(new Date(), -14).getTime();
-
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-
-    SnapshotDto analysis = db.components().insertSnapshot(project, s -> s.setPeriodDate(leakPeriodStart));
-
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(Collections.singletonList(file.uuid()))
-      .setOnComponentOnly(true)
-      .setSinceLeakPeriod(true);
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.componentUuids()).containsOnly(file.uuid());
-    assertThat(query.createdAfter().date()).isEqualTo(new Date(leakPeriodStart));
-    assertThat(query.createdAfter().inclusive()).isFalse();
-
-  }
-
-  @Test
-  public void dates_are_inclusive() {
-    SearchRequest request = new SearchRequest()
-      .setCreatedAfter("2013-04-16")
-      .setCreatedBefore("2013-04-17");
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDate("2013-04-16"));
-    assertThat(query.createdAfter().inclusive()).isTrue();
-    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
-  }
-
-  @Test
-  public void creation_date_support_localdate() {
-    SearchRequest request = new SearchRequest()
-      .setCreatedAt("2013-04-16");
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.createdAt()).isEqualTo(DateUtils.parseDate("2013-04-16"));
-  }
-
-  @Test
-  public void creation_date_support_zoneddatetime() {
-    SearchRequest request = new SearchRequest()
-      .setCreatedAt("2013-04-16T09:08:24+0200");
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.createdAt()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
-  }
-
-  @Test
-  public void add_unknown_when_no_component_found() {
-    SearchRequest request = new SearchRequest()
-      .setComponentKeys(asList("does_not_exist"));
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.componentUuids()).containsOnly("<UNKNOWN>");
-  }
-
-  @Test
-  public void query_without_any_parameter() {
-    SearchRequest request = new SearchRequest();
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.componentUuids()).isEmpty();
-    assertThat(query.projectUuids()).isEmpty();
-    assertThat(query.moduleUuids()).isEmpty();
-    assertThat(query.moduleRootUuids()).isEmpty();
-    assertThat(query.directories()).isEmpty();
-    assertThat(query.fileUuids()).isEmpty();
-    assertThat(query.viewUuids()).isEmpty();
-    assertThat(query.organizationUuid()).isNull();
-    assertThat(query.branchUuid()).isNull();
-  }
-
-  @Test
-  public void fail_if_components_and_components_uuid_params_are_set_at_the_same_time() {
-    SearchRequest request = new SearchRequest()
-      .setComponentKeys(asList("foo"))
-      .setComponentUuids(asList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
-
-    underTest.create(request);
-  }
-
-  @Test
-  public void fail_if_both_projects_and_projectUuids_params_are_set() {
-    SearchRequest request = new SearchRequest()
-      .setProjectKeys(asList("foo"))
-      .setProjectUuids(asList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Parameters projects and projectUuids cannot be set simultaneously");
-
-    underTest.create(request);
-  }
-
-  @Test
-  public void fail_if_both_componentRoots_and_componentRootUuids_params_are_set() {
-    SearchRequest request = new SearchRequest()
-      .setComponentRoots(asList("foo"))
-      .setComponentRootUuids(asList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
-
-    underTest.create(request);
-  }
-
-  @Test
-  public void fail_if_componentRoots_references_components_with_different_qualifier() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    SearchRequest request = new SearchRequest()
-      .setComponentRoots(asList(project.getDbKey(), file.getDbKey()));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("All components must have the same qualifier, found FIL,TRK");
-
-    underTest.create(request);
-  }
-
-  @Test
-  public void param_componentRootUuids_enables_search_in_view_tree_if_user_has_permission_on_view() {
-    ComponentDto view = db.components().insertView();
-    SearchRequest request = new SearchRequest()
-      .setComponentRootUuids(asList(view.uuid()));
-    userSession.registerComponents(view);
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.viewUuids()).containsOnly(view.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void application_search_project_issues() {
-    ComponentDto project1 = db.components().insertPublicProject();
-    ComponentDto project2 = db.components().insertPublicProject();
-    ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
-    db.components().insertComponents(newProjectCopy("PC1", project1, application));
-    db.components().insertComponents(newProjectCopy("PC2", project2, application));
-    userSession.registerComponents(application, project1, project2);
-
-    IssueQuery result = underTest.create(new SearchRequest().setComponentUuids(singletonList(application.uuid())));
-
-    assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
-  }
-
-  @Test
-  public void application_search_project_issues_on_leak() {
-    Date now = new Date();
-    ComponentDto project1 = db.components().insertPublicProject();
-    SnapshotDto analysis1 = db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
-    ComponentDto project2 = db.components().insertPublicProject();
-    SnapshotDto analysis2 = db.components().insertSnapshot(project2, s -> s.setPeriodDate(null));
-    ComponentDto project3 = db.components().insertPublicProject();
-    ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
-    db.components().insertComponents(newProjectCopy("PC1", project1, application));
-    db.components().insertComponents(newProjectCopy("PC2", project2, application));
-    db.components().insertComponents(newProjectCopy("PC3", project3, application));
-    userSession.registerComponents(application, project1, project2, project3);
-
-    IssueQuery result = underTest.create(new SearchRequest()
-      .setComponentUuids(singletonList(application.uuid()))
-      .setSinceLeakPeriod(true));
-
-    assertThat(result.createdAfterByProjectUuids()).hasSize(1);
-    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).date().getTime()).isEqualTo(analysis1.getPeriodDate());
-    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).inclusive()).isFalse();
-    assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
-  }
-
-  @Test
-  public void return_empty_results_if_not_allowed_to_search_for_subview() {
-    ComponentDto view = db.components().insertView();
-    ComponentDto subView = db.components().insertComponent(newSubView(view));
-    SearchRequest request = new SearchRequest()
-      .setComponentRootUuids(asList(subView.uuid()));
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.viewUuids()).containsOnly("<UNKNOWN>");
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_on_project_tree_by_default() {
-    ComponentDto project = db.components().insertPrivateProject();
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(asList(project.uuid()));
-
-    IssueQuery query = underTest.create(request);
-    assertThat(query.projectUuids()).containsExactly(project.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void onComponentOnly_restricts_search_to_specified_componentKeys() {
-    ComponentDto project = db.components().insertPrivateProject();
-    SearchRequest request = new SearchRequest()
-      .setComponentKeys(asList(project.getDbKey()))
-      .setOnComponentOnly(true);
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.projectUuids()).isEmpty();
-    assertThat(query.componentUuids()).containsExactly(project.uuid());
-    assertThat(query.onComponentOnly()).isTrue();
-  }
-
-  @Test
-  public void should_search_in_tree_with_module_uuid() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto module = db.components().insertComponent(newModuleDto(project));
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(asList(module.uuid()));
-
-    IssueQuery query = underTest.create(request);
-    assertThat(query.moduleRootUuids()).containsExactly(module.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_in_directory_tree() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto dir = db.components().insertComponent(newDirectory(project, "src/main/java/foo"));
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(asList(dir.uuid()));
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.moduleUuids()).containsOnly(dir.moduleUuid());
-    assertThat(query.directories()).containsOnly(dir.path());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_by_file() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(asList(file.uuid()));
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.fileUuids()).containsExactly(file.uuid());
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_by_test_file() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
-    SearchRequest request = new SearchRequest()
-      .setComponentUuids(asList(file.uuid()));
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.fileUuids()).containsExactly(file.uuid());
-  }
-
-  @Test
-  public void search_issue_from_branch() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-
-    assertThat(underTest.create(new SearchRequest()
-      .setProjectKeys(singletonList(branch.getKey()))
-      .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
-
-    assertThat(underTest.create(new SearchRequest()
-      .setComponentKeys(singletonList(branch.getKey()))
-      .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
-  }
-
-  @Test
-  public void search_file_issue_from_branch() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-    ComponentDto file = db.components().insertComponent(newFileDto(branch));
-
-    assertThat(underTest.create(new SearchRequest()
-      .setComponentKeys(singletonList(file.getKey()))
-      .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
-
-    assertThat(underTest.create(new SearchRequest()
-      .setComponentKeys(singletonList(branch.getKey()))
-      .setFileUuids(singletonList(file.uuid()))
-      .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
-
-    assertThat(underTest.create(new SearchRequest()
-      .setProjectKeys(singletonList(branch.getKey()))
-      .setFileUuids(singletonList(file.uuid()))
-      .setBranch(branch.getBranch())))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
-  }
-
-  @Test
-  public void search_issue_on_component_only_from_branch() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-    ComponentDto file = db.components().insertComponent(newFileDto(branch));
-
-    assertThat(underTest.create(new SearchRequest()
-      .setComponentKeys(singletonList(file.getKey()))
-      .setBranch(branch.getBranch())
-      .setOnComponentOnly(true)))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch)
-        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
-  }
-
-  @Test
-  public void search_issues_from_main_branch() {
-    ComponentDto project = db.components().insertMainBranch();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-
-    assertThat(underTest.create(new SearchRequest()
-      .setProjectKeys(singletonList(project.getKey()))
-      .setBranch("master")))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
-    assertThat(underTest.create(new SearchRequest()
-      .setComponentKeys(singletonList(project.getKey()))
-      .setBranch("master")))
-        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
-        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
-  }
-
-  @Test
-  public void fail_if_created_after_and_created_since_are_both_set() {
-    SearchRequest request = new SearchRequest()
-      .setCreatedAfter("2013-07-25T07:35:00+0100")
-      .setCreatedInLast("palap");
-
-    try {
-      underTest.create(request);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Parameters createdAfter and createdInLast cannot be set simultaneously");
-    }
-  }
-
-  @Test
-  public void set_created_after_from_created_since() {
-    Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100");
-    when(clock.instant()).thenReturn(now.toInstant());
-    when(clock.getZone()).thenReturn(ZoneOffset.UTC);
-    SearchRequest request = new SearchRequest()
-      .setCreatedInLast("1y2m3w4d");
-    assertThat(underTest.create(request).createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
-    assertThat(underTest.create(request).createdAfter().inclusive()).isTrue();
-
-  }
-
-  @Test
-  public void fail_if_since_leak_period_and_created_after_set_at_the_same_time() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Parameters 'createdAfter' and 'sinceLeakPeriod' cannot be set simultaneously");
-
-    underTest.create(new SearchRequest()
-      .setSinceLeakPeriod(true)
-      .setCreatedAfter("2013-07-25T07:35:00+0100"));
-  }
-
-  @Test
-  public void fail_if_no_component_provided_with_since_leak_period() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
-
-    underTest.create(new SearchRequest().setSinceLeakPeriod(true));
-  }
-
-  @Test
-  public void fail_if_several_components_provided_with_since_leak_period() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
-
-    underTest.create(new SearchRequest()
-      .setSinceLeakPeriod(true)
-      .setComponentKeys(asList(project1.getKey(), project2.getKey())));
-  }
-
-  @Test
-  public void fail_if_date_is_not_formatted_correctly() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("'unknown-date' cannot be parsed as either a date or date+time");
-
-    underTest.create(new SearchRequest()
-      .setCreatedAfter("unknown-date"));
-  }
-
-  @Test
-  public void return_empty_results_if_organization_with_specified_key_does_not_exist() {
-    SearchRequest request = new SearchRequest()
-      .setOrganization("does_not_exist");
-
-    IssueQuery query = underTest.create(request);
-
-    assertThat(query.organizationUuid()).isEqualTo("<UNKNOWN>");
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueQueryTest.java
deleted file mode 100644 (file)
index 957de3d..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import java.util.Date;
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.Severity;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.issue.IssueQuery.PeriodStart;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.apache.commons.lang.math.RandomUtils.nextInt;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-
-public class IssueQueryTest {
-
-  @Test
-  public void build_query() {
-    RuleDefinitionDto rule = new RuleDefinitionDto().setId(nextInt(1000));
-    PeriodStart filterDate = new IssueQuery.PeriodStart(new Date(10_000_000_000L), false);
-    IssueQuery query = IssueQuery.builder()
-      .issueKeys(newArrayList("ABCDE"))
-      .severities(newArrayList(Severity.BLOCKER))
-      .statuses(Lists.newArrayList(Issue.STATUS_RESOLVED))
-      .resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE))
-      .projectUuids(newArrayList("PROJECT"))
-      .componentUuids(newArrayList("org/struts/Action.java"))
-      .moduleUuids(newArrayList("org.struts:core"))
-      .rules(newArrayList(rule))
-      .assigneeUuids(newArrayList("gargantua"))
-      .languages(newArrayList("xoo"))
-      .tags(newArrayList("tag1", "tag2"))
-      .types(newArrayList("RELIABILITY", "SECURITY"))
-      .owaspTop10(newArrayList("a1", "a2"))
-      .sansTop25(newArrayList("insecure-interaction", "porous-defenses"))
-      .cwe(newArrayList("12", "125"))
-      .organizationUuid("orga")
-      .branchUuid("my_branch")
-      .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", filterDate))
-      .assigned(true)
-      .createdAfter(new Date())
-      .createdBefore(new Date())
-      .createdAt(new Date())
-      .resolved(true)
-      .sort(IssueQuery.SORT_BY_CREATION_DATE)
-      .asc(true)
-      .build();
-    assertThat(query.issueKeys()).containsOnly("ABCDE");
-    assertThat(query.severities()).containsOnly(Severity.BLOCKER);
-    assertThat(query.statuses()).containsOnly(Issue.STATUS_RESOLVED);
-    assertThat(query.resolutions()).containsOnly(Issue.RESOLUTION_FALSE_POSITIVE);
-    assertThat(query.projectUuids()).containsOnly("PROJECT");
-    assertThat(query.componentUuids()).containsOnly("org/struts/Action.java");
-    assertThat(query.moduleUuids()).containsOnly("org.struts:core");
-    assertThat(query.assignees()).containsOnly("gargantua");
-    assertThat(query.languages()).containsOnly("xoo");
-    assertThat(query.tags()).containsOnly("tag1", "tag2");
-    assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
-    assertThat(query.owaspTop10()).containsOnly("a1", "a2");
-    assertThat(query.sansTop25()).containsOnly("insecure-interaction", "porous-defenses");
-    assertThat(query.cwe()).containsOnly("12", "125");
-    assertThat(query.organizationUuid()).isEqualTo("orga");
-    assertThat(query.branchUuid()).isEqualTo("my_branch");
-    assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", filterDate));
-    assertThat(query.assigned()).isTrue();
-    assertThat(query.rules()).containsOnly(rule);
-    assertThat(query.createdAfter()).isNotNull();
-    assertThat(query.createdBefore()).isNotNull();
-    assertThat(query.createdAt()).isNotNull();
-    assertThat(query.resolved()).isTrue();
-    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
-    assertThat(query.asc()).isTrue();
-  }
-
-  @Test
-  public void build_query_without_dates() {
-    IssueQuery query = IssueQuery.builder()
-      .issueKeys(newArrayList("ABCDE"))
-      .createdAfter(null)
-      .createdBefore(null)
-      .createdAt(null)
-      .build();
-
-    assertThat(query.issueKeys()).containsOnly("ABCDE");
-    assertThat(query.createdAfter()).isNull();
-    assertThat(query.createdBefore()).isNull();
-    assertThat(query.createdAt()).isNull();
-  }
-
-  @Test
-  public void throw_exception_if_sort_is_not_valid() {
-    try {
-      IssueQuery.builder()
-        .sort("UNKNOWN")
-        .build();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Bad sort field: UNKNOWN");
-    }
-  }
-
-  @Test
-  public void collection_params_should_not_be_null_but_empty() {
-    IssueQuery query = IssueQuery.builder()
-      .issueKeys(null)
-      .projectUuids(null)
-      .componentUuids(null)
-      .moduleUuids(null)
-      .statuses(null)
-      .assigneeUuids(null)
-      .resolutions(null)
-      .rules(null)
-      .severities(null)
-      .languages(null)
-      .tags(null)
-      .types(null)
-      .owaspTop10(null)
-      .sansTop25(null)
-      .cwe(null)
-      .createdAfterByProjectUuids(null)
-      .build();
-    assertThat(query.issueKeys()).isEmpty();
-    assertThat(query.projectUuids()).isEmpty();
-    assertThat(query.componentUuids()).isEmpty();
-    assertThat(query.moduleUuids()).isEmpty();
-    assertThat(query.statuses()).isEmpty();
-    assertThat(query.assignees()).isEmpty();
-    assertThat(query.resolutions()).isEmpty();
-    assertThat(query.rules()).isEmpty();
-    assertThat(query.severities()).isEmpty();
-    assertThat(query.languages()).isEmpty();
-    assertThat(query.tags()).isEmpty();
-    assertThat(query.types()).isEmpty();
-    assertThat(query.owaspTop10()).isEmpty();
-    assertThat(query.sansTop25()).isEmpty();
-    assertThat(query.cwe()).isEmpty();
-    assertThat(query.createdAfterByProjectUuids()).isEmpty();
-  }
-
-  @Test
-  public void test_default_query() {
-    IssueQuery query = IssueQuery.builder().build();
-    assertThat(query.issueKeys()).isEmpty();
-    assertThat(query.projectUuids()).isEmpty();
-    assertThat(query.componentUuids()).isEmpty();
-    assertThat(query.moduleUuids()).isEmpty();
-    assertThat(query.statuses()).isEmpty();
-    assertThat(query.assignees()).isEmpty();
-    assertThat(query.resolutions()).isEmpty();
-    assertThat(query.rules()).isEmpty();
-    assertThat(query.severities()).isEmpty();
-    assertThat(query.languages()).isEmpty();
-    assertThat(query.tags()).isEmpty();
-    assertThat(query.types()).isEmpty();
-    assertThat(query.organizationUuid()).isNull();
-    assertThat(query.branchUuid()).isNull();
-    assertThat(query.assigned()).isNull();
-    assertThat(query.createdAfter()).isNull();
-    assertThat(query.createdBefore()).isNull();
-    assertThat(query.resolved()).isNull();
-    assertThat(query.sort()).isNull();
-    assertThat(query.createdAfterByProjectUuids()).isEmpty();
-  }
-
-  @Test
-  public void should_accept_null_sort() {
-    IssueQuery query = IssueQuery.builder().sort(null).build();
-    assertThat(query.sort()).isNull();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java
deleted file mode 100644 (file)
index b7dac4d..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import java.util.Map;
-import java.util.TimeZone;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.internal.TestSystem2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.issue.IssueDocTesting;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQuery.Builder;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.utils.DateUtils.parseDateTime;
-import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
-
-public class IssueIndexDebtTest {
-
-  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(TimeZone.getTimeZone("GMT-01:00"));
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public LightUserSessionRule userSessionRule = new LightUserSessionRule();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
-  private IssueIndex underTest;
-
-  @Before
-  public void setUp() {
-    underTest = new IssueIndex(es.client(), system2, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
-  }
-
-  @Test
-  public void facets_on_projects() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto, "ABCD");
-    ComponentDto project2 = ComponentTesting.newPrivateProjectDto(organizationDto, "EFGH");
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", ComponentTesting.newFileDto(project, null)).setEffort(10L),
-      IssueDocTesting.newDoc("I2", ComponentTesting.newFileDto(project, null)).setEffort(10L),
-      IssueDocTesting.newDoc("I3", ComponentTesting.newFileDto(project2, null)).setEffort(10L));
-
-    Facets facets = search("projectUuids");
-    assertThat(facets.getNames()).containsOnly("projectUuids", FACET_MODE_EFFORT);
-    assertThat(facets.get("projectUuids")).containsOnly(entry("ABCD", 20L), entry("EFGH", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
-  }
-
-  @Test
-  public void facets_on_components() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto(), "A");
-    ComponentDto file1 = ComponentTesting.newFileDto(project, null, "ABCD");
-    ComponentDto file2 = ComponentTesting.newFileDto(project, null, "BCDE");
-    ComponentDto file3 = ComponentTesting.newFileDto(project, null, "CDEF");
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", project).setEffort(10L),
-      IssueDocTesting.newDoc("I2", file1).setEffort(10L),
-      IssueDocTesting.newDoc("I3", file2).setEffort(10L),
-      IssueDocTesting.newDoc("I4", file2).setEffort(10L),
-      IssueDocTesting.newDoc("I5", file3).setEffort(10L));
-
-    Facets facets = search("fileUuids");
-    assertThat(facets.getNames()).containsOnly("fileUuids", FACET_MODE_EFFORT);
-    assertThat(facets.get("fileUuids"))
-      .containsOnly(entry("A", 10L), entry("ABCD", 10L), entry("BCDE", 20L), entry("CDEF", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 50L));
-  }
-
-  @Test
-  public void facets_on_directories() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file1 = ComponentTesting.newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
-    ComponentDto file2 = ComponentTesting.newFileDto(project, null).setPath("F2.xoo");
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file1).setDirectoryPath("/src/main/xoo").setEffort(10L),
-      IssueDocTesting.newDoc("I2", file2).setDirectoryPath("/").setEffort(10L));
-
-    Facets facets = search("directories");
-    assertThat(facets.getNames()).containsOnly("directories", FACET_MODE_EFFORT);
-    assertThat(facets.get("directories")).containsOnly(entry("/src/main/xoo", 10L), entry("/", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 20L));
-  }
-
-  @Test
-  public void facets_on_severities() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file).setSeverity(Severity.INFO).setEffort(10L),
-      IssueDocTesting.newDoc("I2", file).setSeverity(Severity.INFO).setEffort(10L),
-      IssueDocTesting.newDoc("I3", file).setSeverity(Severity.MAJOR).setEffort(10L));
-
-    Facets facets = search("severities");
-    assertThat(facets.getNames()).containsOnly("severities", FACET_MODE_EFFORT);
-    assertThat(facets.get("severities")).containsOnly(entry("INFO", 20L), entry("MAJOR", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
-  }
-
-  @Test
-  public void facets_on_statuses() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file).setStatus(STATUS_CLOSED).setEffort(10L),
-      IssueDocTesting.newDoc("I2", file).setStatus(STATUS_CLOSED).setEffort(10L),
-      IssueDocTesting.newDoc("I3", file).setStatus(STATUS_OPEN).setEffort(10L));
-
-    Facets facets = search("statuses");
-    assertThat(facets.getNames()).containsOnly("statuses", FACET_MODE_EFFORT);
-    assertThat(facets.get("statuses")).containsOnly(entry("CLOSED", 20L), entry("OPEN", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
-  }
-
-  @Test
-  public void facets_on_resolutions() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
-      IssueDocTesting.newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
-      IssueDocTesting.newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED).setEffort(10L));
-
-    Facets facets = search("resolutions");
-    assertThat(facets.getNames()).containsOnly("resolutions", FACET_MODE_EFFORT);
-    assertThat(facets.get("resolutions")).containsOnly(entry("FALSE-POSITIVE", 20L), entry("FIXED", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
-  }
-
-  @Test
-  public void facets_on_languages() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-    RuleKey ruleKey = RuleKey.of("repo", "X1");
-
-    indexIssues(IssueDocTesting.newDoc("I1", file).setLanguage("xoo").setEffort(10L));
-
-    Facets facets = search("languages");
-    assertThat(facets.getNames()).containsOnly("languages", FACET_MODE_EFFORT);
-    assertThat(facets.get("languages")).containsOnly(entry("xoo", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 10L));
-  }
-
-  private Facets search(String additionalFacet) {
-    return new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList(additionalFacet))), system2.getDefaultTimeZone());
-  }
-
-  @Test
-  public void facets_on_assignees() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file).setAssigneeUuid("uuid-steph").setEffort(10L),
-      IssueDocTesting.newDoc("I2", file).setAssigneeUuid("uuid-simon").setEffort(10L),
-      IssueDocTesting.newDoc("I3", file).setAssigneeUuid("uuid-simon").setEffort(10L),
-      IssueDocTesting.newDoc("I4", file).setAssigneeUuid(null).setEffort(10L));
-
-    Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("assignees"))), system2.getDefaultTimeZone());
-    assertThat(facets.getNames()).containsOnly("assignees", FACET_MODE_EFFORT);
-    assertThat(facets.get("assignees")).containsOnly(entry("uuid-steph", 10L), entry("uuid-simon", 20L), entry("", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
-  }
-
-  @Test
-  public void facets_on_authors() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", file).setAuthorLogin("steph").setEffort(10L),
-      IssueDocTesting.newDoc("I2", file).setAuthorLogin("simon").setEffort(10L),
-      IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L),
-      IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L));
-
-    Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("authors"))), system2.getDefaultTimeZone());
-    assertThat(facets.getNames()).containsOnly("authors", FACET_MODE_EFFORT);
-    assertThat(facets.get("authors")).containsOnly(entry("steph", 10L), entry("simon", 20L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
-  }
-
-  @Test
-  public void facet_on_created_at() {
-    SearchOptions searchOptions = fixtureForCreatedAtFacet();
-
-    Builder query = newQueryBuilder().createdBefore(parseDateTime("2016-01-01T00:00:00+0100"));
-    Map<String, Long> createdAt = new Facets(underTest.search(query.build(), searchOptions), system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2011-01-01", 10L),
-      entry("2012-01-01", 0L),
-      entry("2013-01-01", 0L),
-      entry("2014-01-01", 50L),
-      entry("2015-01-01", 10L));
-  }
-
-  @Test
-  public void deprecated_debt_facets() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto, "ABCD");
-    ComponentDto project2 = ComponentTesting.newPrivateProjectDto(organizationDto, "EFGH");
-
-    indexIssues(
-      IssueDocTesting.newDoc("I1", ComponentTesting.newFileDto(project, null)).setEffort(10L),
-      IssueDocTesting.newDoc("I2", ComponentTesting.newFileDto(project, null)).setEffort(10L),
-      IssueDocTesting.newDoc("I3", ComponentTesting.newFileDto(project2, null)).setEffort(10L));
-
-    Facets facets = new Facets(underTest.search(IssueQuery.builder().facetMode(DEPRECATED_FACET_MODE_DEBT).build(),
-      new SearchOptions().addFacets(asList("projectUuids"))), system2.getDefaultTimeZone());
-    assertThat(facets.getNames()).containsOnly("projectUuids", FACET_MODE_EFFORT);
-    assertThat(facets.get("projectUuids")).containsOnly(entry("ABCD", 20L), entry("EFGH", 10L));
-    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
-  }
-
-  private SearchOptions fixtureForCreatedAtFacet() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-
-    IssueDoc issue0 = IssueDocTesting.newDoc("ISSUE0", file).setEffort(10L).setFuncCreationDate(parseDateTime("2011-04-25T01:05:13+0100"));
-    IssueDoc issue1 = IssueDocTesting.newDoc("I1", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-01T12:34:56+0100"));
-    IssueDoc issue2 = IssueDocTesting.newDoc("I2", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-01T23:46:00+0100"));
-    IssueDoc issue3 = IssueDocTesting.newDoc("I3", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-02T12:34:56+0100"));
-    IssueDoc issue4 = IssueDocTesting.newDoc("I4", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-05T12:34:56+0100"));
-    IssueDoc issue5 = IssueDocTesting.newDoc("I5", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-20T12:34:56+0100"));
-    IssueDoc issue6 = IssueDocTesting.newDoc("I6", file).setEffort(10L).setFuncCreationDate(parseDateTime("2015-01-18T12:34:56+0100"));
-
-    indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
-
-    return new SearchOptions().addFacets("createdAt");
-  }
-
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    for (IssueDoc issue : issues) {
-      addIssueAuthorization(issue.projectUuid());
-    }
-  }
-
-  private void addIssueAuthorization(String projectUuid) {
-    PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(projectUuid, Qualifiers.PROJECT);
-    access.allowAnyone();
-    authorizationIndexerTester.allow(access);
-  }
-
-  private Builder newQueryBuilder() {
-    return IssueQuery.builder().facetMode(FACET_MODE_EFFORT);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java
deleted file mode 100644 (file)
index eda244b..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import java.util.Date;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.utils.System2;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.sonar.db.component.ComponentTesting.newBranchDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.component.ComponentTesting.newProjectBranch;
-import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
-import static org.sonar.server.issue.IssueDocTesting.newDoc;
-
-public class IssueIndexProjectStatisticsTest {
-
-  private System2 system2 = mock(System2.class);
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public LightUserSessionRule userSessionRule = new LightUserSessionRule();
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), null, new IssueIteratorFactory(null));
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
-
-  @Test
-  public void searchProjectStatistics_returns_empty_list_if_no_input() {
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(emptyList(), emptyList(), "unknownUser");
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_empty_list_if_the_input_does_not_match_anything() {
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList("unknownProjectUuid"), singletonList(1_111_234_567_890L), "unknownUser");
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_something() {
-    OrganizationDto organization = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(organization);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
-  }
-
-  @Test
-  public void searchProjectStatistics_does_not_return_results_if_assignee_does_not_match() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String user1Uuid = randomAlphanumeric(40);
-    String user2Uuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(user1Uuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), user2Uuid);
-
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_results_if_assignee_matches() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String user1Uuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(user1Uuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), user1Uuid);
-
-    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_results_if_functional_date_is_strictly_after_from_date() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
-  }
-
-  @Test
-  public void searchProjectStatistics_does_not_return_results_if_functional_date_is_same_as_from_date() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
-  }
-
-  @Test
-  public void searchProjectStatistics_does_not_return_resolved_issues() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(
-      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_REMOVED),
-      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_WONT_FIX));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void searchProjectStatistics_does_not_return_results_if_functional_date_is_before_from_date() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from - 1000L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_issue_count() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(
-      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue2", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue3", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result).extracting(ProjectStatistics::getIssueCount).containsExactly(3L);
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_issue_count_for_multiple_projects() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(org1);
-    ComponentDto project2 = newPrivateProjectDto(org1);
-    ComponentDto project3 = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(
-      newDoc("issue1", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue2", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue3", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-
-      newDoc("issue4", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue5", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(
-      asList(project1.uuid(), project2.uuid(), project3.uuid()),
-      asList(from, from, from),
-      userUuid);
-
-    assertThat(result)
-      .extracting(ProjectStatistics::getProjectUuid, ProjectStatistics::getIssueCount)
-      .containsExactlyInAnyOrder(
-        tuple(project1.uuid(), 3L),
-        tuple(project3.uuid(), 2L));
-  }
-
-  @Test
-  public void searchProjectStatistics_returns_max_date_for_multiple_projects() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(org1);
-    ComponentDto project2 = newPrivateProjectDto(org1);
-    ComponentDto project3 = newPrivateProjectDto(org1);
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_000L;
-    indexIssues(
-      newDoc("issue1", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1_000L)),
-      newDoc("issue2", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 2_000L)),
-      newDoc("issue3", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 3_000L)),
-
-      newDoc("issue4", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 4_000L)),
-      newDoc("issue5", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 5_000L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(
-      asList(project1.uuid(), project2.uuid(), project3.uuid()),
-      asList(from, from, from),
-      userUuid);
-
-    assertThat(result)
-      .extracting(ProjectStatistics::getProjectUuid, ProjectStatistics::getLastIssueDate)
-      .containsExactlyInAnyOrder(
-        tuple(project1.uuid(), from + 3_000L),
-        tuple(project3.uuid(), from + 5_000L));
-  }
-
-  @Test
-  public void searchProjectStatistics_return_branch_issues() {
-    OrganizationDto organization = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(organization);
-    ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey("branch"));
-    String userUuid = randomAlphanumeric(40);
-    long from = 1_111_234_567_890L;
-    indexIssues(
-      newDoc("issue1", branch).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
-      newDoc("issue2", branch).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 2L)),
-      newDoc("issue3", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
-
-    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
-
-    assertThat(result)
-      .extracting(ProjectStatistics::getIssueCount, ProjectStatistics::getProjectUuid, ProjectStatistics::getLastIssueDate)
-      .containsExactly(
-        tuple(2L, branch.uuid(), from + 2L),
-        tuple(1L, project.uuid(), from + 1L));
-  }
-
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    for (IssueDoc issue : issues) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(issue.projectUuid(), "TRK");
-      access.allowAnyone();
-      authorizationIndexerTester.allow(access);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
deleted file mode 100644 (file)
index eddde5f..0000000
+++ /dev/null
@@ -1,1753 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterators;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.OptionalInt;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.assertj.core.api.Fail;
-import org.assertj.core.groups.Tuple;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.internal.TestSystem2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.rule.index.RuleIndexer;
-import org.sonar.server.user.LightUserSessionRule;
-import org.sonar.server.view.index.ViewDoc;
-import org.sonar.server.view.index.ViewIndexer;
-
-import static com.google.common.collect.ImmutableSortedSet.of;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.TimeZone.getTimeZone;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.junit.rules.ExpectedException.none;
-import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.resources.Qualifiers.APP;
-import static org.sonar.api.rules.RuleType.BUG;
-import static org.sonar.api.rules.RuleType.CODE_SMELL;
-import static org.sonar.api.rules.RuleType.VULNERABILITY;
-import static org.sonar.api.utils.DateUtils.addDays;
-import static org.sonar.api.utils.DateUtils.parseDate;
-import static org.sonar.api.utils.DateUtils.parseDateTime;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.component.ComponentTesting.newModuleDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
-import static org.sonar.db.rule.RuleTesting.newRule;
-import static org.sonar.db.user.GroupTesting.newGroupDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
-import static org.sonar.server.issue.IssueDocTesting.newDoc;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
-
-public class IssueIndexTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public LightUserSessionRule userSessionRule = new LightUserSessionRule();
-  @Rule
-  public ExpectedException expectedException = none();
-  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
-  private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
-  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
-
-  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
-
-  @Test
-  public void filter_by_keys() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-
-    indexIssues(
-      newDoc("I1", newFileDto(project, null)),
-      newDoc("I2", newFileDto(project, null)));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().issueKeys(asList("I1", "I2")), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().issueKeys(singletonList("I1")), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().issueKeys(asList("I3", "I4")));
-  }
-
-  @Test
-  public void filter_by_projects() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto module = newModuleDto(project);
-    ComponentDto subModule = newModuleDto(module);
-
-    indexIssues(
-      newDoc("I1", project),
-      newDoc("I2", newFileDto(project, null)),
-      newDoc("I3", module),
-      newDoc("I4", newFileDto(module, null)),
-      newDoc("I5", subModule),
-      newDoc("I6", newFileDto(subModule, null)));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void facet_on_projectUuids() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(organizationDto, "ABCD");
-    ComponentDto project2 = newPrivateProjectDto(organizationDto, "EFGH");
-
-    indexIssues(
-      newDoc("I1", newFileDto(project, null)),
-      newDoc("I2", newFileDto(project, null)),
-      newDoc("I3", newFileDto(project2, null)));
-
-    assertThatFacetHasExactly(IssueQuery.builder(), "projectUuids", entry("ABCD", 2L), entry("EFGH", 1L));
-  }
-
-  @Test
-  public void filter_by_modules() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto module = newModuleDto(project);
-    ComponentDto subModule = newModuleDto(module);
-    ComponentDto file = newFileDto(subModule, null);
-
-    indexIssues(
-      newDoc("I3", module),
-      newDoc("I5", subModule),
-      newDoc("I2", file));
-
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(file.uuid())));
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(module.uuid())), "I3");
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(subModule.uuid())), "I2", "I5");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(project.uuid())));
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void filter_by_components_on_contextualized_search() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto module = newModuleDto(project);
-    ComponentDto subModule = newModuleDto(module);
-    ComponentDto file1 = newFileDto(project, null);
-    ComponentDto file2 = newFileDto(module, null);
-    ComponentDto file3 = newFileDto(subModule, null);
-    String view = "ABCD";
-    indexView(view, asList(project.uuid()));
-
-    indexIssues(
-      newDoc("I1", project),
-      newDoc("I2", file1),
-      newDoc("I3", module),
-      newDoc("I4", file2),
-      newDoc("I5", subModule),
-      newDoc("I6", file3));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(asList(file1.uuid(), file2.uuid(), file3.uuid())), "I2", "I4", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(file1.uuid())), "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().moduleRootUuids(singletonList(subModule.uuid())), "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().moduleRootUuids(singletonList(module.uuid())), "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view)), "I1", "I2", "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void filter_by_components_on_non_contextualized_search() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "project");
-    ComponentDto file1 = newFileDto(project, null, "file1");
-    ComponentDto module = newModuleDto(project).setUuid("module");
-    ComponentDto file2 = newFileDto(module, null, "file2");
-    ComponentDto subModule = newModuleDto(module).setUuid("subModule");
-    ComponentDto file3 = newFileDto(subModule, null, "file3");
-    String view = "ABCD";
-    indexView(view, asList(project.uuid()));
-
-    indexIssues(
-      newDoc("I1", project),
-      newDoc("I2", file1),
-      newDoc("I3", module),
-      newDoc("I4", file2),
-      newDoc("I5", subModule),
-      newDoc("I6", file3));
-
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view)), "I1", "I2", "I3", "I4", "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(module.uuid())), "I3", "I4");
-    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(subModule.uuid())), "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(file1.uuid())), "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(asList(file1.uuid(), file2.uuid(), file3.uuid())), "I2", "I4", "I6");
-  }
-
-  @Test
-  public void facets_on_components() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "A");
-    ComponentDto file1 = newFileDto(project, null, "ABCD");
-    ComponentDto file2 = newFileDto(project, null, "BCDE");
-    ComponentDto file3 = newFileDto(project, null, "CDEF");
-
-    indexIssues(
-      newDoc("I1", project),
-      newDoc("I2", file1),
-      newDoc("I3", file2),
-      newDoc("I4", file2),
-      newDoc("I5", file3));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "fileUuids", entry("A", 1L), entry("ABCD", 1L), entry("BCDE", 2L), entry("CDEF", 1L));
-  }
-
-  @Test
-  public void filter_by_directories() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file1 = newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
-    ComponentDto file2 = newFileDto(project, null).setPath("F2.xoo");
-
-    indexIssues(
-      newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
-      newDoc("I2", file2).setDirectoryPath("/"));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().directories(singletonList("/src/main/xoo")), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().directories(singletonList("/")), "I2");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().directories(singletonList("unknown")));
-  }
-
-  @Test
-  public void facets_on_directories() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file1 = newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
-    ComponentDto file2 = newFileDto(project, null).setPath("F2.xoo");
-
-    indexIssues(
-      newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
-      newDoc("I2", file2).setDirectoryPath("/"));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "directories", entry("/src/main/xoo", 1L), entry("/", 1L));
-  }
-
-  @Test
-  public void filter_by_portfolios() {
-    ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project1));
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    IssueDoc issueOnProject1 = newDoc(project1);
-    IssueDoc issueOnFile = newDoc(file);
-    IssueDoc issueOnProject2 = newDoc(project2);
-
-    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
-    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
-    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())), issueOnProject1.key(), issueOnFile.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio2.uuid())), issueOnProject2.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(portfolio1.uuid(), portfolio2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
-      issueOnFile.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
-    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void filter_by_portfolios_not_having_projects() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(organizationDto);
-    ComponentDto file1 = newFileDto(project1, null);
-    indexIssues(newDoc("I2", file1));
-    String view1 = "ABCD";
-    indexView(view1, emptyList());
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view1)));
-  }
-
-  @Test
-  public void do_not_return_issues_from_project_branch_when_filtering_by_portfolios() {
-    ComponentDto portfolio = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto project = db.components().insertMainBranch();
-    ComponentDto projectBranch = db.components().insertProjectBranch(project);
-    ComponentDto fileOnProjectBranch = db.components().insertComponent(newFileDto(projectBranch));
-    indexView(portfolio.uuid(), singletonList(project.uuid()));
-
-    IssueDoc issueOnProject = newDoc(project);
-    IssueDoc issueOnProjectBranch = newDoc(projectBranch);
-    IssueDoc issueOnFileOnProjectBranch = newDoc(fileOnProjectBranch);
-    indexIssues(issueOnProject, issueOnFileOnProjectBranch, issueOnProjectBranch);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())), issueOnProject.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())).projectUuids(singletonList(project.uuid())),
-      issueOnProject.key());
-    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())).projectUuids(singletonList(projectBranch.uuid())));
-  }
-
-  @Test
-  public void filter_one_issue_by_project_and_branch() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-    ComponentDto anotherbBranch = db.components().insertProjectBranch(project);
-
-    IssueDoc issueOnProject = newDoc(project);
-    IssueDoc issueOnBranch = newDoc(branch);
-    IssueDoc issueOnAnotherBranch = newDoc(anotherbBranch);
-    indexIssues(issueOnProject, issueOnBranch, issueOnAnotherBranch);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(branch.uuid())).branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
-    assertThatSearchReturnsOnly(
-      IssueQuery.builder().componentUuids(singletonList(branch.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()).mainBranch(false),
-      issueOnBranch.key());
-    assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
-  }
-
-  @Test
-  public void issues_from_branch_component_children() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto projectModule = db.components().insertComponent(newModuleDto(project));
-    ComponentDto projectFile = db.components().insertComponent(newFileDto(projectModule));
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
-    ComponentDto branchModule = db.components().insertComponent(newModuleDto(branch));
-    ComponentDto branchFile = db.components().insertComponent(newFileDto(branchModule));
-
-    indexIssues(
-      newDoc("I1", project),
-      newDoc("I2", projectFile),
-      newDoc("I3", projectModule),
-      newDoc("I4", branch),
-      newDoc("I5", branchModule),
-      newDoc("I6", branchFile));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()).mainBranch(false), "I4", "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(branchModule.uuid())).branchUuid(branch.uuid()).mainBranch(false), "I5", "I6");
-    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).branchUuid(branch.uuid()).mainBranch(false), "I6");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).mainBranch(false).branchUuid("unknown"));
-  }
-
-  @Test
-  public void issues_from_main_branch() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-
-    IssueDoc issueOnProject = newDoc(project);
-    IssueDoc issueOnBranch = newDoc(branch);
-    indexIssues(issueOnProject, issueOnBranch);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
-    assertThatSearchReturnsOnly(
-      IssueQuery.builder().componentUuids(singletonList(project.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true),
-      issueOnProject.key());
-  }
-
-  @Test
-  public void branch_issues_are_ignored_when_no_branch_param() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
-
-    IssueDoc projectIssue = newDoc(project);
-    IssueDoc branchIssue = newDoc(branch);
-    indexIssues(projectIssue, branchIssue);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder(), projectIssue.key());
-  }
-
-  @Test
-  public void filter_by_main_application() {
-    ComponentDto application1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto application2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project1));
-    ComponentDto project2 = db.components().insertPrivateProject();
-    indexView(application1.uuid(), singletonList(project1.uuid()));
-    indexView(application2.uuid(), singletonList(project2.uuid()));
-
-    IssueDoc issueOnProject1 = newDoc(project1);
-    IssueDoc issueOnFile = newDoc(file);
-    IssueDoc issueOnProject2 = newDoc(project2);
-    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())), issueOnProject1.key(), issueOnFile.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application2.uuid())), issueOnProject2.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(application1.uuid(), application2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
-      issueOnFile.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
-    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void filter_by_application_branch() {
-    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP));
-    ComponentDto branch1 = db.components().insertProjectBranch(application);
-    ComponentDto branch2 = db.components().insertProjectBranch(application);
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project1));
-    ComponentDto project2 = db.components().insertPrivateProject();
-    indexView(branch1.uuid(), singletonList(project1.uuid()));
-    indexView(branch2.uuid(), singletonList(project2.uuid()));
-
-    IssueDoc issueOnProject1 = newDoc(project1);
-    IssueDoc issueOnFile = newDoc(file);
-    IssueDoc issueOnProject2 = newDoc(project2);
-    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
-      issueOnProject1.key(), issueOnFile.key());
-    assertThatSearchReturnsOnly(
-      IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).projectUuids(singletonList(project1.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
-      issueOnProject1.key(), issueOnFile.key());
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).fileUuids(singletonList(file.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
-      issueOnFile.key());
-    assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
-  }
-
-  @Test
-  public void filter_by_application_branch_having_project_branches() {
-    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP).setDbKey("app"));
-    ComponentDto applicationBranch1 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch1"));
-    ComponentDto applicationBranch2 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch2"));
-    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setDbKey("prj1"));
-    ComponentDto project1Branch1 = db.components().insertProjectBranch(project1);
-    ComponentDto fileOnProject1Branch1 = db.components().insertComponent(newFileDto(project1Branch1));
-    ComponentDto project1Branch2 = db.components().insertProjectBranch(project1);
-    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setDbKey("prj2"));
-    indexView(applicationBranch1.uuid(), asList(project1Branch1.uuid(), project2.uuid()));
-    indexView(applicationBranch2.uuid(), singletonList(project1Branch2.uuid()));
-
-    IssueDoc issueOnProject1 = newDoc(project1);
-    IssueDoc issueOnProject1Branch1 = newDoc(project1Branch1);
-    IssueDoc issueOnFileOnProject1Branch1 = newDoc(fileOnProject1Branch1);
-    IssueDoc issueOnProject1Branch2 = newDoc(project1Branch2);
-    IssueDoc issueOnProject2 = newDoc(project2);
-    indexIssues(issueOnProject1, issueOnProject1Branch1, issueOnFileOnProject1Branch1, issueOnProject1Branch2, issueOnProject2);
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).branchUuid(applicationBranch1.uuid()).mainBranch(false),
-      issueOnProject1Branch1.key(), issueOnFileOnProject1Branch1.key(), issueOnProject2.key());
-    assertThatSearchReturnsOnly(
-      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).projectUuids(singletonList(project1.uuid())).branchUuid(applicationBranch1.uuid()).mainBranch(false),
-      issueOnProject1Branch1.key(), issueOnFileOnProject1Branch1.key());
-    assertThatSearchReturnsOnly(
-      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).fileUuids(singletonList(fileOnProject1Branch1.uuid())).branchUuid(applicationBranch1.uuid())
-        .mainBranch(false),
-      issueOnFileOnProject1Branch1.key());
-    assertThatSearchReturnsEmpty(
-      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).projectUuids(singletonList("unknown")).branchUuid(applicationBranch1.uuid()).mainBranch(false));
-  }
-
-  @Test
-  public void filter_by_created_after_by_projects() {
-    Date now = new Date();
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(organizationDto);
-    IssueDoc project1Issue1 = newDoc(project1).setFuncCreationDate(addDays(now, -10));
-    IssueDoc project1Issue2 = newDoc(project1).setFuncCreationDate(addDays(now, -20));
-    ComponentDto project2 = newPrivateProjectDto(organizationDto);
-    IssueDoc project2Issue1 = newDoc(project2).setFuncCreationDate(addDays(now, -15));
-    IssueDoc project2Issue2 = newDoc(project2).setFuncCreationDate(addDays(now, -30));
-    indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);
-
-    // Search for issues of project 1 having less than 15 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
-      project1Issue1.key());
-
-    // Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
-        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
-      project1Issue1.key(), project2Issue1.key());
-
-    // Search for issues of project 1 having less than 30 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
-      project1Issue1.key(), project1Issue2.key());
-
-    // Search for issues of project 1 and project 2 having less than 5 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
-        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
-  }
-
-  @Test
-  public void filter_by_severities() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setSeverity(Severity.INFO),
-      newDoc("I2", file).setSeverity(Severity.MAJOR));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().severities(asList(Severity.INFO, Severity.MAJOR)), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().severities(singletonList(Severity.INFO)), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().severities(singletonList(Severity.BLOCKER)));
-  }
-
-  @Test
-  public void facets_on_severities() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setSeverity(Severity.INFO),
-      newDoc("I2", file).setSeverity(Severity.INFO),
-      newDoc("I3", file).setSeverity(Severity.MAJOR));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 2L), entry("MAJOR", 1L));
-  }
-
-  @Test
-  public void filter_by_statuses() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED),
-      newDoc("I2", file).setStatus(Issue.STATUS_OPEN));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().statuses(asList(Issue.STATUS_CLOSED, Issue.STATUS_OPEN)), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().statuses(singletonList(Issue.STATUS_CLOSED)), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().statuses(singletonList(Issue.STATUS_CONFIRMED)));
-  }
-
-  @Test
-  public void facets_on_statuses() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED),
-      newDoc("I2", file).setStatus(Issue.STATUS_CLOSED),
-      newDoc("I3", file).setStatus(Issue.STATUS_OPEN));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "statuses", entry("CLOSED", 2L), entry("OPEN", 1L));
-  }
-
-  @Test
-  public void filter_by_resolutions() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      newDoc("I2", file).setResolution(Issue.RESOLUTION_FIXED));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().resolutions(asList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().resolutions(singletonList(Issue.RESOLUTION_FALSE_POSITIVE)), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().resolutions(singletonList(Issue.RESOLUTION_REMOVED)));
-  }
-
-  @Test
-  public void facets_on_resolutions() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
-      newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L));
-  }
-
-  @Test
-  public void filter_by_resolved() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("I2", file).setStatus(Issue.STATUS_OPEN).setResolution(null),
-      newDoc("I3", file).setStatus(Issue.STATUS_OPEN).setResolution(null));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(true), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(false), "I2", "I3");
-    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(null), "I1", "I2", "I3");
-  }
-
-  @Test
-  public void filter_by_rules() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    RuleDefinitionDto ruleDefinitionDto = newRule();
-    db.rules().insert(ruleDefinitionDto);
-
-    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().rules(singletonList(ruleDefinitionDto)), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().rules(singletonList(new RuleDefinitionDto().setId(-1))));
-  }
-
-  @Test
-  public void filter_by_languages() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    RuleDefinitionDto ruleDefinitionDto = newRule();
-    db.rules().insert(ruleDefinitionDto);
-
-    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()).setLanguage("xoo"));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().languages(singletonList("xoo")), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().languages(singletonList("unknown")));
-  }
-
-  @Test
-  public void facets_on_languages() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    RuleDefinitionDto ruleDefinitionDto = newRule();
-    db.rules().insert(ruleDefinitionDto);
-
-    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()).setLanguage("xoo"));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "languages", entry("xoo", 1L));
-  }
-
-  @Test
-  public void filter_by_assignees() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
-      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
-      newDoc("I3", file).setAssigneeUuid(null));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().assigneeUuids(singletonList("steph-uuid")), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().assigneeUuids(asList("steph-uuid", "marcel-uuid")), "I1", "I2");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().assigneeUuids(singletonList("unknown")));
-  }
-
-  @Test
-  public void facets_on_assignees() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
-      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
-      newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
-      newDoc("I4", file).setAssigneeUuid(null));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "assignees", entry("steph-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
-  }
-
-  @Test
-  public void facets_on_assignees_supports_dashes() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAssigneeUuid("j-b-uuid"),
-      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
-      newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
-      newDoc("I4", file).setAssigneeUuid(null));
-
-    assertThatFacetHasOnly(IssueQuery.builder().assigneeUuids(singletonList("j-b")),
-      "assignees", entry("j-b-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
-  }
-
-  @Test
-  public void filter_by_assigned() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
-      newDoc("I2", file).setAssigneeUuid(null),
-      newDoc("I3", file).setAssigneeUuid(null));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(true), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(false), "I2", "I3");
-    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(null), "I1", "I2", "I3");
-  }
-
-  @Test
-  public void filter_by_authors() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAuthorLogin("steph"),
-      newDoc("I2", file).setAuthorLogin("marcel"),
-      newDoc("I3", file).setAssigneeUuid(null));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().authors(singletonList("steph")), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().authors(asList("steph", "marcel")), "I1", "I2");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().authors(singletonList("unknown")));
-  }
-
-  @Test
-  public void facets_on_authors() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setAuthorLogin("steph"),
-      newDoc("I2", file).setAuthorLogin("marcel"),
-      newDoc("I3", file).setAuthorLogin("marcel"),
-      newDoc("I4", file).setAuthorLogin(null));
-
-    assertThatFacetHasOnly(IssueQuery.builder(), "authors", entry("steph", 1L), entry("marcel", 2L));
-  }
-
-  @Test
-  public void filter_by_created_after() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
-      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-19")), "I1", "I2");
-    // Lower bound is included
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-20")), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-21")), "I2");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAfter(parseDate("2014-09-25")));
-  }
-
-  @Test
-  public void filter_by_created_before() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
-      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
-
-    assertThatSearchReturnsEmpty(IssueQuery.builder().createdBefore(parseDate("2014-09-19")));
-    // Upper bound is excluded
-    assertThatSearchReturnsEmpty(IssueQuery.builder().createdBefore(parseDate("2014-09-20")));
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdBefore(parseDate("2014-09-21")), "I1");
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdBefore(parseDate("2014-09-25")), "I1", "I2");
-  }
-
-  @Test
-  public void filter_by_created_after_and_before() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
-      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
-
-    // 19 < createdAt < 25
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-19")).createdBefore(parseDate("2014-09-25")),
-      "I1", "I2");
-
-    // 20 < createdAt < 25: excludes first issue
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-25")), "I1", "I2");
-
-    // 21 < createdAt < 25
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-25")), "I2");
-
-    // 21 < createdAt < 24
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-24")), "I2");
-
-    // 21 < createdAt < 23: excludes second issue
-    assertThatSearchReturnsEmpty(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-23")));
-
-    // 19 < createdAt < 21: only first issue
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-19")).createdBefore(parseDate("2014-09-21")), "I1");
-
-    // 20 < createdAt < 20: exception
-    expectedException.expect(IllegalArgumentException.class);
-    underTest.search(IssueQuery.builder()
-      .createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-20"))
-      .build(), new SearchOptions());
-  }
-
-  @Test
-  public void filter_by_create_after_and_before_take_into_account_timezone() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-20T00:00:00+0100")),
-      newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDateTime("2014-09-19T23:00:00+0000")).createdBefore(parseDateTime("2014-09-22T23:00:01+0000")),
-      "I1", "I2");
-
-    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAfter(parseDateTime("2014-09-19T23:00:01+0000")).createdBefore(parseDateTime("2014-09-22T23:00:00+0000")));
-  }
-
-  @Test
-  public void filter_by_created_before_must_be_lower_than_after() {
-    try {
-      underTest.search(IssueQuery.builder().createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-19")).build(),
-        new SearchOptions());
-      Fail.failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
-    } catch (IllegalArgumentException exception) {
-      assertThat(exception.getMessage()).isEqualTo("Start bound cannot be larger or equal to end bound");
-    }
-  }
-
-  @Test
-  public void fail_if_created_before_equals_created_after() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Start bound cannot be larger or equal to end bound");
-
-    underTest.search(IssueQuery.builder().createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-20")).build(), new SearchOptions());
-  }
-
-  @Test
-  public void filter_by_created_after_must_not_be_in_future() {
-    try {
-      underTest.search(IssueQuery.builder().createdAfter(new Date(Long.MAX_VALUE)).build(), new SearchOptions());
-      Fail.failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
-    } catch (IllegalArgumentException exception) {
-      assertThat(exception.getMessage()).isEqualTo("Start bound cannot be in the future");
-    }
-  }
-
-  @Test
-  public void filter_by_created_at() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder().createdAt(parseDate("2014-09-20")), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAt(parseDate("2014-09-21")));
-  }
-
-  @Test
-  public void facet_on_created_at_with_less_than_20_days() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    IssueQuery query = IssueQuery.builder()
-      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
-      .createdBefore(parseDateTime("2014-09-08T00:00:00+0100"))
-      .checkAuthorization(false)
-      .build();
-    SearchResponse result = underTest.search(query, options);
-    Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(buckets).containsOnly(
-      entry("2014-08-31", 0L),
-      entry("2014-09-01", 2L),
-      entry("2014-09-02", 1L),
-      entry("2014-09-03", 0L),
-      entry("2014-09-04", 0L),
-      entry("2014-09-05", 1L),
-      entry("2014-09-06", 0L),
-      entry("2014-09-07", 0L));
-  }
-
-  @Test
-  public void facet_on_created_at_with_less_than_20_weeks() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
-      .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
-      options);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2014-08-25", 0L),
-      entry("2014-09-01", 4L),
-      entry("2014-09-08", 0L),
-      entry("2014-09-15", 1L));
-  }
-
-  @Test
-  public void facet_on_created_at_with_less_than_20_months() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
-      .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
-      options);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2014-08-01", 0L),
-      entry("2014-09-01", 5L),
-      entry("2014-10-01", 0L),
-      entry("2014-11-01", 0L),
-      entry("2014-12-01", 0L),
-      entry("2015-01-01", 1L));
-  }
-
-  @Test
-  public void facet_on_created_at_with_more_than_20_months() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
-      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
-      options);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2010-01-01", 0L),
-      entry("2011-01-01", 1L),
-      entry("2012-01-01", 0L),
-      entry("2013-01-01", 0L),
-      entry("2014-01-01", 5L),
-      entry("2015-01-01", 1L));
-  }
-
-  @Test
-  public void facet_on_created_at_with_one_day() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
-      .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
-      options);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2014-09-01", 2L));
-  }
-
-  @Test
-  public void facet_on_created_at_with_bounds_outside_of_data() {
-    SearchOptions options = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdAfter(parseDateTime("2009-01-01T00:00:00+0100"))
-      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100"))
-      .build(), options);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2008-01-01", 0L),
-      entry("2009-01-01", 0L),
-      entry("2010-01-01", 0L),
-      entry("2011-01-01", 1L),
-      entry("2012-01-01", 0L),
-      entry("2013-01-01", 0L),
-      entry("2014-01-01", 5L),
-      entry("2015-01-01", 1L));
-  }
-
-  @Test
-  public void facet_on_created_at_without_start_bound() {
-    SearchOptions searchOptions = fixtureForCreatedAtFacet();
-
-    SearchResponse result = underTest.search(IssueQuery.builder()
-      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
-      searchOptions);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).containsOnly(
-      entry("2011-01-01", 1L),
-      entry("2012-01-01", 0L),
-      entry("2013-01-01", 0L),
-      entry("2014-01-01", 5L),
-      entry("2015-01-01", 1L));
-  }
-
-  @Test
-  public void facet_on_created_at_without_issues() {
-    SearchOptions searchOptions = new SearchOptions().addFacets("createdAt");
-
-    SearchResponse result = underTest.search(IssueQuery.builder().build(), searchOptions);
-    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
-    assertThat(createdAt).isNull();
-  }
-
-  private SearchOptions fixtureForCreatedAtFacet() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    IssueDoc issue0 = newDoc("ISSUE0", file).setFuncCreationDate(parseDateTime("2011-04-25T00:05:13+0000"));
-    IssueDoc issue1 = newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-01T12:34:56+0100"));
-    IssueDoc issue2 = newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-01T10:46:00-1200"));
-    IssueDoc issue3 = newDoc("I3", file).setFuncCreationDate(parseDateTime("2014-09-02T23:34:56+1200"));
-    IssueDoc issue4 = newDoc("I4", file).setFuncCreationDate(parseDateTime("2014-09-05T12:34:56+0100"));
-    IssueDoc issue5 = newDoc("I5", file).setFuncCreationDate(parseDateTime("2014-09-20T12:34:56+0100"));
-    IssueDoc issue6 = newDoc("I6", file).setFuncCreationDate(parseDateTime("2015-01-18T12:34:56+0100"));
-
-    indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
-
-    return new SearchOptions().addFacets("createdAt");
-  }
-
-  @Test
-  public void paging() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    for (int i = 0; i < 12; i++) {
-      indexIssues(newDoc("I" + i, file));
-    }
-
-    IssueQuery.Builder query = IssueQuery.builder();
-    // There are 12 issues in total, with 10 issues per page, the page 2 should only contain 2 elements
-    SearchResponse result = underTest.search(query.build(), new SearchOptions().setPage(2, 10));
-    assertThat(result.getHits().hits()).hasSize(2);
-    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
-
-    result = underTest.search(IssueQuery.builder().build(), new SearchOptions().setOffset(0).setLimit(5));
-    assertThat(result.getHits().hits()).hasSize(5);
-    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
-
-    result = underTest.search(IssueQuery.builder().build(), new SearchOptions().setOffset(2).setLimit(0));
-    assertThat(result.getHits().hits()).hasSize(10);
-    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
-  }
-
-  @Test
-  public void search_with_max_limit() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    List<IssueDoc> issues = new ArrayList<>();
-    for (int i = 0; i < 500; i++) {
-      String key = "I" + i;
-      issues.add(newDoc(key, file));
-    }
-    indexIssues(issues.toArray(new IssueDoc[] {}));
-
-    IssueQuery.Builder query = IssueQuery.builder();
-    SearchResponse result = underTest.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE));
-    assertThat(result.getHits().hits()).hasSize(SearchOptions.MAX_LIMIT);
-  }
-
-  @Test
-  public void sort_by_status() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setStatus(Issue.STATUS_OPEN),
-      newDoc("I2", file).setStatus(Issue.STATUS_CLOSED),
-      newDoc("I3", file).setStatus(Issue.STATUS_REOPENED));
-
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(true);
-    assertThatSearchReturnsOnly(query, "I2", "I1", "I3");
-
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(false);
-    assertThatSearchReturnsOnly(query, "I3", "I1", "I2");
-  }
-
-  @Test
-  public void sort_by_severity() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setSeverity(Severity.BLOCKER),
-      newDoc("I2", file).setSeverity(Severity.INFO),
-      newDoc("I3", file).setSeverity(Severity.MINOR),
-      newDoc("I4", file).setSeverity(Severity.CRITICAL),
-      newDoc("I5", file).setSeverity(Severity.MAJOR));
-
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(true);
-    assertThatSearchReturnsOnly(query, "I2", "I3", "I5", "I4", "I1");
-
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(false);
-    assertThatSearchReturnsOnly(query, "I1", "I4", "I5", "I3", "I2");
-  }
-
-  @Test
-  public void sort_by_creation_date() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-24T00:00:00+0100")));
-
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(true);
-    SearchResponse result = underTest.search(query.build(), new SearchOptions());
-    assertThatSearchReturnsOnly(query, "I1", "I2");
-
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(false);
-    assertThatSearchReturnsOnly(query, "I2", "I1");
-  }
-
-  @Test
-  public void sort_by_update_date() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncUpdateDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("I2", file).setFuncUpdateDate(parseDateTime("2014-09-24T00:00:00+0100")));
-
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(true);
-    SearchResponse result = underTest.search(query.build(), new SearchOptions());
-    assertThatSearchReturnsOnly(query, "I1", "I2");
-
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(false);
-    assertThatSearchReturnsOnly(query, "I2", "I1");
-  }
-
-  @Test
-  public void sort_by_close_date() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-
-    indexIssues(
-      newDoc("I1", file).setFuncCloseDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("I2", file).setFuncCloseDate(parseDateTime("2014-09-24T00:00:00+0100")),
-      newDoc("I3", file).setFuncCloseDate(null));
-
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(true);
-    SearchResponse result = underTest.search(query.build(), new SearchOptions());
-    assertThatSearchReturnsOnly(query, "I3", "I1", "I2");
-
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(false);
-    assertThatSearchReturnsOnly(query, "I2", "I1", "I3");
-  }
-
-  @Test
-  public void sort_by_file_and_line() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file1 = newFileDto(project, null, "F1").setPath("src/main/xoo/org/sonar/samples/File.xoo");
-    ComponentDto file2 = newFileDto(project, null, "F2").setPath("src/main/xoo/org/sonar/samples/File2.xoo");
-
-    indexIssues(
-      // file F1
-      newDoc("F1_2", file1).setLine(20),
-      newDoc("F1_1", file1).setLine(null),
-      newDoc("F1_3", file1).setLine(25),
-
-      // file F2
-      newDoc("F2_1", file2).setLine(9),
-      newDoc("F2_2", file2).setLine(109),
-      // two issues on the same line -> sort by key
-      newDoc("F2_3", file2).setLine(109));
-
-    // ascending sort -> F1 then F2. Line "0" first.
-    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(true);
-    assertThatSearchReturnsOnly(query, "F1_1", "F1_2", "F1_3", "F2_1", "F2_2", "F2_3");
-
-    // descending sort -> F2 then F1
-    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(false);
-    assertThatSearchReturnsOnly(query, "F2_3", "F2_2", "F2_1", "F1_3", "F1_2", "F1_1");
-  }
-
-  @Test
-  public void default_sort_is_by_creation_date_then_project_then_file_then_line_then_issue_key() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(organizationDto, "P1");
-    ComponentDto file1 = newFileDto(project1, null, "F1").setPath("src/main/xoo/org/sonar/samples/File.xoo");
-    ComponentDto file2 = newFileDto(project1, null, "F2").setPath("src/main/xoo/org/sonar/samples/File2.xoo");
-
-    ComponentDto project2 = newPrivateProjectDto(organizationDto, "P2");
-    ComponentDto file3 = newFileDto(project2, null, "F3").setPath("src/main/xoo/org/sonar/samples/File3.xoo");
-
-    indexIssues(
-      // file F1 from project P1
-      newDoc("F1_1", file1).setLine(20).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("F1_2", file1).setLine(null).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("F1_3", file1).setLine(25).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-
-      // file F2 from project P1
-      newDoc("F2_1", file2).setLine(9).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      newDoc("F2_2", file2).setLine(109).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-      // two issues on the same line -> sort by key
-      newDoc("F2_3", file2).setLine(109).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
-
-      // file F3 from project P2
-      newDoc("F3_1", file3).setLine(20).setFuncCreationDate(parseDateTime("2014-09-24T00:00:00+0100")),
-      newDoc("F3_2", file3).setLine(20).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")));
-
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "F3_1", "F1_2", "F1_1", "F1_3", "F2_1", "F2_2", "F2_3", "F3_2");
-  }
-
-  @Test
-  public void authorized_issues_on_groups() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(org);
-    ComponentDto project2 = newPrivateProjectDto(org);
-    ComponentDto project3 = newPrivateProjectDto(org);
-    ComponentDto file1 = newFileDto(project1, null);
-    ComponentDto file2 = newFileDto(project2, null);
-    ComponentDto file3 = newFileDto(project3, null);
-    GroupDto group1 = newGroupDto();
-    GroupDto group2 = newGroupDto();
-
-    // project1 can be seen by group1
-    indexIssue(newDoc("I1", file1));
-    authorizationIndexerTester.allowOnlyGroup(project1, group1);
-    // project2 can be seen by group2
-    indexIssue(newDoc("I2", file2));
-    authorizationIndexerTester.allowOnlyGroup(project2, group2);
-    // project3 can be seen by nobody
-    indexIssue(newDoc("I3", file3));
-
-    userSessionRule.logIn().setGroups(group1);
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
-
-    userSessionRule.logIn().setGroups(group2);
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I2");
-
-    userSessionRule.logIn().setGroups(group1, group2);
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1", "I2");
-
-    GroupDto otherGroup = newGroupDto();
-    userSessionRule.logIn().setGroups(otherGroup);
-    assertThatSearchReturnsEmpty(IssueQuery.builder());
-
-    userSessionRule.logIn().setGroups(group1, group2);
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project3.uuid())));
-  }
-
-  @Test
-  public void authorized_issues_on_user() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(org);
-    ComponentDto project2 = newPrivateProjectDto(org);
-    ComponentDto project3 = newPrivateProjectDto(org);
-    ComponentDto file1 = newFileDto(project1, null);
-    ComponentDto file2 = newFileDto(project2, null);
-    ComponentDto file3 = newFileDto(project3, null);
-    UserDto user1 = newUserDto();
-    UserDto user2 = newUserDto();
-
-    // project1 can be seen by john, project2 by max, project3 cannot be seen by anyone
-    indexIssue(newDoc("I1", file1));
-    authorizationIndexerTester.allowOnlyUser(project1, user1);
-    indexIssue(newDoc("I2", file2));
-    authorizationIndexerTester.allowOnlyUser(project2, user2);
-    indexIssue(newDoc("I3", file3));
-
-    userSessionRule.logIn(user1);
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
-    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(asList(project3.getDbKey())));
-
-    userSessionRule.logIn(user2);
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I2");
-
-    // another user
-    userSessionRule.logIn(newUserDto());
-    assertThatSearchReturnsEmpty(IssueQuery.builder());
-  }
-
-  @Test
-  public void root_user_is_authorized_to_access_all_issues() {
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    indexIssue(newDoc("I1", project));
-    userSessionRule.logIn().setRoot();
-
-    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
-  }
-
-  @Test
-  public void list_tags() {
-    RuleDefinitionDto r1 = db.rules().insert();
-    RuleDefinitionDto r2 = db.rules().insert();
-    ruleIndexer.commitAndIndex(db.getSession(), asList(r1.getId(), r2.getId()));
-
-    OrganizationDto org = db.organizations().insert();
-    OrganizationDto anotherOrg = db.organizations().insert();
-    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
-    ComponentDto file = newFileDto(project, null);
-    indexIssues(
-      newDoc("I42", file).setOrganizationUuid(anotherOrg.getUuid()).setRuleId(r1.getId()).setTags(of("another")),
-      newDoc("I1", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention", "java8", "bug")),
-      newDoc("I2", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention", "bug")),
-      newDoc("I3", file).setOrganizationUuid(org.getUuid()).setRuleId(r2.getId()),
-      newDoc("I4", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention")));
-
-    assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug");
-    assertThat(underTest.listTags(org, null, 2)).containsOnly("bug", "convention");
-    assertThat(underTest.listTags(org, "vent", 100)).containsOnly("convention");
-    assertThat(underTest.listTags(org, null, 1)).containsOnly("bug");
-    assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug");
-    assertThat(underTest.listTags(org, "invalidRegexp[", 100)).isEmpty();
-    assertThat(underTest.listTags(null, null, 100)).containsExactlyInAnyOrder("another", "convention", "java8", "bug");
-  }
-
-  @Test
-  public void fail_to_list_tags_when_size_greater_than_500() {
-    OrganizationDto organization = db.organizations().insert();
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Page size must be lower than or equals to 500");
-
-    underTest.listTags(organization, null, 501);
-  }
-
-  @Test
-  public void test_listAuthors() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("issue1", project).setAuthorLogin("luke.skywalker"),
-      newDoc("issue2", project).setAuthorLogin("luke@skywalker.name"),
-      newDoc("issue3", project).setAuthorLogin(null),
-      newDoc("issue4", project).setAuthorLogin("anakin@skywalker.name"));
-    IssueQuery query = IssueQuery.builder()
-      .checkAuthorization(false)
-      .build();
-
-    assertThat(underTest.listAuthors(query, null, 5)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name");
-    assertThat(underTest.listAuthors(query, null, 2)).containsExactly("anakin@skywalker.name", "luke.skywalker");
-    assertThat(underTest.listAuthors(query, "uke", 5)).containsExactly("luke.skywalker", "luke@skywalker.name");
-    assertThat(underTest.listAuthors(query, null, 1)).containsExactly("anakin@skywalker.name");
-    assertThat(underTest.listAuthors(query, null, Integer.MAX_VALUE)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name");
-  }
-
-  @Test
-  public void listAuthors_escapes_regexp_special_characters() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("issue1", project).setAuthorLogin("name++"));
-    IssueQuery query = IssueQuery.builder()
-      .checkAuthorization(false)
-      .build();
-
-    assertThat(underTest.listAuthors(query, "invalidRegexp[", 5)).isEmpty();
-    assertThat(underTest.listAuthors(query, "nam+", 5)).isEmpty();
-    assertThat(underTest.listAuthors(query, "name+", 5)).containsExactly("name++");
-    assertThat(underTest.listAuthors(query, ".*", 5)).isEmpty();
-  }
-
-  @Test
-  public void filter_by_organization() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto projectInOrg1 = newPrivateProjectDto(org1);
-    OrganizationDto org2 = newOrganizationDto();
-    ComponentDto projectInOrg2 = newPrivateProjectDto(org2);
-
-    indexIssues(newDoc("issueInOrg1", projectInOrg1), newDoc("issue1InOrg2", projectInOrg2), newDoc("issue2InOrg2", projectInOrg2));
-
-    verifyOrganizationFilter(org1.getUuid(), "issueInOrg1");
-    verifyOrganizationFilter(org2.getUuid(), "issue1InOrg2", "issue2InOrg2");
-    verifyOrganizationFilter("does_not_exist");
-  }
-
-  @Test
-  public void filter_by_organization_and_project() {
-    OrganizationDto org1 = newOrganizationDto();
-    ComponentDto projectInOrg1 = newPrivateProjectDto(org1);
-    OrganizationDto org2 = newOrganizationDto();
-    ComponentDto projectInOrg2 = newPrivateProjectDto(org2);
-
-    indexIssues(newDoc("issueInOrg1", projectInOrg1), newDoc("issue1InOrg2", projectInOrg2), newDoc("issue2InOrg2", projectInOrg2));
-
-    // no conflict
-    IssueQuery.Builder query = IssueQuery.builder().organizationUuid(org1.getUuid()).projectUuids(singletonList(projectInOrg1.uuid()));
-    assertThatSearchReturnsOnly(query, "issueInOrg1");
-
-    // conflict
-    query = IssueQuery.builder().organizationUuid(org1.getUuid()).projectUuids(singletonList(projectInOrg2.uuid()));
-    assertThatSearchReturnsEmpty(query);
-  }
-
-  @Test
-  public void countTags() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("issue1", project).setTags(ImmutableSet.of("convention", "java8", "bug")),
-      newDoc("issue2", project).setTags(ImmutableSet.of("convention", "bug")),
-      newDoc("issue3", project).setTags(emptyList()),
-      newDoc("issue4", project).setTags(ImmutableSet.of("convention", "java8", "bug")).setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("issue5", project).setTags(ImmutableSet.of("convention")));
-
-    assertThat(underTest.countTags(projectQuery(project.uuid()), 5)).containsOnly(entry("convention", 3L), entry("bug", 2L), entry("java8", 1L));
-    assertThat(underTest.countTags(projectQuery(project.uuid()), 2)).contains(entry("convention", 3L), entry("bug", 2L)).doesNotContainEntry("java8", 1L);
-    assertThat(underTest.countTags(projectQuery("other"), 10)).isEmpty();
-  }
-
-  @Test
-  public void searchBranchStatistics() {
-    ComponentDto project = db.components().insertMainBranch();
-    ComponentDto branch1 = db.components().insertProjectBranch(project);
-    ComponentDto branch2 = db.components().insertProjectBranch(project);
-    ComponentDto branch3 = db.components().insertProjectBranch(project);
-    ComponentDto fileOnBranch3 = db.components().insertComponent(newFileDto(branch3));
-    indexIssues(newDoc(project),
-      newDoc(branch1).setType(BUG).setResolution(null), newDoc(branch1).setType(VULNERABILITY).setResolution(null), newDoc(branch1).setType(CODE_SMELL).setResolution(null),
-      newDoc(branch1).setType(CODE_SMELL).setResolution(RESOLUTION_FIXED),
-      newDoc(branch3).setType(CODE_SMELL).setResolution(null), newDoc(branch3).setType(CODE_SMELL).setResolution(null),
-      newDoc(fileOnBranch3).setType(CODE_SMELL).setResolution(null), newDoc(fileOnBranch3).setType(CODE_SMELL).setResolution(RESOLUTION_FIXED));
-
-    List<BranchStatistics> branchStatistics = underTest.searchBranchStatistics(project.uuid(), asList(branch1.uuid(), branch2.uuid(), branch3.uuid()));
-
-    assertThat(branchStatistics).extracting(BranchStatistics::getBranchUuid, BranchStatistics::getBugs, BranchStatistics::getVulnerabilities, BranchStatistics::getCodeSmells)
-      .containsExactlyInAnyOrder(
-        tuple(branch1.uuid(), 1L, 1L, 1L),
-        tuple(branch3.uuid(), 0L, 0L, 3L));
-  }
-
-  @Test
-  public void searchBranchStatistics_on_many_branches() {
-    ComponentDto project = db.components().insertMainBranch();
-    List<String> branchUuids = new ArrayList<>();
-    List<Tuple> expectedResult = new ArrayList<>();
-    IntStream.range(0, 15).forEach(i -> {
-      ComponentDto branch = db.components().insertProjectBranch(project);
-      addIssues(branch, 1 + i, 2 + i, 3 + i);
-      expectedResult.add(tuple(branch.uuid(), 1L + i, 2L + i, 3L + i));
-      branchUuids.add(branch.uuid());
-    });
-
-    List<BranchStatistics> branchStatistics = underTest.searchBranchStatistics(project.uuid(), branchUuids);
-
-    assertThat(branchStatistics)
-      .extracting(BranchStatistics::getBranchUuid, BranchStatistics::getBugs, BranchStatistics::getVulnerabilities, BranchStatistics::getCodeSmells)
-      .hasSize(15)
-      .containsAll(expectedResult);
-  }
-
-  @Test
-  public void searchBranchStatistics_on_empty_list() {
-    ComponentDto project = db.components().insertMainBranch();
-
-    assertThat(underTest.searchBranchStatistics(project.uuid(), emptyList())).isEmpty();
-    assertThat(underTest.searchBranchStatistics(project.uuid(), singletonList("unknown"))).isEmpty();
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    ComponentDto another = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
-      newDoc("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
-      .contains(
-        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
-
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_dont_count_closed_vulnerabilities() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("openvul1", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
-      newDoc("notopenvul", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
-        .setSeverity(Severity.BLOCKER));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
-      .contains(
-        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_dont_count_old_vulnerabilities() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
-      newDoc("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
-      .containsOnly(
-        tuple(0L, OptionalInt.empty()));
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    ComponentDto another = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
-      newDoc("anotherProject", another).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
-      .contains(
-        tuple("a1", 1L /* openhotspot1 */));
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_dont_count_closed_hotspots() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
-      newDoc("closedHotspot", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
-        .setResolution(Issue.RESOLUTION_FIXED));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
-      .contains(
-        tuple("a1", 1L /* openhotspot1 */));
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_aggregation_no_cwe() {
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
-
-    assertThat(owaspTop10Report).allMatch(category -> category.getChildren().isEmpty());
-  }
-
-  @Test
-  public void test_getOwaspTop10Report_aggregation_with_cwe() {
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
-
-    Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
-      .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
-
-    assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-      SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
-      SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
-      .containsExactlyInAnyOrder(
-        tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
-        tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
-        tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
-    assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-      SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
-      SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
-      .containsExactlyInAnyOrder(
-        tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
-        tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 1L /* toReviewHotspot */, 0L),
-        tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
-  }
-
-  private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    ComponentDto another = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.MAJOR),
-      newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
-        .setSeverity(Severity.MINOR),
-      newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.CRITICAL),
-      newDoc("openhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
-        .setStatus(Issue.STATUS_OPEN),
-      newDoc("openhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REOPENED),
-      newDoc("toReviewHotspot", project).setOwaspTop10(asList("a5", "a3")).setCwe(asList("456")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("WFHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_WONT_FIX),
-      newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
-
-    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe);
-    assertThat(owaspTop10Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
-        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
-      .containsExactlyInAnyOrder(
-        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
-        tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 0L),
-        tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */, 1L /* WFHotspot */),
-        tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 0L),
-        tuple("a5", 0L, OptionalInt.empty(), 0L, 1L/* toReviewHotspot */, 0L),
-        tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* openhotspot2 */, 0L, 0L),
-        tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 0L),
-        tuple("a8", 0L, OptionalInt.empty(), 0L, 0L, 1L /* WFHotspot */),
-        tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 0L),
-        tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 0L),
-        tuple("unknown", 1L /* notowaspvul */, OptionalInt.of(4) /* CRITICAL = D */, 1L /* notowasphotspot */, 0L, 0L));
-    return owaspTop10Report;
-  }
-
-  @Test
-  public void test_getSansTop25Report_aggregation() {
-    OrganizationDto org = newOrganizationDto();
-    ComponentDto project = newPrivateProjectDto(org);
-    indexIssues(
-      newDoc("openvul1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.MAJOR),
-      newDoc("openvul2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
-        .setSeverity(Severity.MINOR),
-      newDoc("notopenvul", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
-        .setResolution(Issue.RESOLUTION_FIXED)
-        .setSeverity(Severity.BLOCKER),
-      newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.CRITICAL),
-      newDoc("openhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
-        .setStatus(Issue.STATUS_OPEN),
-      newDoc("openhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
-        .setStatus(Issue.STATUS_REOPENED),
-      newDoc("toReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("WFHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_WONT_FIX),
-      newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
-
-    List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false);
-    assertThat(sansTop25Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
-        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
-      .containsExactlyInAnyOrder(
-        tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
-        tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */,
-          1L /* WFHotspot */),
-        tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L, 0L));
-
-    assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
-  }
-
-  @Test
-  public void test_getSansTop25Report_aggregation_on_portfolio() {
-    ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    indexIssues(
-      newDoc("openvul1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.MAJOR),
-      newDoc("openvul2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
-        .setSeverity(Severity.MINOR),
-      newDoc("notopenvul", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
-        .setResolution(Issue.RESOLUTION_FIXED)
-        .setSeverity(Severity.BLOCKER),
-      newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
-        .setSeverity(Severity.CRITICAL),
-      newDoc("openhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
-        .setStatus(Issue.STATUS_OPEN),
-      newDoc("openhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
-        .setStatus(Issue.STATUS_REOPENED),
-      newDoc("toReviewHotspot", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_FIXED),
-      newDoc("WFHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
-        .setResolution(Issue.RESOLUTION_WONT_FIX),
-      newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
-
-    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
-    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
-
-    List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(portfolio1.uuid(), true, false);
-    assertThat(sansTop25Report)
-      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
-        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
-        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
-      .containsExactlyInAnyOrder(
-        tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
-        tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* openhotspot1 */, 1L /* toReviewHotspot */, 0L),
-        tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L, 0L));
-
-    assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
-  }
-
-  private void addIssues(ComponentDto component, int bugs, int vulnerabilities, int codeSmelles) {
-    List<IssueDoc> issues = new ArrayList<>();
-    IntStream.range(0, bugs).forEach(b -> issues.add(newDoc(component).setType(BUG).setResolution(null)));
-    IntStream.range(0, vulnerabilities).forEach(v -> issues.add(newDoc(component).setType(VULNERABILITY).setResolution(null)));
-    IntStream.range(0, codeSmelles).forEach(c -> issues.add(newDoc(component).setType(CODE_SMELL).setResolution(null)));
-    indexIssues(issues.toArray(new IssueDoc[issues.size()]));
-  }
-
-  private IssueQuery projectQuery(String projectUuid) {
-    return IssueQuery.builder().projectUuids(singletonList(projectUuid)).resolved(false).build();
-  }
-
-  private void verifyOrganizationFilter(String organizationUuid, String... expectedIssueKeys) {
-    IssueQuery.Builder query = IssueQuery.builder().organizationUuid(organizationUuid);
-    assertThatSearchReturnsOnly(query, expectedIssueKeys);
-  }
-
-  private void indexIssues(IssueDoc... issues) {
-    issueIndexer.index(asList(issues).iterator());
-    for (IssueDoc issue : issues) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(issue.projectUuid(), "TRK");
-      access.allowAnyone();
-      authorizationIndexerTester.allow(access);
-    }
-  }
-
-  private void indexIssue(IssueDoc issue) {
-    issueIndexer.index(Iterators.singletonIterator(issue));
-  }
-
-  private void indexView(String viewUuid, List<String> projects) {
-    viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
-  }
-
-  /**
-   * Execute the search request and return the document ids of results.
-   */
-  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
-    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
-      .map(SearchHit::getId)
-      .collect(Collectors.toList());
-  }
-
-  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
-  }
-
-  private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
-    List<String> keys = searchAndReturnKeys(query);
-    assertThat(keys).isEmpty();
-  }
-
-  private void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
-    SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
-    Facets facets = new Facets(result, system2.getDefaultTimeZone());
-    assertThat(facets.getNames()).containsOnly(facet);
-    assertThat(facets.get(facet)).containsExactly(expectedEntries);
-  }
-
-  private void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
-    SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
-    Facets facets = new Facets(result, system2.getDefaultTimeZone());
-    assertThat(facets.getNames()).containsOnly(facet);
-    assertThat(facets.get(facet)).containsOnly(expectedEntries);
-  }
-}
index 6ecf880a1dc5147b92abbcfc9a12de4befa34716..3c793a28e6ee9991d5e950c670c1349ef9a287d6 100644 (file)
@@ -47,7 +47,7 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.es.IndexingResult;
 import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.permission.index.AuthorizationScope;
-import org.sonar.server.permission.index.PermissionIndexerDao;
+import org.sonar.server.permission.index.IndexPermissions;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
@@ -56,10 +56,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.rules.ExpectedException.none;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.server.issue.IssueDocTesting.newDoc;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
 import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
-import static org.sonar.server.permission.index.AuthorizationTypeSupport.TYPE_AUTHORIZATION;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 public class IssueIndexerTest {
 
@@ -91,9 +91,9 @@ public class IssueIndexerTest {
     assertThat(scope.getIndexType().getIndex()).isEqualTo(INDEX_TYPE_ISSUE.getIndex());
     assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
 
-    Predicate<PermissionIndexerDao.Dto> projectPredicate = scope.getProjectPredicate();
-    PermissionIndexerDao.Dto project = new PermissionIndexerDao.Dto("P1", Qualifiers.PROJECT);
-    PermissionIndexerDao.Dto file = new PermissionIndexerDao.Dto("F1", Qualifiers.FILE);
+    Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
+    IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
+    IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
     assertThat(projectPredicate.test(project)).isTrue();
     assertThat(projectPredicate.test(file)).isFalse();
   }
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
deleted file mode 100644 (file)
index a9eaf31..0000000
+++ /dev/null
@@ -1,1477 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.System2;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.organization.OrganizationTesting;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Sets.newHashSet;
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
-import static org.sonar.api.measures.Metric.Level.ERROR;
-import static org.sonar.api.measures.Metric.Level.OK;
-import static org.sonar.api.measures.Metric.Level.WARN;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.user.GroupTesting.newGroupDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.*;
-
-@RunWith(DataProviderRunner.class)
-public class ProjectMeasuresIndexTest {
-
-  private static final String MAINTAINABILITY_RATING = "sqale_rating";
-  private static final String NEW_MAINTAINABILITY_RATING_KEY = "new_maintainability_rating";
-  private static final String RELIABILITY_RATING = "reliability_rating";
-  private static final String NEW_RELIABILITY_RATING = "new_reliability_rating";
-  private static final String SECURITY_RATING = "security_rating";
-  private static final String NEW_SECURITY_RATING = "new_security_rating";
-  private static final String COVERAGE = "coverage";
-  private static final String NEW_COVERAGE = "new_coverage";
-  private static final String DUPLICATION = "duplicated_lines_density";
-  private static final String NEW_DUPLICATION = "new_duplicated_lines_density";
-  private static final String NCLOC = "ncloc";
-  private static final String NEW_LINES = "new_lines";
-  private static final String LANGUAGES = "languages";
-
-  private static final OrganizationDto ORG = OrganizationTesting.newOrganizationDto();
-  private static final ComponentDto PROJECT1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-1").setName("Project 1").setDbKey("key-1");
-  private static final ComponentDto PROJECT2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-2").setName("Project 2").setDbKey("key-2");
-  private static final ComponentDto PROJECT3 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-3").setName("Project 3").setDbKey("key-3");
-  private static final UserDto USER1 = newUserDto();
-  private static final UserDto USER2 = newUserDto();
-  private static final GroupDto GROUP1 = newGroupDto();
-  private static final GroupDto GROUP2 = newGroupDto();
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-
-  @DataProvider
-  public static Object[][] rating_metric_keys() {
-    return new Object[][]{{MAINTAINABILITY_RATING}, {NEW_MAINTAINABILITY_RATING_KEY}, {RELIABILITY_RATING}, {NEW_RELIABILITY_RATING}, {SECURITY_RATING}, {NEW_SECURITY_RATING}};
-  }
-
-  private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, projectMeasureIndexer);
-  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
-
-  @Test
-  public void return_empty_if_no_projects() {
-    assertNoResults(new ProjectMeasuresQuery());
-  }
-
-  @Test
-  public void default_sort_is_by_ascending_case_insensitive_name_then_by_key() {
-    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
-    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
-    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
-    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
-    index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
-
-    assertResults(new ProjectMeasuresQuery(), apache1, apache2, apachee, windows);
-  }
-
-  @Test
-  public void sort_by_insensitive_name() {
-    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows");
-    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee");
-    ComponentDto apache = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache").setName("Apache");
-    index(newDoc(windows), newDoc(apachee), newDoc(apache));
-
-    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(true), apache, apachee, windows);
-    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(false), windows, apachee, apache);
-  }
-
-  @Test
-  public void sort_by_ncloc() {
-    index(
-      newDoc(PROJECT1, NCLOC, 15_000d),
-      newDoc(PROJECT2, NCLOC, 30_000d),
-      newDoc(PROJECT3, NCLOC, 1_000d));
-
-    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), PROJECT3, PROJECT1, PROJECT2);
-    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), PROJECT2, PROJECT1, PROJECT3);
-  }
-
-  @Test
-  public void sort_by_a_metric_then_by_name_then_by_key() {
-    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
-    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
-    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
-    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
-    index(
-      newDoc(windows, NCLOC, 10_000d),
-      newDoc(apachee, NCLOC, 5_000d),
-      newDoc(apache1, NCLOC, 5_000d),
-      newDoc(apache2, NCLOC, 5_000d));
-
-    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), apache1, apache2, apachee, windows);
-    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), windows, apache1, apache2, apachee);
-  }
-
-  @Test
-  public void sort_by_quality_gate_status() {
-    ComponentDto project4 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-4").setName("Project 4").setDbKey("key-4");
-    index(
-      newDoc(PROJECT1).setQualityGateStatus(OK.name()),
-      newDoc(PROJECT2).setQualityGateStatus(ERROR.name()),
-      newDoc(PROJECT3).setQualityGateStatus(WARN.name()),
-      newDoc(project4).setQualityGateStatus(OK.name()));
-
-    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(true), PROJECT1, project4, PROJECT3, PROJECT2);
-    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), PROJECT2, PROJECT3, PROJECT1, project4);
-  }
-
-  @Test
-  public void sort_by_quality_gate_status_then_by_name_then_by_key() {
-    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
-    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
-    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
-    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
-    index(
-      newDoc(windows).setQualityGateStatus(WARN.name()),
-      newDoc(apachee).setQualityGateStatus(OK.name()),
-      newDoc(apache1).setQualityGateStatus(OK.name()),
-      newDoc(apache2).setQualityGateStatus(OK.name()));
-
-    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(true), apache1, apache2, apachee, windows);
-    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), windows, apache1, apache2, apachee);
-  }
-
-  @Test
-  public void paginate_results() {
-    IntStream.rangeClosed(1, 9)
-      .forEach(i -> index(newDoc(newPrivateProjectDto(ORG, "P" + i))));
-
-    SearchIdResult<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().setPage(2, 3));
-
-    assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
-    assertThat(result.getTotal()).isEqualTo(9);
-  }
-
-  @Test
-  public void filter_with_lower_than() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 80d));
-
-    assertResults(query, PROJECT1);
-  }
-
-  @Test
-  public void filter_with_lower_than_or_equals() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LTE, 80d));
-
-    assertResults(query, PROJECT1, PROJECT2);
-  }
-
-  @Test
-  public void filter_with_greater_than() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
-      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 30_000d));
-    assertResults(query, PROJECT2, PROJECT3);
-
-    query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 100_000d));
-    assertNoResults(query);
-  }
-
-  @Test
-  public void filter_with_greater_than_or_equals() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
-      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GTE, 30_001d));
-    assertResults(query, PROJECT2, PROJECT3);
-
-    query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GTE, 100_000d));
-    assertNoResults(query);
-  }
-
-  @Test
-  public void filter_with_equals() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.EQ, 80d));
-
-    assertResults(query, PROJECT2);
-  }
-
-  @Test
-  public void filter_on_no_data_with_several_projects() {
-    index(
-      newDoc(PROJECT1, NCLOC, 1d),
-      newDoc(PROJECT2, DUPLICATION, 80d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-      .addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
-
-    assertResults(query, PROJECT1);
-  }
-
-  @Test
-  public void filter_on_no_data_should_not_return_projects_with_data_and_other_measures() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
-    index(newDoc(project, DUPLICATION, 80d, NCLOC, 1d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
-
-    assertNoResults(query);
-  }
-
-  @Test
-  public void filter_on_no_data_should_not_return_projects_with_data() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
-    index(newDoc(project, DUPLICATION, 80d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
-
-    assertNoResults(query);
-  }
-
-  @Test
-  public void filter_on_no_data_should_return_projects_with_no_data() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
-    index(newDoc(project, NCLOC, 1d));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
-
-    assertResults(query, project);
-  }
-
-  @Test
-  public void filter_on_several_metrics() {
-    index(
-      newDoc(PROJECT1, COVERAGE, 81d, NCLOC, 10_001d),
-      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_001d),
-      newDoc(PROJECT3, COVERAGE, 79d, NCLOC, 10_000d));
-
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
-      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LTE, 80d))
-      .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 10_000d))
-      .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.LT, 11_000d));
-    assertResults(esQuery, PROJECT2);
-  }
-
-  @Test
-  public void filter_on_quality_gate_status() {
-    index(
-      newDoc(PROJECT1).setQualityGateStatus(OK.name()),
-      newDoc(PROJECT2).setQualityGateStatus(OK.name()),
-      newDoc(PROJECT3).setQualityGateStatus(WARN.name()));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().setQualityGateStatus(OK);
-    assertResults(query, PROJECT1, PROJECT2);
-  }
-
-  @Test
-  public void filter_on_languages() {
-    ComponentDto project4 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-4").setName("Project 4").setDbKey("key-4");
-    index(
-      newDoc(PROJECT1).setLanguages(singletonList("java")),
-      newDoc(PROJECT2).setLanguages(singletonList("xoo")),
-      newDoc(PROJECT3).setLanguages(singletonList("xoo")),
-      newDoc(project4).setLanguages(asList("<null>", "java", "xoo")));
-
-    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("java", "xoo")), PROJECT1, PROJECT2, PROJECT3, project4);
-    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("java")), PROJECT1, project4);
-    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("unknown")));
-  }
-
-  @Test
-  public void filter_on_query_text() {
-    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
-    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
-    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
-    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
-    index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
-
-    assertResults(new ProjectMeasuresQuery().setQueryText("windows"), windows);
-    assertResults(new ProjectMeasuresQuery().setQueryText("project2"), apachee);
-    assertResults(new ProjectMeasuresQuery().setQueryText("pAch"), apache1, apache2, apachee);
-  }
-
-  @Test
-  public void filter_on_ids() {
-    index(
-      newDoc(PROJECT1),
-      newDoc(PROJECT2),
-      newDoc(PROJECT3));
-
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery().setProjectUuids(newHashSet(PROJECT1.uuid(), PROJECT3.uuid()));
-    assertResults(query, PROJECT1, PROJECT3);
-  }
-
-  @Test
-  public void filter_on_tags() {
-    index(
-      newDoc(PROJECT1).setTags(newArrayList("finance", "platform")),
-      newDoc(PROJECT2).setTags(newArrayList("marketing", "platform")),
-      newDoc(PROJECT3).setTags(newArrayList("finance", "language")));
-
-    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance")), PROJECT1, PROJECT3);
-    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance", "language")), PROJECT1, PROJECT3);
-    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance", "marketing")), PROJECT1, PROJECT2, PROJECT3);
-    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("marketing")), PROJECT2);
-    assertNoResults(new ProjectMeasuresQuery().setTags(newHashSet("tag 42")));
-  }
-
-  @Test
-  public void filter_on_organization() {
-    OrganizationDto org1 = OrganizationTesting.newOrganizationDto();
-    OrganizationDto org2 = OrganizationTesting.newOrganizationDto();
-    ComponentDto projectInOrg1 = ComponentTesting.newPrivateProjectDto(org1);
-    ComponentDto projectInOrg2 = ComponentTesting.newPrivateProjectDto(org2);
-    index(newDoc(projectInOrg1), newDoc(projectInOrg2));
-
-    ProjectMeasuresQuery query1 = new ProjectMeasuresQuery().setOrganizationUuid(org1.getUuid());
-    assertResults(query1, projectInOrg1);
-
-    ProjectMeasuresQuery query2 = new ProjectMeasuresQuery().setOrganizationUuid(org2.getUuid());
-    assertResults(query2, projectInOrg2);
-
-    ProjectMeasuresQuery query3 = new ProjectMeasuresQuery().setOrganizationUuid("another_org");
-    assertNoResults(query3);
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user() {
-    indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2));
-    indexForUser(USER2, newDoc(PROJECT3));
-
-    userSession.logIn(USER1);
-    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2);
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user_groups() {
-    indexForGroup(GROUP1, newDoc(PROJECT1), newDoc(PROJECT2));
-    indexForGroup(GROUP2, newDoc(PROJECT3));
-
-    userSession.logIn().setGroups(GROUP1);
-    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2);
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user_and_groups() {
-    indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2));
-    indexForGroup(GROUP1, newDoc(PROJECT3));
-
-    userSession.logIn(USER1).setGroups(GROUP1);
-    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2, PROJECT3);
-  }
-
-  @Test
-  public void anonymous_user_can_only_access_projects_authorized_for_anyone() {
-    index(newDoc(PROJECT1));
-    indexForUser(USER1, newDoc(PROJECT2));
-
-    userSession.anonymous();
-    assertResults(new ProjectMeasuresQuery(), PROJECT1);
-  }
-
-  @Test
-  public void root_user_can_access_all_projects() {
-    indexForUser(USER1, newDoc(PROJECT1));
-    // connecting with a root but not USER1
-    userSession.logIn().setRoot();
-
-    assertResults(new ProjectMeasuresQuery(), PROJECT1);
-  }
-
-  @Test
-  public void does_not_return_facet_when_no_facets_in_options() {
-    index(
-      newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
-        .setQualityGateStatus(OK.name()));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getFacets();
-
-    assertThat(facets.getAll()).isEmpty();
-  }
-
-  @Test
-  public void facet_ncloc() {
-    index(
-      // 3 docs with ncloc<1K
-      newDoc(NCLOC, 0d),
-      newDoc(NCLOC, 0d),
-      newDoc(NCLOC, 999d),
-      // 2 docs with ncloc>=1K and ncloc<10K
-      newDoc(NCLOC, 1_000d),
-      newDoc(NCLOC, 9_999d),
-      // 4 docs with ncloc>=10K and ncloc<100K
-      newDoc(NCLOC, 10_000d),
-      newDoc(NCLOC, 10_000d),
-      newDoc(NCLOC, 11_000d),
-      newDoc(NCLOC, 99_000d),
-      // 2 docs with ncloc>=100K and ncloc<500K
-      newDoc(NCLOC, 100_000d),
-      newDoc(NCLOC, 499_000d),
-      // 5 docs with ncloc>= 500K
-      newDoc(NCLOC, 500_000d),
-      newDoc(NCLOC, 100_000_000d),
-      newDoc(NCLOC, 500_000d),
-      newDoc(NCLOC, 1_000_000d),
-      newDoc(NCLOC, 100_000_000_000d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
-
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 3L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 4L),
-      entry("100000.0-500000.0", 2L),
-      entry("500000.0-*", 5L));
-  }
-
-  @Test
-  public void facet_ncloc_is_sticky() {
-    index(
-      // 1 docs with ncloc<1K
-      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
-      // 2 docs with ncloc>=1K and ncloc<10K
-      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
-      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
-      // 3 docs with ncloc>=10K and ncloc<100K
-      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
-      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
-      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
-      // 2 docs with ncloc>=100K and ncloc<500K
-      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
-      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 0d),
-      // 1 docs with ncloc>= 500K
-      newDoc(NCLOC, 501_000d, COVERAGE, 81d, DUPLICATION, 20d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-        .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.LT, 10_000d))
-        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d)),
-      new SearchOptions().addFacets(NCLOC, COVERAGE)).getFacets();
-
-    // Sticky facet on ncloc does not take into account ncloc filter
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 1L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 3L),
-      entry("100000.0-500000.0", 2L),
-      entry("500000.0-*", 0L));
-    // But facet on coverage does well take into into filters
-    assertThat(facets.get(COVERAGE)).containsOnly(
-      entry("NO_DATA", 0L),
-      entry("*-30.0", 3L),
-      entry("30.0-50.0", 0L),
-      entry("50.0-70.0", 0L),
-      entry("70.0-80.0", 0L),
-      entry("80.0-*", 0L));
-  }
-
-  @Test
-  public void facet_ncloc_contains_only_projects_authorized_for_user() {
-    // User can see these projects
-    indexForUser(USER1,
-      // docs with ncloc<1K
-      newDoc(NCLOC, 0d),
-      newDoc(NCLOC, 100d),
-      newDoc(NCLOC, 999d),
-      // docs with ncloc>=1K and ncloc<10K
-      newDoc(NCLOC, 1_000d),
-      newDoc(NCLOC, 9_999d));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      // doc with ncloc>=10K and ncloc<100K
-      newDoc(NCLOC, 11_000d),
-      // doc with ncloc>=100K and ncloc<500K
-      newDoc(NCLOC, 499_000d),
-      // doc with ncloc>= 500K
-      newDoc(NCLOC, 501_000d));
-
-    userSession.logIn(USER1);
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
-
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 3L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 0L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_new_lines() {
-    index(
-      // 3 docs with ncloc<1K
-      newDoc(NEW_LINES, 0d),
-      newDoc(NEW_LINES, 0d),
-      newDoc(NEW_LINES, 999d),
-      // 2 docs with ncloc>=1K and ncloc<10K
-      newDoc(NEW_LINES, 1_000d),
-      newDoc(NEW_LINES, 9_999d),
-      // 4 docs with ncloc>=10K and ncloc<100K
-      newDoc(NEW_LINES, 10_000d),
-      newDoc(NEW_LINES, 10_000d),
-      newDoc(NEW_LINES, 11_000d),
-      newDoc(NEW_LINES, 99_000d),
-      // 2 docs with ncloc>=100K and ncloc<500K
-      newDoc(NEW_LINES, 100_000d),
-      newDoc(NEW_LINES, 499_000d),
-      // 5 docs with ncloc>= 500K
-      newDoc(NEW_LINES, 500_000d),
-      newDoc(NEW_LINES, 100_000_000d),
-      newDoc(NEW_LINES, 500_000d),
-      newDoc(NEW_LINES, 1_000_000d),
-      newDoc(NEW_LINES, 100_000_000_000d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_LINES)).getFacets();
-
-    assertThat(facets.get(NEW_LINES)).containsExactly(
-      entry("*-1000.0", 3L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 4L),
-      entry("100000.0-500000.0", 2L),
-      entry("500000.0-*", 5L));
-  }
-
-  @Test
-  public void facet_coverage() {
-    index(
-      // 1 doc with no coverage
-      newDocWithNoMeasure(),
-      // 3 docs with coverage<30%
-      newDoc(COVERAGE, 0d),
-      newDoc(COVERAGE, 0d),
-      newDoc(COVERAGE, 29d),
-      // 2 docs with coverage>=30% and coverage<50%
-      newDoc(COVERAGE, 30d),
-      newDoc(COVERAGE, 49d),
-      // 4 docs with coverage>=50% and coverage<70%
-      newDoc(COVERAGE, 50d),
-      newDoc(COVERAGE, 60d),
-      newDoc(COVERAGE, 60d),
-      newDoc(COVERAGE, 69d),
-      // 2 docs with coverage>=70% and coverage<80%
-      newDoc(COVERAGE, 70d),
-      newDoc(COVERAGE, 79d),
-      // 5 docs with coverage>= 80%
-      newDoc(COVERAGE, 80d),
-      newDoc(COVERAGE, 80d),
-      newDoc(COVERAGE, 90d),
-      newDoc(COVERAGE, 90.5d),
-      newDoc(COVERAGE, 100d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
-
-    assertThat(facets.get(COVERAGE)).containsOnly(
-      entry("NO_DATA", 1L),
-      entry("*-30.0", 3L),
-      entry("30.0-50.0", 2L),
-      entry("50.0-70.0", 4L),
-      entry("70.0-80.0", 2L),
-      entry("80.0-*", 5L));
-  }
-
-  @Test
-  public void facet_coverage_is_sticky() {
-    index(
-      // docs with no coverage
-      newDoc(NCLOC, 999d, DUPLICATION, 0d),
-      newDoc(NCLOC, 999d, DUPLICATION, 1d),
-      newDoc(NCLOC, 999d, DUPLICATION, 20d),
-      // docs with coverage<30%
-      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
-      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
-      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
-      // docs with coverage>=30% and coverage<50%
-      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
-      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
-      // docs with coverage>=50% and coverage<70%
-      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
-      // docs with coverage>=70% and coverage<80%
-      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
-      // docs with coverage>= 80%
-      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 15d),
-      newDoc(NCLOC, 501_000d, COVERAGE, 810d, DUPLICATION, 20d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d))
-        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d)),
-      new SearchOptions().addFacets(COVERAGE, NCLOC)).getFacets();
-
-    // Sticky facet on coverage does not take into account coverage filter
-    assertThat(facets.get(COVERAGE)).containsExactly(
-      entry("NO_DATA", 2L),
-      entry("*-30.0", 3L),
-      entry("30.0-50.0", 2L),
-      entry("50.0-70.0", 1L),
-      entry("70.0-80.0", 1L),
-      entry("80.0-*", 0L));
-    // But facet on ncloc does well take into into filters
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 1L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 0L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_coverage_contains_only_projects_authorized_for_user() {
-    // User can see these projects
-    indexForUser(USER1,
-      // 1 doc with no coverage
-      newDocWithNoMeasure(),
-      // docs with coverage<30%
-      newDoc(COVERAGE, 0d),
-      newDoc(COVERAGE, 0d),
-      newDoc(COVERAGE, 29d),
-      // docs with coverage>=30% and coverage<50%
-      newDoc(COVERAGE, 30d),
-      newDoc(COVERAGE, 49d));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      // 2 docs with no coverage
-      newDocWithNoMeasure(),
-      newDocWithNoMeasure(),
-      // docs with coverage>=50% and coverage<70%
-      newDoc(COVERAGE, 50d),
-      // docs with coverage>=70% and coverage<80%
-      newDoc(COVERAGE, 70d),
-      // docs with coverage>= 80%
-      newDoc(COVERAGE, 80d));
-
-    userSession.logIn(USER1);
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
-
-    assertThat(facets.get(COVERAGE)).containsExactly(
-      entry("NO_DATA", 1L),
-      entry("*-30.0", 3L),
-      entry("30.0-50.0", 2L),
-      entry("50.0-70.0", 0L),
-      entry("70.0-80.0", 0L),
-      entry("80.0-*", 0L));
-  }
-
-  @Test
-  public void facet_new_coverage() {
-    index(
-      // 1 doc with no coverage
-      newDocWithNoMeasure(),
-      // 3 docs with coverage<30%
-      newDoc(NEW_COVERAGE, 0d),
-      newDoc(NEW_COVERAGE, 0d),
-      newDoc(NEW_COVERAGE, 29d),
-      // 2 docs with coverage>=30% and coverage<50%
-      newDoc(NEW_COVERAGE, 30d),
-      newDoc(NEW_COVERAGE, 49d),
-      // 4 docs with coverage>=50% and coverage<70%
-      newDoc(NEW_COVERAGE, 50d),
-      newDoc(NEW_COVERAGE, 60d),
-      newDoc(NEW_COVERAGE, 60d),
-      newDoc(NEW_COVERAGE, 69d),
-      // 2 docs with coverage>=70% and coverage<80%
-      newDoc(NEW_COVERAGE, 70d),
-      newDoc(NEW_COVERAGE, 79d),
-      // 5 docs with coverage>= 80%
-      newDoc(NEW_COVERAGE, 80d),
-      newDoc(NEW_COVERAGE, 80d),
-      newDoc(NEW_COVERAGE, 90d),
-      newDoc(NEW_COVERAGE, 90.5d),
-      newDoc(NEW_COVERAGE, 100d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_COVERAGE)).getFacets();
-
-    assertThat(facets.get(NEW_COVERAGE)).containsOnly(
-      entry("NO_DATA", 1L),
-      entry("*-30.0", 3L),
-      entry("30.0-50.0", 2L),
-      entry("50.0-70.0", 4L),
-      entry("70.0-80.0", 2L),
-      entry("80.0-*", 5L));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density() {
-    index(
-      // 1 doc with no duplication
-      newDocWithNoMeasure(),
-      // 3 docs with duplication<3%
-      newDoc(DUPLICATION, 0d),
-      newDoc(DUPLICATION, 0d),
-      newDoc(DUPLICATION, 2.9d),
-      // 2 docs with duplication>=3% and duplication<5%
-      newDoc(DUPLICATION, 3d),
-      newDoc(DUPLICATION, 4.9d),
-      // 4 docs with duplication>=5% and duplication<10%
-      newDoc(DUPLICATION, 5d),
-      newDoc(DUPLICATION, 6d),
-      newDoc(DUPLICATION, 6d),
-      newDoc(DUPLICATION, 9.9d),
-      // 2 docs with duplication>=10% and duplication<20%
-      newDoc(DUPLICATION, 10d),
-      newDoc(DUPLICATION, 19.9d),
-      // 5 docs with duplication>= 20%
-      newDoc(DUPLICATION, 20d),
-      newDoc(DUPLICATION, 20d),
-      newDoc(DUPLICATION, 50d),
-      newDoc(DUPLICATION, 80d),
-      newDoc(DUPLICATION, 100d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
-
-    assertThat(facets.get(DUPLICATION)).containsOnly(
-      entry("NO_DATA", 1L),
-      entry("*-3.0", 3L),
-      entry("3.0-5.0", 2L),
-      entry("5.0-10.0", 4L),
-      entry("10.0-20.0", 2L),
-      entry("20.0-*", 5L));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density_is_sticky() {
-    index(
-      // docs with no duplication
-      newDoc(NCLOC, 50_001d, COVERAGE, 29d),
-      // docs with duplication<3%
-      newDoc(DUPLICATION, 0d, NCLOC, 999d, COVERAGE, 0d),
-      // docs with duplication>=3% and duplication<5%
-      newDoc(DUPLICATION, 3d, NCLOC, 5000d, COVERAGE, 0d),
-      newDoc(DUPLICATION, 4.9d, NCLOC, 6000d, COVERAGE, 0d),
-      // docs with duplication>=5% and duplication<10%
-      newDoc(DUPLICATION, 5d, NCLOC, 11000d, COVERAGE, 0d),
-      // docs with duplication>=10% and duplication<20%
-      newDoc(DUPLICATION, 10d, NCLOC, 120000d, COVERAGE, 10d),
-      newDoc(DUPLICATION, 19.9d, NCLOC, 130000d, COVERAGE, 20d),
-      // docs with duplication>= 20%
-      newDoc(DUPLICATION, 20d, NCLOC, 1000000d, COVERAGE, 40d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d))
-        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d)),
-      new SearchOptions().addFacets(DUPLICATION, NCLOC)).getFacets();
-
-    // Sticky facet on duplication does not take into account duplication filter
-    assertThat(facets.get(DUPLICATION)).containsOnly(
-      entry("NO_DATA", 1L),
-      entry("*-3.0", 1L),
-      entry("3.0-5.0", 2L),
-      entry("5.0-10.0", 1L),
-      entry("10.0-20.0", 2L),
-      entry("20.0-*", 0L));
-    // But facet on ncloc does well take into into filters
-    assertThat(facets.get(NCLOC)).containsOnly(
-      entry("*-1000.0", 1L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 1L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() {
-    // User can see these projects
-    indexForUser(USER1,
-      // docs with no duplication
-      newDocWithNoMeasure(),
-      // docs with duplication<3%
-      newDoc(DUPLICATION, 0d),
-      newDoc(DUPLICATION, 0d),
-      newDoc(DUPLICATION, 2.9d),
-      // docs with duplication>=3% and duplication<5%
-      newDoc(DUPLICATION, 3d),
-      newDoc(DUPLICATION, 4.9d));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      // docs with no duplication
-      newDocWithNoMeasure(),
-      newDocWithNoMeasure(),
-      // docs with duplication>=5% and duplication<10%
-      newDoc(DUPLICATION, 5d),
-      // docs with duplication>=10% and duplication<20%
-      newDoc(DUPLICATION, 10d),
-      // docs with duplication>= 20%
-      newDoc(DUPLICATION, 20d));
-
-    userSession.logIn(USER1);
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
-
-    assertThat(facets.get(DUPLICATION)).containsOnly(
-      entry("NO_DATA", 1L),
-      entry("*-3.0", 3L),
-      entry("3.0-5.0", 2L),
-      entry("5.0-10.0", 0L),
-      entry("10.0-20.0", 0L),
-      entry("20.0-*", 0L));
-  }
-
-  @Test
-  public void facet_new_duplicated_lines_density() {
-    index(
-      // 2 docs with no measure
-      newDocWithNoMeasure(),
-      newDocWithNoMeasure(),
-      // 3 docs with duplication<3%
-      newDoc(NEW_DUPLICATION, 0d),
-      newDoc(NEW_DUPLICATION, 0d),
-      newDoc(NEW_DUPLICATION, 2.9d),
-      // 2 docs with duplication>=3% and duplication<5%
-      newDoc(NEW_DUPLICATION, 3d),
-      newDoc(NEW_DUPLICATION, 4.9d),
-      // 4 docs with duplication>=5% and duplication<10%
-      newDoc(NEW_DUPLICATION, 5d),
-      newDoc(NEW_DUPLICATION, 6d),
-      newDoc(NEW_DUPLICATION, 6d),
-      newDoc(NEW_DUPLICATION, 9.9d),
-      // 2 docs with duplication>=10% and duplication<20%
-      newDoc(NEW_DUPLICATION, 10d),
-      newDoc(NEW_DUPLICATION, 19.9d),
-      // 5 docs with duplication>= 20%
-      newDoc(NEW_DUPLICATION, 20d),
-      newDoc(NEW_DUPLICATION, 20d),
-      newDoc(NEW_DUPLICATION, 50d),
-      newDoc(NEW_DUPLICATION, 80d),
-      newDoc(NEW_DUPLICATION, 100d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_DUPLICATION)).getFacets();
-
-    assertThat(facets.get(NEW_DUPLICATION)).containsExactly(
-      entry("NO_DATA", 2L),
-      entry("*-3.0", 3L),
-      entry("3.0-5.0", 2L),
-      entry("5.0-10.0", 4L),
-      entry("10.0-20.0", 2L),
-      entry("20.0-*", 5L));
-  }
-
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating(String metricKey) {
-    index(
-      // 3 docs with rating A
-      newDoc(metricKey, 1d),
-      newDoc(metricKey, 1d),
-      newDoc(metricKey, 1d),
-      // 2 docs with rating B
-      newDoc(metricKey, 2d),
-      newDoc(metricKey, 2d),
-      // 4 docs with rating C
-      newDoc(metricKey, 3d),
-      newDoc(metricKey, 3d),
-      newDoc(metricKey, 3d),
-      newDoc(metricKey, 3d),
-      // 2 docs with rating D
-      newDoc(metricKey, 4d),
-      newDoc(metricKey, 4d),
-      // 5 docs with rating E
-      newDoc(metricKey, 5d),
-      newDoc(metricKey, 5d),
-      newDoc(metricKey, 5d),
-      newDoc(metricKey, 5d),
-      newDoc(metricKey, 5d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
-
-    assertThat(facets.get(metricKey)).containsExactly(
-      entry("1", 3L),
-      entry("2", 2L),
-      entry("3", 4L),
-      entry("4", 2L),
-      entry("5", 5L));
-  }
-
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating_is_sticky(String metricKey) {
-    index(
-      // docs with rating A
-      newDoc(metricKey, 1d, NCLOC, 100d, COVERAGE, 0d),
-      newDoc(metricKey, 1d, NCLOC, 200d, COVERAGE, 0d),
-      newDoc(metricKey, 1d, NCLOC, 999d, COVERAGE, 0d),
-      // docs with rating B
-      newDoc(metricKey, 2d, NCLOC, 2000d, COVERAGE, 0d),
-      newDoc(metricKey, 2d, NCLOC, 5000d, COVERAGE, 0d),
-      // docs with rating C
-      newDoc(metricKey, 3d, NCLOC, 20000d, COVERAGE, 0d),
-      newDoc(metricKey, 3d, NCLOC, 30000d, COVERAGE, 0d),
-      newDoc(metricKey, 3d, NCLOC, 40000d, COVERAGE, 0d),
-      newDoc(metricKey, 3d, NCLOC, 50000d, COVERAGE, 0d),
-      // docs with rating D
-      newDoc(metricKey, 4d, NCLOC, 120000d, COVERAGE, 0d),
-      // docs with rating E
-      newDoc(metricKey, 5d, NCLOC, 600000d, COVERAGE, 40d),
-      newDoc(metricKey, 5d, NCLOC, 700000d, COVERAGE, 50d),
-      newDoc(metricKey, 5d, NCLOC, 800000d, COVERAGE, 60d));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-        .addMetricCriterion(MetricCriterion.create(metricKey, Operator.LT, 3d))
-        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d)),
-      new SearchOptions().addFacets(metricKey, NCLOC)).getFacets();
-
-    // Sticky facet on maintainability rating does not take into account maintainability rating filter
-    assertThat(facets.get(metricKey)).containsExactly(
-      entry("1", 3L),
-      entry("2", 2L),
-      entry("3", 4L),
-      entry("4", 1L),
-      entry("5", 0L));
-    // But facet on ncloc does well take into into filters
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 3L),
-      entry("1000.0-10000.0", 2L),
-      entry("10000.0-100000.0", 0L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating_contains_only_projects_authorized_for_user(String metricKey) {
-    // User can see these projects
-    indexForUser(USER1,
-      // 3 docs with rating A
-      newDoc(metricKey, 1d),
-      newDoc(metricKey, 1d),
-      newDoc(metricKey, 1d),
-      // 2 docs with rating B
-      newDoc(metricKey, 2d),
-      newDoc(metricKey, 2d));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      // docs with rating C
-      newDoc(metricKey, 3d),
-      // docs with rating D
-      newDoc(metricKey, 4d),
-      // docs with rating E
-      newDoc(metricKey, 5d));
-
-    userSession.logIn(USER1);
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
-
-    assertThat(facets.get(metricKey)).containsExactly(
-      entry("1", 3L),
-      entry("2", 2L),
-      entry("3", 0L),
-      entry("4", 0L),
-      entry("5", 0L));
-  }
-
-  @Test
-  public void facet_quality_gate() {
-    index(
-      // 2 docs with QG OK
-      newDoc().setQualityGateStatus(OK.name()),
-      newDoc().setQualityGateStatus(OK.name()),
-      // 3 docs with QG WARN
-      newDoc().setQualityGateStatus(WARN.name()),
-      newDoc().setQualityGateStatus(WARN.name()),
-      newDoc().setQualityGateStatus(WARN.name()),
-      // 4 docs with QG ERROR
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()));
-
-    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
-
-    assertThat(result).containsOnly(
-      entry(ERROR.name(), 4L),
-      entry(WARN.name(), 3L),
-      entry(OK.name(), 2L));
-  }
-
-  @Test
-  public void facet_quality_gate_is_sticky() {
-    index(
-      // 2 docs with QG OK
-      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGateStatus(OK.name()),
-      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGateStatus(OK.name()),
-      // 3 docs with QG WARN
-      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
-      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
-      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
-      // 4 docs with QG ERROR
-      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(ERROR.name()),
-      newDoc(NCLOC, 5000d, COVERAGE, 40d).setQualityGateStatus(ERROR.name()),
-      newDoc(NCLOC, 12000d, COVERAGE, 50d).setQualityGateStatus(ERROR.name()),
-      newDoc(NCLOC, 13000d, COVERAGE, 60d).setQualityGateStatus(ERROR.name()));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-        .setQualityGateStatus(ERROR)
-        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 55d)),
-      new SearchOptions().addFacets(ALERT_STATUS_KEY, NCLOC)).getFacets();
-
-    // Sticky facet on quality gate does not take into account quality gate filter
-    assertThat(facets.get(ALERT_STATUS_KEY)).containsOnly(
-      entry(OK.name(), 2L),
-      entry(WARN.name(), 3L),
-      entry(ERROR.name(), 3L));
-    // But facet on ncloc does well take into into filters
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 1L),
-      entry("1000.0-10000.0", 1L),
-      entry("10000.0-100000.0", 1L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_quality_gate_contains_only_projects_authorized_for_user() {
-    // User can see these projects
-    indexForUser(USER1,
-      // 2 docs with QG OK
-      newDoc().setQualityGateStatus(OK.name()),
-      newDoc().setQualityGateStatus(OK.name()),
-      // 3 docs with QG WARN
-      newDoc().setQualityGateStatus(WARN.name()),
-      newDoc().setQualityGateStatus(WARN.name()),
-      newDoc().setQualityGateStatus(WARN.name()));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      // 4 docs with QG ERROR
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()),
-      newDoc().setQualityGateStatus(ERROR.name()));
-
-    userSession.logIn(USER1);
-    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
-
-    assertThat(result).containsOnly(
-      entry(ERROR.name(), 0L),
-      entry(WARN.name(), 3L),
-      entry(OK.name(), 2L));
-  }
-
-  @Test
-  public void facet_languages() {
-    index(
-      newDoc().setLanguages(singletonList("java")),
-      newDoc().setLanguages(singletonList("java")),
-      newDoc().setLanguages(singletonList("xoo")),
-      newDoc().setLanguages(singletonList("xml")),
-      newDoc().setLanguages(asList("<null>", "java")),
-      newDoc().setLanguages(asList("<null>", "java", "xoo")));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets();
-
-    assertThat(facets.get(LANGUAGES)).containsOnly(
-      entry("<null>", 2L),
-      entry("java", 4L),
-      entry("xoo", 2L),
-      entry("xml", 1L));
-  }
-
-  @Test
-  public void facet_languages_is_limited_to_10_languages() {
-    index(
-      newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
-      newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
-      newDoc().setLanguages(asList("js", "scala")));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets();
-
-    assertThat(facets.get(LANGUAGES)).hasSize(10);
-  }
-
-  @Test
-  public void facet_languages_is_sticky() {
-    index(
-      newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
-      newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
-      newDoc(NCLOC, 10d).setLanguages(singletonList("xoo")),
-      newDoc(NCLOC, 100d).setLanguages(singletonList("xml")),
-      newDoc(NCLOC, 100d).setLanguages(asList("<null>", "java")),
-      newDoc(NCLOC, 5000d).setLanguages(asList("<null>", "java", "xoo")));
-
-    Facets facets = underTest.search(
-      new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("java")),
-      new SearchOptions().addFacets(LANGUAGES, NCLOC)).getFacets();
-
-    // Sticky facet on language does not take into account language filter
-    assertThat(facets.get(LANGUAGES)).containsOnly(
-      entry("<null>", 2L),
-      entry("java", 4L),
-      entry("xoo", 2L),
-      entry("xml", 1L));
-    // But facet on ncloc does well take account into filters
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", 3L),
-      entry("1000.0-10000.0", 1L),
-      entry("10000.0-100000.0", 0L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_languages_returns_more_than_10_languages_when_languages_filter_contains_value_not_in_top_10() {
-    index(
-      newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
-      newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
-      newDoc().setLanguages(asList("js", "scala")));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("xoo", "xml")), new SearchOptions().addFacets(LANGUAGES)).getFacets();
-
-    assertThat(facets.get(LANGUAGES)).containsOnly(
-      entry("<null>", 1L),
-      entry("cpp", 1L),
-      entry("css", 1L),
-      entry("java", 1L),
-      entry("js", 1L),
-      entry("perl", 1L),
-      entry("php", 1L),
-      entry("python", 1L),
-      entry("ruby", 1L),
-      entry("scala", 1L),
-      entry("xoo", 1L),
-      entry("xml", 1L));
-  }
-
-  @Test
-  public void facet_languages_contains_only_projects_authorized_for_user() {
-    // User can see these projects
-    indexForUser(USER1,
-      newDoc().setLanguages(singletonList("java")),
-      newDoc().setLanguages(asList("java", "xoo")));
-
-    // User cannot see these projects
-    indexForUser(USER2,
-      newDoc().setLanguages(singletonList("java")),
-      newDoc().setLanguages(asList("java", "xoo")));
-
-    userSession.logIn(USER1);
-    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets().get(LANGUAGES);
-
-    assertThat(result).containsOnly(
-      entry("java", 2L),
-      entry("xoo", 1L));
-  }
-
-  @Test
-  public void facet_tags() {
-    index(
-      newDoc().setTags(newArrayList("finance", "offshore", "java")),
-      newDoc().setTags(newArrayList("finance", "javascript")),
-      newDoc().setTags(newArrayList("marketing", "finance")),
-      newDoc().setTags(newArrayList("marketing", "offshore")),
-      newDoc().setTags(newArrayList("finance", "marketing")),
-      newDoc().setTags(newArrayList("finance")));
-
-    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FIELD_TAGS)).getFacets().get(FIELD_TAGS);
-
-    assertThat(result).containsOnly(
-      entry("finance", 5L),
-      entry("marketing", 3L),
-      entry("offshore", 2L),
-      entry("java", 1L),
-      entry("javascript", 1L));
-  }
-
-  @Test
-  public void facet_tags_is_sticky() {
-    index(
-      newDoc().setTags(newArrayList("finance")).setQualityGateStatus(OK.name()),
-      newDoc().setTags(newArrayList("finance")).setQualityGateStatus(ERROR.name()),
-      newDoc().setTags(newArrayList("cpp")).setQualityGateStatus(WARN.name()));
-
-    Facets facets = underTest.search(
-      new ProjectMeasuresQuery().setTags(newHashSet("cpp")),
-      new SearchOptions().addFacets(FIELD_TAGS).addFacets(ALERT_STATUS_KEY))
-      .getFacets();
-
-    assertThat(facets.get(FIELD_TAGS)).containsOnly(
-      entry("finance", 2L),
-      entry("cpp", 1L));
-    assertThat(facets.get(ALERT_STATUS_KEY)).containsOnly(
-      entry(OK.name(), 0L),
-      entry(ERROR.name(), 0L),
-      entry(WARN.name(), 1L));
-  }
-
-  @Test
-  public void facet_tags_returns_10_elements_by_default() {
-    index(
-      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
-      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
-      newDoc().setTags(newArrayList("solo")));
-
-    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FIELD_TAGS)).getFacets().get(FIELD_TAGS);
-
-    assertThat(result).hasSize(10).containsOnlyKeys("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10");
-  }
-
-  @Test
-  public void facet_tags_returns_more_than_10_tags_when_tags_filter_contains_value_not_in_top_10() {
-    index(
-      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
-      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
-      newDoc().setTags(newArrayList("solo", "solo2")));
-
-    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery().setTags(ImmutableSet.of("solo", "solo2")), new SearchOptions().addFacets(FIELD_TAGS)).getFacets()
-      .get(FIELD_TAGS);
-
-    assertThat(result).hasSize(12).containsOnlyKeys("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10", "solo",
-      "solo2");
-  }
-
-  @Test
-  public void search_tags() {
-    index(
-      newDoc().setTags(newArrayList("finance", "offshore", "java")),
-      newDoc().setTags(newArrayList("official", "javascript")),
-      newDoc().setTags(newArrayList("marketing", "official")),
-      newDoc().setTags(newArrayList("marketing", "Madhoff")),
-      newDoc().setTags(newArrayList("finance", "offshore")),
-      newDoc().setTags(newArrayList("offshore")));
-
-    List<String> result = underTest.searchTags("off", 10);
-
-    assertThat(result).containsOnly("offshore", "official", "Madhoff");
-  }
-
-  @Test
-  public void search_tags_return_all_tags() {
-    index(
-      newDoc().setTags(newArrayList("finance", "offshore", "java")),
-      newDoc().setTags(newArrayList("official", "javascript")),
-      newDoc().setTags(newArrayList("marketing", "official")),
-      newDoc().setTags(newArrayList("marketing", "Madhoff")),
-      newDoc().setTags(newArrayList("finance", "offshore")),
-      newDoc().setTags(newArrayList("offshore")));
-
-    List<String> result = underTest.searchTags(null, 10);
-
-    assertThat(result).containsOnly("offshore", "official", "Madhoff", "finance", "marketing", "java", "javascript");
-  }
-
-  @Test
-  public void search_tags_in_lexical_order() {
-    index(
-      newDoc().setTags(newArrayList("finance", "offshore", "java")),
-      newDoc().setTags(newArrayList("official", "javascript")),
-      newDoc().setTags(newArrayList("marketing", "official")),
-      newDoc().setTags(newArrayList("marketing", "Madhoff")),
-      newDoc().setTags(newArrayList("finance", "offshore")),
-      newDoc().setTags(newArrayList("offshore")));
-
-    List<String> result = underTest.searchTags(null, 10);
-
-    assertThat(result).containsExactly("Madhoff", "finance", "java", "javascript", "marketing", "official", "offshore");
-  }
-
-  @Test
-  public void search_tags_only_of_authorized_projects() {
-    indexForUser(USER1,
-      newDoc(PROJECT1).setTags(singletonList("finance")),
-      newDoc(PROJECT2).setTags(singletonList("marketing")));
-    indexForUser(USER2,
-      newDoc(PROJECT3).setTags(singletonList("offshore")));
-
-    userSession.logIn(USER1);
-
-    List<String> result = underTest.searchTags(null, 10);
-
-    assertThat(result).containsOnly("finance", "marketing");
-  }
-
-  @Test
-  public void search_tags_with_no_tags() {
-    List<String> result = underTest.searchTags("whatever", 10);
-
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void search_tags_with_page_size_at_0() {
-    index(newDoc().setTags(newArrayList("offshore")));
-
-    List<String> result = underTest.searchTags(null, 0);
-
-    assertThat(result).isEmpty();
-  }
-
-  @Test
-  public void search_statistics() {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES,
-      newDoc("lines", 10, "coverage", 80)
-        .setLanguages(Arrays.asList("java", "cs", "js"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 200, "cs", 250, "js", 50)),
-      newDoc("lines", 20, "coverage", 80)
-        .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)));
-
-    ProjectMeasuresStatistics result = underTest.searchTelemetryStatistics();
-
-    assertThat(result.getProjectCount()).isEqualTo(2);
-    assertThat(result.getProjectCountByLanguage()).containsOnly(
-      entry("java", 2L), entry("cs", 1L), entry("js", 1L), entry("python", 1L), entry("kotlin", 1L));
-    assertThat(result.getNclocByLanguage()).containsOnly(
-      entry("java", 500L), entry("cs", 250L), entry("js", 50L), entry("python", 100L), entry("kotlin", 404L));
-  }
-
-  @Test
-  public void fail_if_page_size_greater_than_500() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Page size must be lower than or equals to 500");
-
-    underTest.searchTags("whatever", 501);
-  }
-
-  private void index(ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
-    for (ProjectMeasuresDoc doc : docs) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(doc.getId(), Qualifiers.PROJECT);
-      access.allowAnyone();
-      authorizationIndexerTester.allow(access);
-    }
-  }
-
-  private void indexForUser(UserDto user, ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
-    for (ProjectMeasuresDoc doc : docs) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(doc.getId(), Qualifiers.PROJECT);
-      access.addUserId(user.getId());
-      authorizationIndexerTester.allow(access);
-    }
-  }
-
-  private void indexForGroup(GroupDto group, ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
-    for (ProjectMeasuresDoc doc : docs) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(doc.getId(), Qualifiers.PROJECT);
-      access.addGroupId(group.getId());
-      authorizationIndexerTester.allow(access);
-    }
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project) {
-    return new ProjectMeasuresDoc()
-      .setOrganizationUuid(project.getOrganizationUuid())
-      .setId(project.uuid())
-      .setKey(project.getDbKey())
-      .setName(project.name());
-  }
-
-  private static ProjectMeasuresDoc newDoc() {
-    return newDoc(ComponentTesting.newPrivateProjectDto(ORG));
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1) {
-    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1)));
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1, String metric2, Object value2) {
-    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1), newMeasure(metric2, value2)));
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1, String metric2, Object value2, String metric3, Object value3) {
-    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1), newMeasure(metric2, value2), newMeasure(metric3, value3)));
-  }
-
-  private static Map<String, Object> newMeasure(String key, Object value) {
-    return ImmutableMap.of("key", key, "value", value);
-  }
-
-  private static ProjectMeasuresDoc newDocWithNoMeasure() {
-    return newDoc(ComponentTesting.newPrivateProjectDto(ORG));
-  }
-
-  private static ProjectMeasuresDoc newDoc(String metric1, Object value1) {
-    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1);
-  }
-
-  private static ProjectMeasuresDoc newDoc(String metric1, Object value1, String metric2, Object value2) {
-    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1, metric2, value2);
-  }
-
-  private static ProjectMeasuresDoc newDoc(String metric1, Object value1, String metric2, Object value2, String metric3, Object value3) {
-    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1, metric2, value2, metric3, value3);
-  }
-
-  private void assertResults(ProjectMeasuresQuery query, ComponentDto... expectedProjects) {
-    List<String> result = underTest.search(query, new SearchOptions()).getIds();
-    assertThat(result).containsExactly(Arrays.stream(expectedProjects).map(ComponentDto::uuid).toArray(String[]::new));
-  }
-
-  private void assertNoResults(ProjectMeasuresQuery query) {
-    assertResults(query);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java
deleted file mode 100644 (file)
index d66998b..0000000
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.System2;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.organization.OrganizationTesting;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Arrays.stream;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.GT;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.LT;
-
-public class ProjectMeasuresIndexTextSearchTest {
-
-  private static final String NCLOC = "ncloc";
-
-  private static final OrganizationDto ORG = OrganizationTesting.newOrganizationDto();
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-
-  private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, projectMeasureIndexer);
-  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
-
-  @Test
-  public void match_exact_case_insensitive_name() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube")));
-
-    assertTextQueryResults("Apache Struts", "struts");
-    assertTextQueryResults("APACHE STRUTS", "struts");
-    assertTextQueryResults("APACHE struTS", "struts");
-  }
-
-  @Test
-  public void match_from_sub_name() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
-
-    assertTextQueryResults("truts", "struts");
-    assertTextQueryResults("pache", "struts");
-    assertTextQueryResults("apach", "struts");
-    assertTextQueryResults("che stru", "struts");
-  }
-
-  @Test
-  public void match_name_with_dot() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache.Struts")));
-
-    assertTextQueryResults("apache struts", "struts");
-  }
-
-  @Test
-  public void match_partial_name() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("XstrutsxXjavax")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_partial_name_prefix_word1() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.java")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_partial_name_suffix_word1() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("StrutsObject.java")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_partial_name_prefix_word2() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.xjava")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_partial_name_suffix_word2() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStrutsObject.xjavax")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_subset_of_document_terms() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Some.Struts.Project.java.old")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void match_partial_match_prefix_and_suffix_everywhere() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.javax")));
-
-    assertTextQueryResults("struts java", "struts");
-  }
-
-  @Test
-  public void ignore_empty_words() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Struts")));
-
-    assertTextQueryResults("            struts   \n     \n\n", "struts");
-  }
-
-  @Test
-  public void match_name_from_prefix() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
-
-    assertTextQueryResults("apach", "struts");
-    assertTextQueryResults("ApA", "struts");
-    assertTextQueryResults("AP", "struts");
-  }
-
-  @Test
-  public void match_name_from_two_words() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("project").setName("ApacheStrutsFoundation")));
-
-    assertTextQueryResults("apache struts", "project");
-    assertTextQueryResults("struts apache", "project");
-    // Only one word is matching
-    assertNoResults("apache plugin");
-    assertNoResults("project struts");
-  }
-
-  @Test
-  public void match_long_name() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("LongNameLongNameLongNameLongNameSonarQube")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("LongNameLongNameLongNameLongNameSonarQubeX")));
-
-    assertTextQueryResults("LongNameLongNameLongNameLongNameSonarQube", "project1", "project2");
-  }
-
-  @Test
-  public void match_name_with_two_characters() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
-
-    assertTextQueryResults("st", "struts");
-    assertTextQueryResults("tr", "struts");
-  }
-
-  @Test
-  public void match_exact_case_insensitive_key() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("Windows").setDbKey("project1")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("apachee").setDbKey("project2")));
-
-    assertTextQueryResults("project1", "project1");
-    assertTextQueryResults("PROJECT1", "project1");
-    assertTextQueryResults("pRoJecT1", "project1");
-  }
-
-  @Test
-  public void match_key_with_dot() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org.sonarqube")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
-
-    assertTextQueryResults("org.sonarqube", "sonarqube");
-    assertNoResults("orgsonarqube");
-    assertNoResults("org-sonarqube");
-    assertNoResults("org:sonarqube");
-    assertNoResults("org sonarqube");
-  }
-
-  @Test
-  public void match_key_with_dash() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org-sonarqube")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
-
-    assertTextQueryResults("org-sonarqube", "sonarqube");
-    assertNoResults("orgsonarqube");
-    assertNoResults("org.sonarqube");
-    assertNoResults("org:sonarqube");
-    assertNoResults("org sonarqube");
-  }
-
-  @Test
-  public void match_key_with_colon() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org:sonarqube")),
-      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
-
-    assertTextQueryResults("org:sonarqube", "sonarqube");
-    assertNoResults("orgsonarqube");
-    assertNoResults("org-sonarqube");
-    assertNoResults("org_sonarqube");
-    assertNoResults("org sonarqube");
-  }
-
-  @Test
-  public void match_key_having_all_special_characters() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org.sonarqube:sonar-sérvèr_ç")));
-
-    assertTextQueryResults("org.sonarqube:sonar-sérvèr_ç", "sonarqube");
-  }
-
-  @Test
-  public void does_not_match_partial_key() {
-    index(newDoc(newPrivateProjectDto(ORG).setUuid("project").setName("some name").setDbKey("theKey")));
-
-    assertNoResults("theke");
-    assertNoResults("hekey");
-  }
-
-  @Test
-  public void facets_take_into_account_text_search() {
-    index(
-      // docs with ncloc<1K
-      newDoc(newPrivateProjectDto(ORG).setName("Windows").setDbKey("project1"), NCLOC, 0d),
-      newDoc(newPrivateProjectDto(ORG).setName("apachee").setDbKey("project2"), NCLOC, 999d),
-      // docs with ncloc>=1K and ncloc<10K
-      newDoc(newPrivateProjectDto(ORG).setName("Apache").setDbKey("project3"), NCLOC, 1_000d),
-      // docs with ncloc>=100K and ncloc<500K
-      newDoc(newPrivateProjectDto(ORG).setName("Apache Foundation").setDbKey("project4"), NCLOC, 100_000d));
-
-    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("apache"), 1L, 1L, 0L, 1L, 0L);
-    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("PAch"), 1L, 1L, 0L, 1L, 0L);
-    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("apache foundation"), 0L, 0L, 0L, 1L, 0L);
-    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("project3"), 0L, 1L, 0L, 0L, 0L);
-    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("project"), 0L, 0L, 0L, 0L, 0L);
-  }
-
-  @Test
-  public void filter_by_metric_take_into_account_text_search() {
-    index(
-      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("Windows").setDbKey("project1"), NCLOC, 30_000d),
-      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("apachee").setDbKey("project2"), NCLOC, 40_000d),
-      newDoc(newPrivateProjectDto(ORG).setUuid("project3").setName("Apache").setDbKey("project3"), NCLOC, 50_000d),
-      newDoc(newPrivateProjectDto(ORG).setUuid("project4").setName("Apache").setDbKey("project4"), NCLOC, 60_000d));
-
-    assertResults(new ProjectMeasuresQuery().setQueryText("apache").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 20_000d)), "project3", "project4", "project2");
-    assertResults(new ProjectMeasuresQuery().setQueryText("apache").addMetricCriterion(MetricCriterion.create(NCLOC, LT, 55_000d)), "project3", "project2");
-    assertResults(new ProjectMeasuresQuery().setQueryText("PAC").addMetricCriterion(MetricCriterion.create(NCLOC, LT, 55_000d)), "project3", "project2");
-    assertResults(new ProjectMeasuresQuery().setQueryText("apachee").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 30_000d)), "project2");
-    assertResults(new ProjectMeasuresQuery().setQueryText("unknown").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 20_000d)));
-  }
-
-  private void index(ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
-    stream(docs).forEach(doc -> {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(doc.getId(), Qualifiers.PROJECT);
-      access.allowAnyone();
-      authorizationIndexerTester.allow(access);
-    });
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project) {
-    return new ProjectMeasuresDoc()
-      .setOrganizationUuid(project.getOrganizationUuid())
-      .setId(project.uuid())
-      .setKey(project.getDbKey())
-      .setName(project.name());
-  }
-
-  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1) {
-    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1)));
-  }
-
-  private static Map<String, Object> newMeasure(String key, Object value) {
-    return ImmutableMap.of("key", key, "value", value);
-  }
-
-  private void assertResults(ProjectMeasuresQuery query, String... expectedProjectUuids) {
-    List<String> result = underTest.search(query, new SearchOptions()).getIds();
-    assertThat(result).containsExactly(expectedProjectUuids);
-  }
-
-  private void assertTextQueryResults(String queryText, String... expectedProjectUuids) {
-    assertResults(new ProjectMeasuresQuery().setQueryText(queryText), expectedProjectUuids);
-  }
-
-  private void assertNoResults(String queryText) {
-    assertTextQueryResults(queryText);
-  }
-
-  private void assertNclocFacet(ProjectMeasuresQuery query, Long... facetExpectedValues) {
-    checkArgument(facetExpectedValues.length == 5, "5 facet values is required");
-    Facets facets = underTest.search(query, new SearchOptions().addFacets(NCLOC)).getFacets();
-    assertThat(facets.get(NCLOC)).containsExactly(
-      entry("*-1000.0", facetExpectedValues[0]),
-      entry("1000.0-10000.0", facetExpectedValues[1]),
-      entry("10000.0-100000.0", facetExpectedValues[2]),
-      entry("100000.0-500000.0", facetExpectedValues[3]),
-      entry("500000.0-*", facetExpectedValues[4]));
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java
deleted file mode 100644 (file)
index 6da1e29..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.measures.Metric.Level;
-import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.sonar.api.measures.Metric.Level.OK;
-import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.EQ;
-
-public class ProjectMeasuresQueryTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private ProjectMeasuresQuery underTest = new ProjectMeasuresQuery();
-
-  @Test
-  public void empty_query() {
-    assertThat(underTest.getMetricCriteria()).isEmpty();
-    assertThat(underTest.getQualityGateStatus()).isEmpty();
-    assertThat(underTest.getOrganizationUuid()).isEmpty();
-  }
-
-  @Test
-  public void add_metric_criterion() {
-    underTest.addMetricCriterion(MetricCriterion.create("coverage", EQ, 10d));
-
-    assertThat(underTest.getMetricCriteria())
-      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
-      .containsOnly(tuple("coverage", EQ, 10d));
-  }
-
-  @Test
-  public void isNoData_returns_true_when_no_data() {
-    underTest.addMetricCriterion(MetricCriterion.createNoData("coverage"));
-
-    assertThat(underTest.getMetricCriteria())
-      .extracting(MetricCriterion::getMetricKey, MetricCriterion::isNoData)
-      .containsOnly(tuple("coverage", true));
-  }
-
-  @Test
-  public void isNoData_returns_false_when_data_exists() {
-    underTest.addMetricCriterion(MetricCriterion.create("coverage", EQ, 10d));
-
-    assertThat(underTest.getMetricCriteria())
-      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::isNoData)
-      .containsOnly(tuple("coverage", EQ, false));
-  }
-
-  @Test
-  public void set_quality_gate_status() {
-    underTest.setQualityGateStatus(OK);
-
-    assertThat(underTest.getQualityGateStatus().get()).isEqualTo(Level.OK);
-  }
-
-  @Test
-  public void default_sort_is_by_name() {
-    assertThat(underTest.getSort()).isEqualTo("name");
-  }
-
-  @Test
-  public void fail_to_set_null_sort() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("Sort cannot be null");
-
-    underTest.setSort(null);
-  }
-
-  @Test
-  public void fail_to_get_value_when_no_data() {
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("The criterion for metric coverage has no data");
-
-    MetricCriterion.createNoData("coverage").getValue();
-  }
-
-  @Test
-  public void fail_to_get_operator_when_no_data() {
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("The criterion for metric coverage has no data");
-
-    MetricCriterion.createNoData("coverage").getOperator();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java
deleted file mode 100644 (file)
index 9f36521..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-import org.sonar.server.measure.index.ProjectsEsModule;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectsEsModuleTest {
-  @Test
-  public void verify_count_of_added_components() {
-    ComponentContainer container = new ComponentContainer();
-    new ProjectsEsModule().configure(container);
-    assertThat(container.size()).isEqualTo(3 + 2);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java
deleted file mode 100644 (file)
index e4d0d77..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import org.elasticsearch.index.query.MatchAllQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.join.query.HasParentQueryBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.GroupTesting;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.test.JsonAssert.assertJson;
-
-public class AuthorizationTypeSupportTest {
-
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-
-  private AuthorizationTypeSupport underTest = new AuthorizationTypeSupport(userSession);
-
-  @Test
-  public void createQueryFilter_does_not_include_permission_filters_if_user_is_flagged_as_root() {
-    userSession.logIn().setRoot();
-
-    QueryBuilder filter = underTest.createQueryFilter();
-
-    assertThat(filter).isInstanceOf(MatchAllQueryBuilder.class);
-  }
-
-  @Test
-  public void createQueryFilter_sets_filter_on_anyone_group_if_user_is_anonymous() {
-    userSession.anonymous();
-
-    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
-
-    assertJson(filter.toString()).isSimilarTo("{" +
-        "  \"has_parent\" : {" +
-        "    \"query\" : {" +
-        "      \"bool\" : {" +
-        "        \"filter\" : [{" +
-        "          \"bool\" : {" +
-        "            \"should\" : [{" +
-        "              \"term\" : {" +
-        "                \"allowAnyone\" : {\"value\": true}" +
-        "              }" +
-        "            }]" +
-        "          }" +
-        "        }]" +
-        "      }" +
-        "    }," +
-        "    \"parent_type\" : \"authorization\"" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void createQueryFilter_sets_filter_on_anyone_and_user_id_if_user_is_logged_in_but_has_no_groups() {
-    userSession.logIn().setUserId(1234);
-
-    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
-
-    assertJson(filter.toString()).isSimilarTo("{" +
-        "  \"has_parent\": {" +
-        "    \"query\": {" +
-        "      \"bool\": {" +
-        "        \"filter\": [{" +
-        "          \"bool\": {" +
-        "            \"should\": [" +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"allowAnyone\": {\"value\": true}" +
-        "                }" +
-        "              }," +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"userIds\": {\"value\": 1234}" +
-        "                }" +
-        "              }" +
-        "            ]" +
-        "          }" +
-        "        }]" +
-        "      }" +
-        "    }," +
-        "    \"parent_type\": \"authorization\"" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void createQueryFilter_sets_filter_on_anyone_and_user_id_and_group_ids_if_user_is_logged_in_and_has_groups() {
-    GroupDto group1 = GroupTesting.newGroupDto().setId(10);
-    GroupDto group2 = GroupTesting.newGroupDto().setId(11);
-    userSession.logIn().setUserId(1234).setGroups(group1, group2);
-
-    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
-
-    assertJson(filter.toString()).isSimilarTo("{" +
-        "  \"has_parent\": {" +
-        "    \"query\": {" +
-        "      \"bool\": {" +
-        "        \"filter\": [{" +
-        "          \"bool\": {" +
-        "            \"should\": [" +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"allowAnyone\": {\"value\": true}" +
-        "                }" +
-        "              }," +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"userIds\": {\"value\": 1234}" +
-        "                }" +
-        "              }," +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"groupIds\": {\"value\": 10}" +
-        "                }" +
-        "              }," +
-        "              {" +
-        "                \"term\": {" +
-        "                  \"groupIds\": {\"value\": 11}" +
-        "                }" +
-        "              }" +
-        "            ]" +
-        "          }" +
-        "        }]" +
-        "      }" +
-        "    }," +
-        "    \"parent_type\": \"authorization\"" +
-        "  }" +
-        "}");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndex.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndex.java
deleted file mode 100644 (file)
index 8f0c8dc..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import java.util.Arrays;
-import java.util.List;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHits;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.server.es.EsClient;
-
-import static org.sonar.server.permission.index.FooIndexDefinition.FOO_INDEX;
-import static org.sonar.server.permission.index.FooIndexDefinition.FOO_TYPE;
-
-public class FooIndex {
-
-  private final EsClient esClient;
-  private final AuthorizationTypeSupport authorizationTypeSupport;
-
-  public FooIndex(EsClient esClient, AuthorizationTypeSupport authorizationTypeSupport) {
-    this.esClient = esClient;
-    this.authorizationTypeSupport = authorizationTypeSupport;
-  }
-
-  public boolean hasAccessToProject(String projectUuid) {
-    SearchHits hits = esClient.prepareSearch(FOO_INDEX)
-      .setTypes(FOO_TYPE)
-      .setQuery(QueryBuilders.boolQuery()
-        .must(QueryBuilders.termQuery(FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
-        .filter(authorizationTypeSupport.createQueryFilter()))
-      .get()
-      .getHits();
-    List<String> names = Arrays.stream(hits.hits())
-      .map(h -> h.getSource().get(FooIndexDefinition.FIELD_NAME).toString())
-      .collect(MoreCollectors.toList());
-    return names.size() == 2 && names.contains("bar") && names.contains("baz");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java
deleted file mode 100644 (file)
index fa3d294..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
-
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
-
-public class FooIndexDefinition implements IndexDefinition {
-
-  public static final String FOO_INDEX = "foos";
-  public static final String FOO_TYPE = "foo";
-  public static final IndexType INDEX_TYPE_FOO = new IndexType(FOO_INDEX, FOO_TYPE);
-  public static final String FIELD_NAME = "name";
-  public static final String FIELD_PROJECT_UUID = "projectUuid";
-
-  @Override
-  public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(FOO_INDEX, newBuilder(new MapSettings().asConfig()).setRefreshInterval(MANUAL_REFRESH_INTERVAL).build());
-
-    NewIndex.NewIndexType type = index.createType(FOO_TYPE)
-      .requireProjectAuthorization();
-
-    type.keywordFieldBuilder(FIELD_NAME).build();
-    type.keywordFieldBuilder(FIELD_PROJECT_UUID).build();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexer.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/FooIndexer.java
deleted file mode 100644 (file)
index f512268..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.util.Collection;
-import java.util.Set;
-import org.sonar.db.DbSession;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.ProjectIndexer;
-
-import static org.sonar.server.permission.index.FooIndexDefinition.INDEX_TYPE_FOO;
-
-public class FooIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
-
-  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_FOO, p -> true);
-
-  private final EsClient esClient;
-
-  public FooIndexer(EsClient esClient) {
-    this.esClient = esClient;
-  }
-
-  @Override
-  public AuthorizationScope getAuthorizationScope() {
-    return AUTHORIZATION_SCOPE;
-  }
-
-  @Override
-  public void indexOnAnalysis(String branchUuid) {
-    addToIndex(branchUuid, "bar");
-    addToIndex(branchUuid, "baz");
-  }
-
-  @Override
-  public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
-    throw new UnsupportedOperationException();
-  }
-
-  private void addToIndex(String projectUuid, String name) {
-    esClient.prepareIndex(INDEX_TYPE_FOO)
-      .setRouting(projectUuid)
-      .setParent(projectUuid)
-      .setSource(ImmutableMap.of(
-        FooIndexDefinition.FIELD_NAME, name,
-        FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
-      .get();
-  }
-
-  @Override
-  public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_FOO);
-  }
-
-  @Override
-  public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
-    throw new UnsupportedOperationException();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java
deleted file mode 100644 (file)
index 75eb48e..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.GroupPermissionDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDbTester;
-import org.sonar.db.user.UserDto;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.resources.Qualifiers.APP;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-import static org.sonar.api.resources.Qualifiers.VIEW;
-import static org.sonar.api.web.UserRole.ADMIN;
-import static org.sonar.api.web.UserRole.USER;
-
-public class PermissionIndexerDaoTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
-
-  private ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
-  private UserDbTester userDbTester = new UserDbTester(dbTester);
-
-  private OrganizationDto organization;
-  private ComponentDto publicProject;
-  private ComponentDto privateProject1;
-  private ComponentDto privateProject2;
-  private ComponentDto view1;
-  private ComponentDto view2;
-  private ComponentDto application;
-  private UserDto user1;
-  private UserDto user2;
-  private GroupDto group;
-
-  private PermissionIndexerDao underTest = new PermissionIndexerDao();
-
-  @Before
-  public void setUp() {
-    organization = dbTester.organizations().insert();
-    publicProject = componentDbTester.insertPublicProject(organization);
-    privateProject1 = componentDbTester.insertPrivateProject(organization);
-    privateProject2 = componentDbTester.insertPrivateProject(organization);
-    view1 = componentDbTester.insertView(organization);
-    view2 = componentDbTester.insertView(organization);
-    application = componentDbTester.insertApplication(organization);
-    user1 = userDbTester.insertUser();
-    user2 = userDbTester.insertUser();
-    group = userDbTester.insertGroup(organization);
-  }
-
-  @Test
-  public void select_all() {
-    insertTestDataForProjectsAndViews();
-
-    Collection<PermissionIndexerDao.Dto> dtos = underTest.selectAll(dbClient, dbSession);
-    assertThat(dtos).hasSize(6);
-
-    PermissionIndexerDao.Dto publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos);
-    isPublic(publicProjectAuthorization, PROJECT);
-
-    PermissionIndexerDao.Dto view1Authorization = getByProjectUuid(view1.uuid(), dtos);
-    isPublic(view1Authorization, VIEW);
-
-    PermissionIndexerDao.Dto applicationAuthorization = getByProjectUuid(application.uuid(), dtos);
-    isPublic(applicationAuthorization, APP);
-
-    PermissionIndexerDao.Dto privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos);
-    assertThat(privateProject1Authorization.getGroupIds()).containsOnly(group.getId());
-    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject1Authorization.getUserIds()).containsOnly(user1.getId(), user2.getId());
-    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    PermissionIndexerDao.Dto privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos);
-    assertThat(privateProject2Authorization.getGroupIds()).isEmpty();
-    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject2Authorization.getUserIds()).containsOnly(user1.getId());
-    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    PermissionIndexerDao.Dto view2Authorization = getByProjectUuid(view2.uuid(), dtos);
-    isPublic(view2Authorization, VIEW);
-  }
-
-  @Test
-  public void selectByUuids() {
-    insertTestDataForProjectsAndViews();
-
-    Map<String, PermissionIndexerDao.Dto> dtos = underTest
-      .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid()))
-      .stream()
-      .collect(MoreCollectors.uniqueIndex(PermissionIndexerDao.Dto::getProjectUuid, Function.identity()));
-    assertThat(dtos).hasSize(6);
-
-    PermissionIndexerDao.Dto publicProjectAuthorization = dtos.get(publicProject.uuid());
-    isPublic(publicProjectAuthorization, PROJECT);
-
-    PermissionIndexerDao.Dto view1Authorization = dtos.get(view1.uuid());
-    isPublic(view1Authorization, VIEW);
-
-    PermissionIndexerDao.Dto applicationAuthorization = dtos.get(application.uuid());
-    isPublic(applicationAuthorization, APP);
-
-    PermissionIndexerDao.Dto privateProject1Authorization = dtos.get(privateProject1.uuid());
-    assertThat(privateProject1Authorization.getGroupIds()).containsOnly(group.getId());
-    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject1Authorization.getUserIds()).containsOnly(user1.getId(), user2.getId());
-    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    PermissionIndexerDao.Dto privateProject2Authorization = dtos.get(privateProject2.uuid());
-    assertThat(privateProject2Authorization.getGroupIds()).isEmpty();
-    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject2Authorization.getUserIds()).containsOnly(user1.getId());
-    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    PermissionIndexerDao.Dto view2Authorization = dtos.get(view2.uuid());
-    isPublic(view2Authorization, VIEW);
-  }
-
-  @Test
-  public void selectByUuids_returns_empty_list_when_project_does_not_exist() {
-    insertTestDataForProjectsAndViews();
-
-    List<PermissionIndexerDao.Dto> dtos = underTest.selectByUuids(dbClient, dbSession, asList("missing"));
-    assertThat(dtos).isEmpty();
-  }
-
-  @Test
-  public void select_by_projects_with_high_number_of_projects() {
-    List<String> projectUuids = new ArrayList<>();
-    for (int i = 0; i < 350; i++) {
-      ComponentDto project = ComponentTesting.newPrivateProjectDto(organization, Integer.toString(i));
-      dbClient.componentDao().insert(dbSession, project);
-      projectUuids.add(project.uuid());
-      GroupPermissionDto dto = new GroupPermissionDto()
-        .setOrganizationUuid(group.getOrganizationUuid())
-        .setGroupId(group.getId())
-        .setRole(USER)
-        .setResourceId(project.getId());
-      dbClient.groupPermissionDao().insert(dbSession, dto);
-    }
-    dbSession.commit();
-
-    assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids))
-      .hasSize(350)
-      .extracting(PermissionIndexerDao.Dto::getProjectUuid)
-      .containsAll(projectUuids);
-  }
-
-  @Test
-  public void return_private_project_without_any_permission_when_no_permission_in_DB() {
-    List<PermissionIndexerDao.Dto> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    // no permissions
-    assertThat(dtos).hasSize(1);
-    PermissionIndexerDao.Dto dto = dtos.get(0);
-    assertThat(dto.getGroupIds()).isEmpty();
-    assertThat(dto.getUserIds()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  @Test
-  public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() {
-    List<PermissionIndexerDao.Dto> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid()));
-
-    assertThat(dtos).hasSize(1);
-    PermissionIndexerDao.Dto dto = dtos.get(0);
-    assertThat(dto.getGroupIds()).isEmpty();
-    assertThat(dto.getUserIds()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isTrue();
-    assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier());
-  }
-
-  @Test
-  public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() {
-    dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1);
-    List<PermissionIndexerDao.Dto> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    assertThat(dtos).hasSize(1);
-    PermissionIndexerDao.Dto dto = dtos.get(0);
-    assertThat(dto.getGroupIds()).isEmpty();
-    assertThat(dto.getUserIds()).containsOnly(user1.getId());
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  @Test
-  public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() {
-    dbTester.users().insertMember(group, user1);
-    dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1);
-    List<PermissionIndexerDao.Dto> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    assertThat(dtos).hasSize(1);
-    PermissionIndexerDao.Dto dto = dtos.get(0);
-    assertThat(dto.getGroupIds()).containsOnly(group.getId());
-    assertThat(dto.getUserIds()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  private void isPublic(PermissionIndexerDao.Dto view1Authorization, String qualifier) {
-    assertThat(view1Authorization.getGroupIds()).isEmpty();
-    assertThat(view1Authorization.isAllowAnyone()).isTrue();
-    assertThat(view1Authorization.getUserIds()).isEmpty();
-    assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier);
-  }
-
-  private static PermissionIndexerDao.Dto getByProjectUuid(String projectUuid, Collection<PermissionIndexerDao.Dto> dtos) {
-    return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new);
-  }
-
-  private void insertTestDataForProjectsAndViews() {
-    // user1 has USER access on both private projects
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject);
-    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2);
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1);
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application);
-
-    // user2 has USER access on privateProject1 only
-    userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2);
-
-    // group1 has USER access on privateProject1 only
-    userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java
deleted file mode 100644 (file)
index cab4ce4..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import java.util.Collection;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.ProjectIndexer;
-import org.sonar.server.user.LightUserSessionRule;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.web.UserRole.ADMIN;
-import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.server.es.ProjectIndexer.Cause.PERMISSION_CHANGE;
-
-public class PermissionIndexerTest {
-
-  private static final IndexType INDEX_TYPE_FOO_AUTH = AuthorizationTypeSupport.getAuthorizationIndexType(FooIndexDefinition.INDEX_TYPE_FOO);
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public EsTester es = EsTester.createCustom(new FooIndexDefinition());
-  @Rule
-  public LightUserSessionRule userSession = new LightUserSessionRule();
-
-  private FooIndex fooIndex = new FooIndex(es.client(), new AuthorizationTypeSupport(userSession));
-  private FooIndexer fooIndexer = new FooIndexer(es.client());
-  private PermissionIndexer underTest = new PermissionIndexer(db.getDbClient(), es.client(), fooIndexer);
-
-  @Test
-  public void indexOnStartup_grants_access_to_any_user_and_to_group_Anyone_on_public_projects() {
-    ComponentDto project = createAndIndexPublicProject();
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-
-    indexOnStartup();
-
-    verifyAnyoneAuthorized(project);
-    verifyAuthorized(project, user1);
-    verifyAuthorized(project, user2);
-  }
-
-  @Test
-  public void deletion_resilience_will_deindex_projects() {
-    ComponentDto project1 = createUnindexedPublicProject();
-    ComponentDto project2 = createUnindexedPublicProject();
-    //UserDto user1 = db.users().insertUser();
-    indexOnStartup();
-    assertThat(es.countDocuments(INDEX_TYPE_FOO_AUTH)).isEqualTo(2);
-
-    // Simulate a indexation issue
-    db.getDbClient().componentDao().delete(db.getSession(), project1.getId());
-    underTest.prepareForRecovery(db.getSession(), asList(project1.uuid()), ProjectIndexer.Cause.PROJECT_DELETION);
-    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isEqualTo(1);
-    Collection<EsQueueDto> esQueueDtos = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), Long.MAX_VALUE, 2);
-
-    underTest.index(db.getSession(), esQueueDtos);
-
-    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isEqualTo(0);
-    assertThat(es.countDocuments(INDEX_TYPE_FOO_AUTH)).isEqualTo(1);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_to_user() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    db.users().insertProjectPermissionOnUser(user1, USER, project);
-    db.users().insertProjectPermissionOnUser(user2, ADMIN, project);
-
-    indexOnStartup();
-
-    // anonymous
-    verifyAnyoneNotAuthorized(project);
-
-    // user1 has access
-    verifyAuthorized(project, user1);
-
-    // user2 has not access (only USER permission is accepted)
-    verifyNotAuthorized(project, user2);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_to_group_on_private_project() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    UserDto user3 = db.users().insertUser();
-    GroupDto group1 = db.users().insertGroup();
-    GroupDto group2 = db.users().insertGroup();
-    db.users().insertProjectPermissionOnGroup(group1, USER, project);
-    db.users().insertProjectPermissionOnGroup(group2, ADMIN, project);
-
-    indexOnStartup();
-
-    // anonymous
-    verifyAnyoneNotAuthorized(project);
-
-    // group1 has access
-    verifyAuthorized(project, user1, group1);
-
-    // group2 has not access (only USER permission is accepted)
-    verifyNotAuthorized(project, user2, group2);
-
-    // user3 is not in any group
-    verifyNotAuthorized(project, user3);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_to_user_and_group() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    GroupDto group = db.users().insertGroup();
-    db.users().insertMember(group, user2);
-    db.users().insertProjectPermissionOnUser(user1, USER, project);
-    db.users().insertProjectPermissionOnGroup(group, USER, project);
-
-    indexOnStartup();
-
-    // anonymous
-    verifyAnyoneNotAuthorized(project);
-
-    // has direct access
-    verifyAuthorized(project, user1);
-
-    // has access through group
-    verifyAuthorized(project, user1, group);
-
-    // no access
-    verifyNotAuthorized(project, user2);
-  }
-
-  @Test
-  public void indexOnStartup_does_not_grant_access_to_anybody_on_private_project() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user = db.users().insertUser();
-    GroupDto group = db.users().insertGroup();
-
-    indexOnStartup();
-
-    verifyAnyoneNotAuthorized(project);
-    verifyNotAuthorized(project, user);
-    verifyNotAuthorized(project, user, group);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_to_anybody_on_public_project() {
-    ComponentDto project = createAndIndexPublicProject();
-    UserDto user = db.users().insertUser();
-    GroupDto group = db.users().insertGroup();
-
-    indexOnStartup();
-
-    verifyAnyoneAuthorized(project);
-    verifyAuthorized(project, user);
-    verifyAuthorized(project, user, group);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_to_anybody_on_view() {
-    ComponentDto view = createAndIndexView();
-    UserDto user = db.users().insertUser();
-    GroupDto group = db.users().insertGroup();
-
-    indexOnStartup();
-
-    verifyAnyoneAuthorized(view);
-    verifyAuthorized(view, user);
-    verifyAuthorized(view, user, group);
-  }
-
-  @Test
-  public void indexOnStartup_grants_access_on_many_projects() {
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    ComponentDto project = null;
-    for (int i = 0; i < 10; i++) {
-      project = createAndIndexPrivateProject();
-      db.users().insertProjectPermissionOnUser(user1, USER, project);
-    }
-
-    indexOnStartup();
-
-    verifyAnyoneNotAuthorized(project);
-    verifyAuthorized(project, user1);
-    verifyNotAuthorized(project, user2);
-  }
-
-  @Test
-  public void public_projects_are_visible_to_anybody_whatever_the_organization() {
-    ComponentDto projectOnOrg1 = createAndIndexPublicProject(db.organizations().insert());
-    ComponentDto projectOnOrg2 = createAndIndexPublicProject(db.organizations().insert());
-    UserDto user = db.users().insertUser();
-
-    indexOnStartup();
-
-    verifyAnyoneAuthorized(projectOnOrg1);
-    verifyAnyoneAuthorized(projectOnOrg2);
-    verifyAuthorized(projectOnOrg1, user);
-    verifyAuthorized(projectOnOrg2, user);
-  }
-
-  @Test
-  public void indexOnAnalysis_does_nothing_because_CE_does_not_touch_permissions() {
-    ComponentDto project = createAndIndexPublicProject();
-
-    underTest.indexOnAnalysis(project.uuid());
-
-    assertThatAuthIndexHasSize(0);
-    verifyAnyoneNotAuthorized(project);
-  }
-
-  @Test
-  public void permissions_are_not_updated_on_project_tags_update() {
-    ComponentDto project = createAndIndexPublicProject();
-
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
-
-    assertThatAuthIndexHasSize(0);
-    verifyAnyoneNotAuthorized(project);
-  }
-
-  @Test
-  public void permissions_are_not_updated_on_project_key_update() {
-    ComponentDto project = createAndIndexPublicProject();
-
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
-
-    assertThatAuthIndexHasSize(0);
-    verifyAnyoneNotAuthorized(project);
-  }
-
-  @Test
-  public void index_permissions_on_project_creation() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user = db.users().insertUser();
-    db.users().insertProjectPermissionOnUser(user, USER, project);
-
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
-
-    assertThatAuthIndexHasSize(1);
-    verifyAuthorized(project, user);
-  }
-
-  @Test
-  public void index_permissions_on_permission_change() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    db.users().insertProjectPermissionOnUser(user1, USER, project);
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
-    verifyAuthorized(project, user1);
-    verifyNotAuthorized(project, user2);
-
-    db.users().insertProjectPermissionOnUser(user2, USER, project);
-    indexPermissions(project, PERMISSION_CHANGE);
-
-    verifyAuthorized(project, user1);
-    verifyAuthorized(project, user1);
-  }
-
-  @Test
-  public void delete_permissions_on_project_deletion() {
-    ComponentDto project = createAndIndexPrivateProject();
-    UserDto user = db.users().insertUser();
-    db.users().insertProjectPermissionOnUser(user, USER, project);
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
-    verifyAuthorized(project, user);
-
-    db.getDbClient().componentDao().delete(db.getSession(), project.getId());
-    indexPermissions(project, ProjectIndexer.Cause.PROJECT_DELETION);
-
-    verifyNotAuthorized(project, user);
-    assertThatAuthIndexHasSize(0);
-  }
-
-  @Test
-  public void errors_during_indexing_are_recovered() {
-    ComponentDto project = createAndIndexPublicProject();
-    es.lockWrites(INDEX_TYPE_FOO_AUTH);
-
-    IndexingResult result = indexPermissions(project, PERMISSION_CHANGE);
-    assertThat(result.getTotal()).isEqualTo(1L);
-    assertThat(result.getFailures()).isEqualTo(1L);
-
-    // index is still read-only, fail to recover
-    result = recover();
-    assertThat(result.getTotal()).isEqualTo(1L);
-    assertThat(result.getFailures()).isEqualTo(1L);
-    assertThatAuthIndexHasSize(0);
-    assertThatEsQueueTableHasSize(1);
-
-    es.unlockWrites(INDEX_TYPE_FOO_AUTH);
-
-    result = recover();
-    assertThat(result.getTotal()).isEqualTo(1L);
-    assertThat(result.getFailures()).isEqualTo(0L);
-    verifyAnyoneAuthorized(project);
-    assertThatEsQueueTableHasSize(0);
-  }
-
-  private void assertThatAuthIndexHasSize(int expectedSize) {
-    IndexType authIndexType = underTest.getIndexTypes().iterator().next();
-    assertThat(es.countDocuments(authIndexType)).isEqualTo(expectedSize);
-  }
-
-  private void indexOnStartup() {
-    underTest.indexOnStartup(underTest.getIndexTypes());
-  }
-
-  private void verifyAuthorized(ComponentDto project, UserDto user) {
-    logIn(user);
-    verifyAuthorized(project, true);
-  }
-
-  private void verifyAuthorized(ComponentDto project, UserDto user, GroupDto group) {
-    logIn(user).setGroups(group);
-    verifyAuthorized(project, true);
-  }
-
-  private void verifyNotAuthorized(ComponentDto project, UserDto user) {
-    logIn(user);
-    verifyAuthorized(project, false);
-  }
-
-  private void verifyNotAuthorized(ComponentDto project, UserDto user, GroupDto group) {
-    logIn(user).setGroups(group);
-    verifyAuthorized(project, false);
-  }
-
-  private void verifyAnyoneAuthorized(ComponentDto project) {
-    userSession.anonymous();
-    verifyAuthorized(project, true);
-  }
-
-  private void verifyAnyoneNotAuthorized(ComponentDto project) {
-    userSession.anonymous();
-    verifyAuthorized(project, false);
-  }
-
-  private void verifyAuthorized(ComponentDto project, boolean expectedAccess) {
-    assertThat(fooIndex.hasAccessToProject(project.uuid())).isEqualTo(expectedAccess);
-  }
-
-  private LightUserSessionRule logIn(UserDto u) {
-    userSession.logIn(u.getLogin()).setUserId(u.getId());
-    return userSession;
-  }
-
-  private IndexingResult indexPermissions(ComponentDto project, ProjectIndexer.Cause cause) {
-    DbSession dbSession = db.getSession();
-    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
-    dbSession.commit();
-    return underTest.index(dbSession, items);
-  }
-
-  private ComponentDto createUnindexedPublicProject() {
-    ComponentDto project = db.components().insertPublicProject();
-    return project;
-  }
-
-  private ComponentDto createAndIndexPrivateProject() {
-    ComponentDto project = db.components().insertPrivateProject();
-    fooIndexer.indexOnAnalysis(project.uuid());
-    return project;
-  }
-
-  private ComponentDto createAndIndexPublicProject() {
-    ComponentDto project = db.components().insertPublicProject();
-    fooIndexer.indexOnAnalysis(project.uuid());
-    return project;
-  }
-
-  private ComponentDto createAndIndexView() {
-    ComponentDto view = db.components().insertView();
-    fooIndexer.indexOnAnalysis(view.uuid());
-    return view;
-  }
-
-  private ComponentDto createAndIndexPublicProject(OrganizationDto org) {
-    ComponentDto project = db.components().insertPublicProject(org);
-    fooIndexer.indexOnAnalysis(project.uuid());
-    return project;
-  }
-
-  private IndexingResult recover() {
-    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
-    return underTest.index(db.getSession(), items);
-  }
-
-  private void assertThatEsQueueTableHasSize(int expectedSize) {
-    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java
deleted file mode 100644 (file)
index 41a9c6b..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.permission.index;
-
-import java.util.Arrays;
-import java.util.stream.Stream;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-
-import static java.util.Arrays.asList;
-
-public class PermissionIndexerTester {
-
-  private final PermissionIndexer permissionIndexer;
-
-  public PermissionIndexerTester(EsTester esTester, NeedAuthorizationIndexer indexer, NeedAuthorizationIndexer... others) {
-    NeedAuthorizationIndexer[] indexers = Stream.concat(Stream.of(indexer), Arrays.stream(others)).toArray(NeedAuthorizationIndexer[]::new);
-    this.permissionIndexer = new PermissionIndexer(null, esTester.client(), indexers);
-  }
-
-  public PermissionIndexerTester allowOnlyAnyone(ComponentDto project) {
-    PermissionIndexerDao.Dto dto = new PermissionIndexerDao.Dto(project.uuid(), project.qualifier());
-    dto.allowAnyone();
-    permissionIndexer.index(asList(dto));
-    return this;
-  }
-
-  public PermissionIndexerTester allowOnlyUser(ComponentDto project, UserDto user) {
-    PermissionIndexerDao.Dto dto = new PermissionIndexerDao.Dto(project.uuid(), project.qualifier())
-      .addUserId(user.getId());
-    permissionIndexer.index(asList(dto));
-    return this;
-  }
-
-  public PermissionIndexerTester allowOnlyGroup(ComponentDto project, GroupDto group) {
-    PermissionIndexerDao.Dto dto = new PermissionIndexerDao.Dto(project.uuid(), project.qualifier())
-      .addGroupId(group.getId());
-    permissionIndexer.index(asList(dto));
-    return this;
-  }
-
-  public PermissionIndexerTester allow(PermissionIndexerDao.Dto access) {
-    permissionIndexer.index(asList(access));
-    return this;
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/user/LightUserSessionRule.java b/server/sonar-server-common/src/test/java/org/sonar/server/user/LightUserSessionRule.java
deleted file mode 100644 (file)
index 640dcb9..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.user;
-
-import com.google.common.collect.HashMultimap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.sonar.api.web.UserRole;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-
-import static com.google.common.collect.Maps.newHashMap;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-
-public class LightUserSessionRule extends BaseUserSession implements TestRule {
-  private HashMultimap<String, String> projectUuidByPermission = HashMultimap.create();
-  private Set<String> projectPermissionsCheckedByUuid = new HashSet<>();
-  private Map<String, String> projectUuidByComponentUuid = newHashMap();
-  private boolean root = false;
-  private String login;
-  private Integer userId;
-  private String uuid;
-  private String name;
-  private Set<GroupDto> groups = new HashSet<>();
-
-  public Statement apply(final Statement base, Description description) {
-    return new Statement() {
-      @Override
-      public void evaluate() throws Throwable {
-        reset();
-        try {
-          base.evaluate();
-        } finally {
-          reset();
-        }
-      }
-    };
-  }
-
-  private void reset() {
-    this.root = false;
-    this.login = null;
-    this.userId = null;
-    this.userId = null;
-    this.name = null;
-    this.groups.clear();
-  }
-
-  public LightUserSessionRule setRoot() {
-    this.root = true;
-    return this;
-  }
-
-  public LightUserSessionRule anonymous() {
-    reset();
-    return this;
-  }
-
-  public LightUserSessionRule logIn() {
-    return logIn(randomAlphabetic(6));
-  }
-
-  public LightUserSessionRule logIn(String login) {
-    reset();
-    this.login = login;
-    return this;
-  }
-
-  public LightUserSessionRule logIn(UserDto userDto) {
-    reset();
-    logIn(userDto.getLogin());
-    setUserId(userDto.getId());
-    return this;
-  }
-
-  public LightUserSessionRule setUserId(Integer userId) {
-    this.userId = userId;
-    return this;
-  }
-
-  public LightUserSessionRule setUuid(String uuid) {
-    this.uuid = uuid;
-    return this;
-  }
-
-  public LightUserSessionRule setName(String name) {
-    this.name = name;
-    return this;
-  }
-
-  public LightUserSessionRule setGroups(GroupDto... groups) {
-    this.groups.clear();
-    this.groups.addAll(Arrays.asList(groups));
-    return this;
-  }
-
-  /**
-   * Use this method to register public root component and non root components the UserSession must be aware of.
-   * (ie. this method can be used to emulate the content of the DB)
-   */
-  public LightUserSessionRule registerComponents(ComponentDto... components) {
-    Arrays.stream(components)
-      .forEach(component -> {
-        if (component.projectUuid().equals(component.uuid()) && !component.isPrivate()) {
-          this.projectUuidByPermission.put(UserRole.USER, component.uuid());
-          this.projectUuidByPermission.put(UserRole.CODEVIEWER, component.uuid());
-          this.projectPermissionsCheckedByUuid.add(UserRole.USER);
-          this.projectPermissionsCheckedByUuid.add(UserRole.CODEVIEWER);
-        }
-        this.projectUuidByComponentUuid.put(component.uuid(), component.projectUuid());
-      });
-    return this;
-  }
-
-  @Override
-  protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) {
-    throw new UnsupportedOperationException("hasPermissionImpl not implemented");
-  }
-
-  @Override
-  protected boolean hasMembershipImpl(OrganizationDto organization) {
-    throw new UnsupportedOperationException("hasMembershipImpl not implemented");
-  }
-
-  @Override
-  protected Optional<String> componentUuidToProjectUuid(String componentUuid) {
-    return Optional.ofNullable(projectUuidByComponentUuid.get(componentUuid));
-  }
-
-  @Override
-  protected boolean hasProjectUuidPermission(String permission, String projectUuid) {
-    return projectPermissionsCheckedByUuid.contains(permission) && projectUuidByPermission.get(permission).contains(projectUuid);
-  }
-
-  @CheckForNull
-  @Override
-  public String getLogin() {
-    return login;
-  }
-
-  @CheckForNull
-  @Override
-  public String getUuid() {
-    return uuid;
-  }
-
-  @CheckForNull
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @CheckForNull
-  @Override
-  public Integer getUserId() {
-    return userId;
-  }
-
-  @Override
-  public Collection<GroupDto> getGroups() {
-    return groups;
-  }
-
-  @Override
-  public boolean isLoggedIn() {
-    return login != null;
-  }
-
-  @Override
-  public boolean isRoot() {
-    return root;
-  }
-
-  @Override
-  public UserSession checkIsRoot() {
-    throw new UnsupportedOperationException("checkIsRoot not implemented");
-  }
-
-  @Override
-  public UserSession checkLoggedIn() {
-    throw new UnsupportedOperationException("checkLoggedIn not implemented");
-  }
-
-  @Override
-  public UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization) {
-    throw new UnsupportedOperationException("checkPermission not implemented");
-  }
-
-  @Override
-  public UserSession checkPermission(OrganizationPermission permission, String organizationUuid) {
-    throw new UnsupportedOperationException("checkPermission not implemented");
-  }
-
-  @Override
-  public UserSession checkComponentPermission(String projectPermission, ComponentDto component) {
-    throw new UnsupportedOperationException("checkComponentPermission not implemented");
-  }
-
-  @Override
-  public UserSession checkComponentUuidPermission(String permission, String componentUuid) {
-    throw new UnsupportedOperationException("checkComponentUuidPermission not implemented");
-  }
-
-  @Override
-  public boolean isSystemAdministrator() {
-    throw new UnsupportedOperationException("isSystemAdministrator not implemented");
-  }
-
-  @Override
-  public UserSession checkIsSystemAdministrator() {
-    throw new UnsupportedOperationException("checkIsSystemAdministrator not implemented");
-  }
-
-  @Override
-  public UserSession checkMembership(OrganizationDto organization) {
-    throw new UnsupportedOperationException("checkMembership not implemented");
-  }
-}
index 00638cd8e3b009c543f8774f8ba863e976d54c35..6a3e75e6b258f8f4a141d3c9bd055df8fc1abcc8 100644 (file)
@@ -22,32 +22,16 @@ package org.sonar.server.view.index;
 import com.google.common.collect.Maps;
 import java.util.List;
 import java.util.Map;
-import org.elasticsearch.action.search.SearchResponse;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.DisableOnDebug;
 import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
-import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.index.IssueIndex;
-import org.sonar.server.issue.index.IssueIndexer;
-import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexer;
-import org.sonar.server.user.LightUserSessionRule;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Arrays.asList;
@@ -66,13 +50,9 @@ public class ViewIndexerTest {
   public DbTester db = DbTester.create();
   @Rule
   public EsTester es = EsTester.create();
-  @Rule
-  public LightUserSessionRule userSessionRule = new LightUserSessionRule();
 
   private DbClient dbClient = db.getDbClient();
   private DbSession dbSession = db.getSession();
-  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
-  private PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);
   private ViewIndexer underTest = new ViewIndexer(dbClient, es.client());
 
   @Test
@@ -180,46 +160,6 @@ public class ViewIndexerTest {
         tuple(applicationBranch1.uuid(), asList(project1Branch.uuid(), project2Branch.uuid())));
   }
 
-  @Test
-  public void clear_views_lookup_cache_on_index_view_uuid() {
-    IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
-    IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
-
-    String viewUuid = "ABCD";
-
-    RuleDto rule = RuleTesting.newXooX1();
-    dbClient.ruleDao().insert(dbSession, rule.getDefinition());
-    ComponentDto project1 = addProjectWithIssue(rule, db.organizations().insert());
-    issueIndexer.indexOnStartup(issueIndexer.getIndexTypes());
-    permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
-
-    OrganizationDto organizationDto = db.organizations().insert();
-    ComponentDto view = ComponentTesting.newView(organizationDto, "ABCD");
-    ComponentDto techProject1 = newProjectCopy("CDEF", project1, view);
-    dbClient.componentDao().insert(dbSession, view, techProject1);
-    dbSession.commit();
-
-    // First view indexation
-    underTest.index(viewUuid);
-
-    // Execute issue query on view -> 1 issue on view
-    SearchResponse issueResponse = issueIndex.search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), new SearchOptions());
-    assertThat(issueResponse.getHits().getHits()).hasSize(1);
-
-    // Add a project to the view and index it again
-    ComponentDto project2 = addProjectWithIssue(rule, organizationDto);
-    issueIndexer.indexOnStartup(issueIndexer.getIndexTypes());
-    permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
-
-    ComponentDto techProject2 = newProjectCopy("EFGH", project2, view);
-    dbClient.componentDao().insert(dbSession, techProject2);
-    dbSession.commit();
-    underTest.index(viewUuid);
-
-    // Execute issue query on view -> issue of project2 are well taken into account : the cache has been cleared
-    assertThat(issueIndex.search(IssueQuery.builder().viewUuids(newArrayList(viewUuid)).build(), new SearchOptions()).getHits()).hasSize(2);
-  }
-
   @Test
   public void delete_should_delete_the_view() {
     ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(asList("P1"));
@@ -238,16 +178,4 @@ public class ViewIndexerTest {
       .containsOnly(view3.uuid());
   }
 
-  private ComponentDto addProjectWithIssue(RuleDto rule, OrganizationDto org) {
-    ComponentDto project = ComponentTesting.newPublicProjectDto(org);
-    ComponentDto file = ComponentTesting.newFileDto(project, null);
-    db.components().insertComponents(project, file);
-
-    IssueDto issue = IssueTesting.newDto(rule, file, project);
-    dbClient.issueDao().insert(dbSession, issue);
-    dbSession.commit();
-
-    return project;
-  }
-
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java
new file mode 100644 (file)
index 0000000..b7a20f6
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.SearchHits;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregator.KeyedFilter;
+import org.elasticsearch.search.aggregations.bucket.filters.InternalFilters;
+import org.elasticsearch.search.aggregations.bucket.filters.InternalFilters.InternalBucket;
+import org.elasticsearch.search.aggregations.metrics.tophits.InternalTopHits;
+import org.elasticsearch.search.aggregations.metrics.tophits.TopHitsAggregationBuilder;
+import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
+import org.elasticsearch.search.sort.FieldSortBuilder;
+import org.elasticsearch.search.sort.ScoreSortBuilder;
+import org.elasticsearch.search.sort.SortOrder;
+import org.sonar.api.utils.System2;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory;
+import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory.ComponentTextSearchQuery;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_LANGUAGE;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_ORGANIZATION_UUID;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_QUALIFIER;
+import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_TYPE_COMPONENT;
+import static org.sonar.server.component.index.ComponentIndexDefinition.NAME_ANALYZERS;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+
+public class ComponentIndex {
+
+  private static final String FILTERS_AGGREGATION_NAME = "filters";
+  private static final String DOCS_AGGREGATION_NAME = "docs";
+
+  private final EsClient client;
+  private final WebAuthorizationTypeSupport authorizationTypeSupport;
+  private final System2 system2;
+
+  public ComponentIndex(EsClient client, WebAuthorizationTypeSupport authorizationTypeSupport, System2 system2) {
+    this.client = client;
+    this.authorizationTypeSupport = authorizationTypeSupport;
+    this.system2 = system2;
+  }
+
+  public SearchIdResult<String> search(ComponentQuery query, SearchOptions searchOptions) {
+    SearchRequestBuilder requestBuilder = client
+      .prepareSearch(INDEX_TYPE_COMPONENT)
+      .setFetchSource(false)
+      .setFrom(searchOptions.getOffset())
+      .setSize(searchOptions.getLimit());
+
+    BoolQueryBuilder esQuery = boolQuery();
+    esQuery.filter(authorizationTypeSupport.createQueryFilter());
+    setNullable(query.getQuery(), q -> {
+      ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQuery.builder()
+        .setQueryText(q)
+        .setFieldKey(FIELD_KEY)
+        .setFieldName(FIELD_NAME)
+        .build();
+      esQuery.must(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, ComponentTextSearchFeatureRepertoire.values()));
+    });
+    setEmptiable(query.getQualifiers(), q -> esQuery.must(termsQuery(FIELD_QUALIFIER, q)));
+    setNullable(query.getLanguage(), l -> esQuery.must(termQuery(FIELD_LANGUAGE, l)));
+    setNullable(query.getOrganizationUuid(), o -> esQuery.must(termQuery(FIELD_ORGANIZATION_UUID, o)));
+    requestBuilder.setQuery(esQuery);
+    requestBuilder.addSort(SORTABLE_ANALYZER.subField(FIELD_NAME), SortOrder.ASC);
+
+    return new SearchIdResult<>(requestBuilder.get(), id -> id, system2.getDefaultTimeZone());
+  }
+
+  public ComponentIndexResults searchSuggestions(SuggestionQuery query) {
+    return searchSuggestions(query, ComponentTextSearchFeatureRepertoire.values());
+  }
+
+  @VisibleForTesting
+  ComponentIndexResults searchSuggestions(SuggestionQuery query, ComponentTextSearchFeature... features) {
+    Collection<String> qualifiers = query.getQualifiers();
+    if (qualifiers.isEmpty()) {
+      return ComponentIndexResults.newBuilder().build();
+    }
+
+    SearchRequestBuilder request = client
+      .prepareSearch(INDEX_TYPE_COMPONENT)
+      .setQuery(createQuery(query, features))
+      .addAggregation(createAggregation(query))
+
+      // the search hits are part of the aggregations
+      .setSize(0);
+
+    SearchResponse response = request.get();
+
+    return aggregationsToQualifiers(response);
+  }
+
+  private static HighlightBuilder.Field createHighlighterField() {
+    HighlightBuilder.Field field = new HighlightBuilder.Field(FIELD_NAME);
+    field.highlighterType("fvh");
+    field.matchedFields(
+      Stream.concat(
+        Stream.of(FIELD_NAME),
+        Arrays
+          .stream(NAME_ANALYZERS)
+          .map(a -> a.subField(FIELD_NAME)))
+        .toArray(String[]::new));
+    return field;
+  }
+
+  private static FiltersAggregationBuilder createAggregation(SuggestionQuery query) {
+    return AggregationBuilders.filters(
+      FILTERS_AGGREGATION_NAME,
+      query.getQualifiers().stream().map(q -> new KeyedFilter(q, termQuery(FIELD_QUALIFIER, q))).toArray(KeyedFilter[]::new))
+      .subAggregation(createSubAggregation(query));
+  }
+
+  private static TopHitsAggregationBuilder createSubAggregation(SuggestionQuery query) {
+    return AggregationBuilders.topHits(DOCS_AGGREGATION_NAME)
+      .highlighter(new HighlightBuilder()
+        .encoder("html")
+        .preTags("<mark>")
+        .postTags("</mark>")
+        .field(createHighlighterField()))
+      .from(query.getSkip())
+      .size(query.getLimit())
+      .sort(new ScoreSortBuilder())
+      .sort(new FieldSortBuilder(ComponentIndexDefinition.FIELD_NAME))
+      .fetchSource(false);
+  }
+
+  private QueryBuilder createQuery(SuggestionQuery query, ComponentTextSearchFeature... features) {
+    BoolQueryBuilder esQuery = boolQuery();
+    esQuery.filter(authorizationTypeSupport.createQueryFilter());
+    ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQuery.builder()
+      .setQueryText(query.getQuery())
+      .setFieldKey(FIELD_KEY)
+      .setFieldName(FIELD_NAME)
+      .setRecentlyBrowsedKeys(query.getRecentlyBrowsedKeys())
+      .setFavoriteKeys(query.getFavoriteKeys())
+      .build();
+    return esQuery.must(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, features));
+  }
+
+  private static ComponentIndexResults aggregationsToQualifiers(SearchResponse response) {
+    InternalFilters filtersAgg = response.getAggregations().get(FILTERS_AGGREGATION_NAME);
+    List<InternalBucket> buckets = filtersAgg.getBuckets();
+    return ComponentIndexResults.newBuilder()
+      .setQualifiers(
+        buckets.stream().map(ComponentIndex::bucketToQualifier))
+      .build();
+  }
+
+  private static ComponentHitsPerQualifier bucketToQualifier(InternalBucket bucket) {
+    InternalTopHits docs = bucket.getAggregations().get(DOCS_AGGREGATION_NAME);
+
+    SearchHits hitList = docs.getHits();
+    SearchHit[] hits = hitList.getHits();
+
+    return new ComponentHitsPerQualifier(bucket.getKey(), ComponentHit.fromSearchHits(hits), hitList.getTotalHits());
+  }
+
+  private static <T> void setNullable(@Nullable T parameter, Consumer<T> consumer) {
+    if (parameter != null) {
+      consumer.accept(parameter);
+    }
+  }
+
+  private static <T> void setEmptiable(Collection<T> parameter, Consumer<Collection<T>> consumer) {
+    if (!parameter.isEmpty()) {
+      consumer.accept(parameter);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java
new file mode 100644 (file)
index 0000000..9154f3b
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Collections.emptyList;
+import static java.util.Objects.requireNonNull;
+
+public class ComponentIndexResults {
+
+  private final List<ComponentHitsPerQualifier> qualifiers;
+
+  private ComponentIndexResults(Builder builder) {
+    this.qualifiers = requireNonNull(builder.qualifiers);
+  }
+
+  public Stream<ComponentHitsPerQualifier> getQualifiers() {
+    return qualifiers.stream();
+  }
+
+  public boolean isEmpty() {
+    return qualifiers.isEmpty();
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private List<ComponentHitsPerQualifier> qualifiers = emptyList();
+
+    private Builder() {
+    }
+
+    public Builder setQualifiers(Stream<ComponentHitsPerQualifier> qualifiers) {
+      this.qualifiers = qualifiers.collect(Collectors.toList());
+      return this;
+    }
+
+    public ComponentIndexResults build() {
+      return new ComponentIndexResults(this);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentQuery.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentQuery.java
new file mode 100644 (file)
index 0000000..8093620
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Collection;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableCollection;
+
+public class ComponentQuery {
+  private final String organizationUuid;
+  private final String query;
+  private final Collection<String> qualifiers;
+  private final String language;
+
+  private ComponentQuery(Builder builder) {
+    this.organizationUuid = builder.organizationUuid;
+    this.query = builder.query;
+    this.qualifiers = builder.qualifiers;
+    this.language = builder.language;
+  }
+
+  @CheckForNull
+  public String getOrganizationUuid() {
+    return organizationUuid;
+  }
+
+  @CheckForNull
+  public String getQuery() {
+    return query;
+  }
+
+  public Collection<String> getQualifiers() {
+    return qualifiers;
+  }
+
+  @CheckForNull
+  public String getLanguage() {
+    return language;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String organizationUuid;
+    private String query;
+    private Collection<String> qualifiers = emptySet();
+    private String language;
+
+    private Builder() {
+      // enforce static factory method
+    }
+
+    public Builder setOrganization(@Nullable String organizationUuid) {
+      this.organizationUuid = organizationUuid;
+      return this;
+    }
+
+    public Builder setQuery(@Nullable String query) {
+      this.query = query;
+      return this;
+    }
+
+    public Builder setQualifiers(Collection<String> qualifiers) {
+      this.qualifiers = unmodifiableCollection(qualifiers);
+      return this;
+    }
+
+    public Builder setLanguage(@Nullable String language) {
+      this.language = language;
+      return this;
+    }
+
+    public ComponentQuery build() {
+      return new ComponentQuery(this);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/package-info.java
new file mode 100644 (file)
index 0000000..54f6c6e
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.component.index;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 21004ab49da183ec0a5d8bc63ed8a728b71576a9..2dbd6604799f867e5eff8d9f790b82f690d891d8 100644 (file)
@@ -27,6 +27,7 @@ import java.util.List;
 import javax.annotation.Nonnull;
 import org.sonar.api.rule.Severity;
 import org.sonar.db.issue.IssueDto;
+import org.sonar.server.issue.index.IssueQuery;
 
 /**
  * @since 3.6
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
new file mode 100644 (file)
index 0000000..f623f12
--- /dev/null
@@ -0,0 +1,918 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.indices.TermsLookup;
+import org.elasticsearch.search.aggregations.AggregationBuilder;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.HasAggregations;
+import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
+import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
+import org.elasticsearch.search.aggregations.bucket.histogram.ExtendedBounds;
+import org.elasticsearch.search.aggregations.bucket.terms.InternalTerms;
+import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
+import org.elasticsearch.search.aggregations.bucket.terms.Terms;
+import org.elasticsearch.search.aggregations.bucket.terms.Terms.Order;
+import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude;
+import org.elasticsearch.search.aggregations.metrics.max.InternalMax;
+import org.elasticsearch.search.aggregations.metrics.min.Min;
+import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
+import org.elasticsearch.search.aggregations.metrics.valuecount.InternalValueCount;
+import org.elasticsearch.search.sort.FieldSortBuilder;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Duration;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.es.BaseDoc;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsUtils;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.Sorting;
+import org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.issue.index.IssueQuery.PeriodStart;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.view.index.ViewIndexDefinition;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds;
+import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID;
+import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_ACTION_PLANS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_ASSIGNED_TO_ME;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHORS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILE_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
+
+/**
+ * The unique entry-point to interact with Elasticsearch index "issues".
+ * All the requests are listed here.
+ */
+public class IssueIndex {
+
+  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
+    PARAM_SEVERITIES,
+    PARAM_STATUSES,
+    PARAM_RESOLUTIONS,
+    DEPRECATED_PARAM_ACTION_PLANS,
+    PARAM_PROJECT_UUIDS,
+    PARAM_RULES,
+    PARAM_ASSIGNEES,
+    FACET_ASSIGNED_TO_ME,
+    PARAM_REPORTERS,
+    PARAM_AUTHORS,
+    PARAM_MODULE_UUIDS,
+    PARAM_FILE_UUIDS,
+    PARAM_DIRECTORIES,
+    PARAM_LANGUAGES,
+    PARAM_TAGS,
+    PARAM_TYPES,
+    PARAM_OWASP_TOP_10,
+    PARAM_SANS_TOP_25,
+    PARAM_CWE,
+    PARAM_CREATED_AT);
+  public static final String AGGREGATION_NAME_FOR_TAGS = "tags__issues";
+  private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*";
+  // TODO to be documented
+  // TODO move to Facets ?
+  private static final String FACET_SUFFIX_MISSING = "_missing";
+  private static final String IS_ASSIGNED_FILTER = "__isAssigned";
+  private static final SumAggregationBuilder EFFORT_AGGREGATION = AggregationBuilders.sum(FACET_MODE_EFFORT).field(IssueIndexDefinition.FIELD_ISSUE_EFFORT);
+  private static final Order EFFORT_AGGREGATION_ORDER = Order.aggregation(FACET_MODE_EFFORT, false);
+  private static final int DEFAULT_FACET_SIZE = 15;
+  private static final Duration TWENTY_DAYS = Duration.standardDays(20L);
+  private static final Duration TWENTY_WEEKS = Duration.standardDays(20L * 7L);
+  private static final Duration TWENTY_MONTHS = Duration.standardDays(20L * 30L);
+  private static final String COUNT = "count";
+  private final Sorting sorting;
+  private final EsClient client;
+  private final System2 system;
+  private final UserSession userSession;
+  private final WebAuthorizationTypeSupport authorizationTypeSupport;
+
+  public IssueIndex(EsClient client, System2 system, UserSession userSession, WebAuthorizationTypeSupport authorizationTypeSupport) {
+    this.client = client;
+    this.system = system;
+    this.userSession = userSession;
+    this.authorizationTypeSupport = authorizationTypeSupport;
+
+    this.sorting = new Sorting();
+    this.sorting.add(IssueQuery.SORT_BY_STATUS, IssueIndexDefinition.FIELD_ISSUE_STATUS);
+    this.sorting.add(IssueQuery.SORT_BY_SEVERITY, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE);
+    this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT);
+    this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT);
+    this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT);
+    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID);
+    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_FILE_PATH);
+    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_LINE);
+    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE).reverse();
+    this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, IssueIndexDefinition.FIELD_ISSUE_KEY);
+
+    // by default order by created date, project, file, line and issue key (in order to be deterministic when same ms)
+    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).reverse();
+    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID);
+    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_FILE_PATH);
+    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_LINE);
+    this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_KEY);
+  }
+
+  public SearchResponse search(IssueQuery query, SearchOptions options) {
+    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_TYPE_ISSUE);
+
+    configureSorting(query, requestBuilder);
+    configurePagination(options, requestBuilder);
+    configureRouting(query, options, requestBuilder);
+
+    QueryBuilder esQuery = matchAllQuery();
+    BoolQueryBuilder esFilter = boolQuery();
+    Map<String, QueryBuilder> filters = createFilters(query);
+    for (QueryBuilder filter : filters.values()) {
+      if (filter != null) {
+        esFilter.must(filter);
+      }
+    }
+    if (esFilter.hasClauses()) {
+      requestBuilder.setQuery(boolQuery().must(esQuery).filter(esFilter));
+    } else {
+      requestBuilder.setQuery(esQuery);
+    }
+
+    configureStickyFacets(query, options, filters, esQuery, requestBuilder);
+    requestBuilder.setFetchSource(false);
+    return requestBuilder.get();
+  }
+
+  /**
+   * Optimization - do not send ES request to all shards when scope is restricted
+   * to a set of projects. Because project UUID is used for routing, the request
+   * can be sent to only the shards containing the specified projects.
+   * Note that sticky facets may involve all projects, so this optimization must be
+   * disabled when facets are enabled.
+   */
+  private static void configureRouting(IssueQuery query, SearchOptions options, SearchRequestBuilder requestBuilder) {
+    Collection<String> uuids = query.projectUuids();
+    if (!uuids.isEmpty() && options.getFacets().isEmpty()) {
+      requestBuilder.setRouting(uuids.toArray(new String[uuids.size()]));
+    }
+  }
+
+  private static void configurePagination(SearchOptions options, SearchRequestBuilder esSearch) {
+    esSearch.setFrom(options.getOffset()).setSize(options.getLimit());
+  }
+
+  private Map<String, QueryBuilder> createFilters(IssueQuery query) {
+    Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__authorization", createAuthorizationFilter(query.checkAuthorization()));
+
+    // Issue is assigned Filter
+    if (BooleanUtils.isTrue(query.assigned())) {
+      filters.put(IS_ASSIGNED_FILTER, existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID));
+    } else if (BooleanUtils.isFalse(query.assigned())) {
+      filters.put(IS_ASSIGNED_FILTER, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID)));
+    }
+
+    // Issue is Resolved Filter
+    String isResolved = "__isResolved";
+    if (BooleanUtils.isTrue(query.resolved())) {
+      filters.put(isResolved, existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION));
+    } else if (BooleanUtils.isFalse(query.resolved())) {
+      filters.put(isResolved, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)));
+    }
+
+    // Field Filters
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_TYPE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_ID, createTermsFilter(
+      IssueIndexDefinition.FIELD_ISSUE_RULE_ID,
+      query.rules().stream().map(RuleDefinitionDto::getId).collect(Collectors.toList())));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, query.sansTop25()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_CWE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_CWE, query.cwe()));
+
+    addComponentRelatedFilters(query, filters);
+
+    addDatesFilter(filters, query);
+    addCreatedAfterByProjectsFilter(filters, query);
+    return filters;
+  }
+
+  private static void addComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    addCommonComponentRelatedFilters(query, filters);
+    if (query.viewUuids().isEmpty()) {
+      addBranchComponentRelatedFilters(query, filters);
+    } else {
+      addViewRelatedFilters(query, filters);
+    }
+  }
+
+  private static void addCommonComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    QueryBuilder componentFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.componentUuids());
+    QueryBuilder projectFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids());
+    QueryBuilder moduleRootFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, query.moduleRootUuids());
+    QueryBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids());
+    QueryBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories());
+    QueryBuilder fileFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids());
+
+    if (BooleanUtils.isTrue(query.onComponentOnly())) {
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentFilter);
+    } else {
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectFilter);
+      filters.put("__module", moduleRootFilter);
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleFilter);
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryFilter);
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, fileFilter != null ? fileFilter : componentFilter);
+    }
+  }
+
+  private static void addBranchComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    if (BooleanUtils.isTrue(query.onComponentOnly())) {
+      return;
+    }
+    QueryBuilder branchFilter = createTermFilter(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, query.branchUuid());
+    filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchFilter);
+  }
+
+  private static void addViewRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    if (BooleanUtils.isTrue(query.onComponentOnly())) {
+      return;
+    }
+    Collection<String> viewUuids = query.viewUuids();
+    String branchUuid = query.branchUuid();
+    boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
+    if (onApplicationBranch) {
+      filters.put("__view", createViewFilter(singletonList(query.branchUuid())));
+    } else {
+      filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(true)));
+      filters.put("__view", createViewFilter(viewUuids));
+    }
+  }
+
+  @CheckForNull
+  private static QueryBuilder createViewFilter(Collection<String> viewUuids) {
+    if (viewUuids.isEmpty()) {
+      return null;
+    }
+
+    BoolQueryBuilder viewsFilter = boolQuery();
+    for (String viewUuid : viewUuids) {
+      viewsFilter.should(QueryBuilders.termsLookupQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID,
+        new TermsLookup(
+          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
+          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
+          viewUuid,
+          ViewIndexDefinition.FIELD_PROJECTS)));
+    }
+    return viewsFilter;
+  }
+
+  private static StickyFacetBuilder newStickyFacetBuilder(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
+    if (hasQueryEffortFacet(query)) {
+      return new StickyFacetBuilder(esQuery, filters, EFFORT_AGGREGATION, EFFORT_AGGREGATION_ORDER);
+    }
+    return new StickyFacetBuilder(esQuery, filters);
+  }
+
+  private static void addSimpleStickyFacetIfNeeded(SearchOptions options, StickyFacetBuilder stickyFacetBuilder, SearchRequestBuilder esSearch,
+    String facetName, String fieldName, Object... selectedValues) {
+    if (options.getFacets().contains(facetName)) {
+      esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldName, facetName, DEFAULT_FACET_SIZE, selectedValues));
+    }
+  }
+
+  private static AggregationBuilder addEffortAggregationIfNeeded(IssueQuery query, AggregationBuilder aggregation) {
+    if (hasQueryEffortFacet(query)) {
+      aggregation.subAggregation(EFFORT_AGGREGATION);
+    }
+    return aggregation;
+  }
+
+  private static boolean hasQueryEffortFacet(IssueQuery query) {
+    return FACET_MODE_EFFORT.equals(query.facetMode()) || DEPRECATED_FACET_MODE_DEBT.equals(query.facetMode());
+  }
+
+  private static AggregationBuilder createAssigneesFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) {
+    String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
+    String facetName = PARAM_ASSIGNEES;
+
+    // Same as in super.stickyFacetBuilder
+    Map<String, QueryBuilder> assigneeFilters = Maps.newHashMap(filters);
+    assigneeFilters.remove(IS_ASSIGNED_FILTER);
+    assigneeFilters.remove(fieldName);
+    StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, assigneeFilters, queryBuilder);
+    BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName);
+    FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE);
+
+    Collection<String> assigneesEscaped = escapeValuesForFacetInclusion(query.assignees());
+    if (!assigneesEscaped.isEmpty()) {
+      facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t, assigneesEscaped.toArray());
+    }
+
+    // Add missing facet for unassigned issues
+    facetTopAggregation.subAggregation(
+      addEffortAggregationIfNeeded(query, AggregationBuilders
+        .missing(facetName + FACET_SUFFIX_MISSING)
+        .field(fieldName)));
+
+    return AggregationBuilders
+      .global(facetName)
+      .subAggregation(facetTopAggregation);
+  }
+
+  private static Collection<String> escapeValuesForFacetInclusion(@Nullable Collection<String> values) {
+    if (values == null) {
+      return Collections.emptyList();
+    }
+    return values.stream().map(Pattern::quote).collect(MoreCollectors.toArrayList(values.size()));
+  }
+
+  private static AggregationBuilder createResolutionFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
+    String fieldName = IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
+    String facetName = PARAM_RESOLUTIONS;
+
+    // Same as in super.stickyFacetBuilder
+    Map<String, QueryBuilder> resolutionFilters = Maps.newHashMap(filters);
+    resolutionFilters.remove("__isResolved");
+    resolutionFilters.remove(fieldName);
+    StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, resolutionFilters, esQuery);
+    BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName);
+    FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, DEFAULT_FACET_SIZE);
+    facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t);
+
+    // Add missing facet for unresolved issues
+    facetTopAggregation.subAggregation(
+      addEffortAggregationIfNeeded(query, AggregationBuilders
+        .missing(facetName + FACET_SUFFIX_MISSING)
+        .field(fieldName)));
+
+    return AggregationBuilders
+      .global(facetName)
+      .subAggregation(facetTopAggregation);
+  }
+
+  @CheckForNull
+  private static QueryBuilder createTermsFilter(String field, Collection<?> values) {
+    return values.isEmpty() ? null : termsQuery(field, values);
+  }
+
+  @CheckForNull
+  private static QueryBuilder createTermFilter(String field, @Nullable String value) {
+    return value == null ? null : termQuery(field, value);
+  }
+
+  private void configureSorting(IssueQuery query, SearchRequestBuilder esRequest) {
+    createSortBuilders(query).forEach(esRequest::addSort);
+  }
+
+  private List<FieldSortBuilder> createSortBuilders(IssueQuery query) {
+    String sortField = query.sort();
+    if (sortField != null) {
+      boolean asc = BooleanUtils.isTrue(query.asc());
+      return sorting.fill(sortField, asc);
+    }
+    return sorting.fillDefault();
+  }
+
+  private QueryBuilder createAuthorizationFilter(boolean checkAuthorization) {
+    if (checkAuthorization) {
+      return authorizationTypeSupport.createQueryFilter();
+    }
+    return matchAllQuery();
+  }
+
+  private void addDatesFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
+    PeriodStart createdAfter = query.createdAfter();
+    Date createdBefore = query.createdBefore();
+
+    validateCreationDateBounds(createdBefore, createdAfter != null ? createdAfter.date() : null);
+
+    if (createdAfter != null) {
+      filters.put("__createdAfter", QueryBuilders
+        .rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
+        .from(BaseDoc.dateToEpochSeconds(createdAfter.date()), createdAfter.inclusive()));
+    }
+    if (createdBefore != null) {
+      filters.put("__createdBefore", QueryBuilders
+        .rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
+        .lt(BaseDoc.dateToEpochSeconds(createdBefore)));
+    }
+    Date createdAt = query.createdAt();
+    if (createdAt != null) {
+      filters.put("__createdAt", termQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT, BaseDoc.dateToEpochSeconds(createdAt)));
+    }
+  }
+
+  private static void addCreatedAfterByProjectsFilter(Map<String, QueryBuilder> filters, IssueQuery query) {
+    Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
+    BoolQueryBuilder boolQueryBuilder = boolQuery();
+    createdAfterByProjectUuids.forEach((projectUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
+      .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
+      .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).from(BaseDoc.dateToEpochSeconds(createdAfterDate.date()), createdAfterDate.inclusive()))));
+    filters.put("createdAfterByProjectUuids", boolQueryBuilder);
+  }
+
+  private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
+    Preconditions.checkArgument(createdAfter == null || createdAfter.before(new Date(system.now())),
+      "Start bound cannot be in the future");
+    Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore),
+      "Start bound cannot be larger or equal to end bound");
+  }
+
+  private void configureStickyFacets(IssueQuery query, SearchOptions options, Map<String, QueryBuilder> filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) {
+    if (!options.getFacets().isEmpty()) {
+      StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, filters, esQuery);
+      // Execute Term aggregations
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_SEVERITIES, IssueIndexDefinition.FIELD_ISSUE_SEVERITY);
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_STATUSES, IssueIndexDefinition.FIELD_ISSUE_STATUS);
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_PROJECT_UUIDS, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids().toArray());
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_MODULE_UUIDS, IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids().toArray());
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_DIRECTORIES, IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories().toArray());
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_FILE_UUIDS, IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids().toArray());
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_LANGUAGES, IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages().toArray());
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_RULES, IssueIndexDefinition.FIELD_ISSUE_RULE_ID, query.rules().stream().map(RuleDefinitionDto::getId).toArray());
+
+      addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch,
+        PARAM_AUTHORS, IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors().toArray());
+
+      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_TAGS, IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags());
+      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_TYPES, IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types());
+      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_OWASP_TOP_10, IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10());
+      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_SANS_TOP_25, IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, query.sansTop25());
+      addStickyFacetIfNeeded(options, esSearch, stickyFacetBuilder, PARAM_CWE, IssueIndexDefinition.FIELD_ISSUE_CWE, query.cwe());
+      if (options.getFacets().contains(PARAM_RESOLUTIONS)) {
+        esSearch.addAggregation(createResolutionFacet(query, filters, esQuery));
+      }
+      if (options.getFacets().contains(PARAM_ASSIGNEES)) {
+        esSearch.addAggregation(createAssigneesFacet(query, filters, esQuery));
+      }
+      addAssignedToMeFacetIfNeeded(esSearch, options, query, filters, esQuery);
+      if (options.getFacets().contains(PARAM_CREATED_AT)) {
+        getCreatedAtFacet(query, filters, esQuery).ifPresent(esSearch::addAggregation);
+      }
+    }
+
+    if (hasQueryEffortFacet(query)) {
+      esSearch.addAggregation(EFFORT_AGGREGATION);
+    }
+  }
+
+  private static void addStickyFacetIfNeeded(SearchOptions options, SearchRequestBuilder esSearch, StickyFacetBuilder stickyFacetBuilder, String paramTags, String fieldIssueTags,
+    Collection<String> tags) {
+    if (options.getFacets().contains(paramTags)) {
+      esSearch.addAggregation(stickyFacetBuilder.buildStickyFacet(fieldIssueTags, paramTags, tags.toArray()));
+    }
+  }
+
+  private Optional<AggregationBuilder> getCreatedAtFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
+    long startTime;
+    boolean startInclusive;
+    PeriodStart createdAfter = query.createdAfter();
+    if (createdAfter == null) {
+      Optional<Long> minDate = getMinCreatedAt(filters, esQuery);
+      if (!minDate.isPresent()) {
+        return Optional.empty();
+      }
+      startTime = minDate.get();
+      startInclusive = true;
+    } else {
+      startTime = createdAfter.date().getTime();
+      startInclusive = createdAfter.inclusive();
+    }
+    Date createdBefore = query.createdBefore();
+    long endTime = createdBefore == null ? system.now() : createdBefore.getTime();
+
+    Duration timeSpan = new Duration(startTime, endTime);
+    DateHistogramInterval bucketSize = DateHistogramInterval.YEAR;
+    if (timeSpan.isShorterThan(TWENTY_DAYS)) {
+      bucketSize = DateHistogramInterval.DAY;
+    } else if (timeSpan.isShorterThan(TWENTY_WEEKS)) {
+      bucketSize = DateHistogramInterval.WEEK;
+    } else if (timeSpan.isShorterThan(TWENTY_MONTHS)) {
+      bucketSize = DateHistogramInterval.MONTH;
+    }
+
+    AggregationBuilder dateHistogram = AggregationBuilders.dateHistogram(PARAM_CREATED_AT)
+      .field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT)
+      .dateHistogramInterval(bucketSize)
+      .minDocCount(0L)
+      .format(DateUtils.DATETIME_FORMAT)
+      .timeZone(DateTimeZone.forOffsetMillis(system.getDefaultTimeZone().getRawOffset()))
+      // ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
+      .extendedBounds(new ExtendedBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
+    addEffortAggregationIfNeeded(query, dateHistogram);
+    return Optional.of(dateHistogram);
+  }
+
+  private Optional<Long> getMinCreatedAt(Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
+    String facetNameAndField = IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
+    SearchRequestBuilder esRequest = client
+      .prepareSearch(INDEX_TYPE_ISSUE)
+      .setSize(0);
+    BoolQueryBuilder esFilter = boolQuery();
+    filters.values().stream().filter(Objects::nonNull).forEach(esFilter::must);
+    if (esFilter.hasClauses()) {
+      esRequest.setQuery(QueryBuilders.boolQuery().must(esQuery).filter(esFilter));
+    } else {
+      esRequest.setQuery(esQuery);
+    }
+    esRequest.addAggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField));
+    Min minValue = esRequest.get().getAggregations().get(facetNameAndField);
+
+    Double actualValue = minValue.getValue();
+    if (actualValue.isInfinite()) {
+      return Optional.empty();
+    }
+    return Optional.of(actualValue.longValue());
+  }
+
+  private void addAssignedToMeFacetIfNeeded(SearchRequestBuilder builder, SearchOptions options, IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) {
+    String uuid = userSession.getUuid();
+
+    if (!options.getFacets().contains(FACET_ASSIGNED_TO_ME) || StringUtils.isEmpty(uuid)) {
+      return;
+    }
+
+    String fieldName = IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
+    String facetName = FACET_ASSIGNED_TO_ME;
+
+    // Same as in super.stickyFacetBuilder
+    StickyFacetBuilder assignedToMeFacetBuilder = newStickyFacetBuilder(query, filters, queryBuilder);
+    BoolQueryBuilder facetFilter = assignedToMeFacetBuilder.getStickyFacetFilter(IS_ASSIGNED_FILTER, fieldName);
+
+    FilterAggregationBuilder facetTopAggregation = AggregationBuilders
+      .filter(facetName + "__filter", facetFilter)
+      .subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders.terms(facetName + "__terms")
+        .field(fieldName)
+        .includeExclude(new IncludeExclude(escapeSpecialRegexChars(uuid), null))));
+
+    builder.addAggregation(
+      AggregationBuilders.global(facetName)
+        .subAggregation(facetTopAggregation));
+  }
+
+  public List<String> listTags(@Nullable OrganizationDto organization, @Nullable String textQuery, int size) {
+    int maxPageSize = 500;
+    checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize);
+    if (size <= 0) {
+      return emptyList();
+    }
+
+    BoolQueryBuilder esQuery = boolQuery()
+      .filter(createAuthorizationFilter(true));
+    if (organization != null) {
+      esQuery.filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid()));
+    }
+
+    SearchRequestBuilder requestBuilder = client
+      .prepareSearch(INDEX_TYPE_ISSUE)
+      .setQuery(esQuery)
+      .setSize(0);
+
+    TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME_FOR_TAGS)
+      .field(IssueIndexDefinition.FIELD_ISSUE_TAGS)
+      .size(size)
+      .order(Terms.Order.term(true))
+      .minDocCount(1L);
+    if (textQuery != null) {
+      String escapedTextQuery = escapeSpecialRegexChars(textQuery);
+      termsAggregation.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapedTextQuery), null));
+    }
+    requestBuilder.addAggregation(termsAggregation);
+
+    SearchResponse searchResponse = requestBuilder.get();
+    Terms issuesResult = searchResponse.getAggregations().get(AGGREGATION_NAME_FOR_TAGS);
+    return EsUtils.termsKeys(issuesResult);
+  }
+
+  public Map<String, Long> countTags(IssueQuery query, int maxNumberOfTags) {
+    Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_TAGS, query, null, Terms.Order.count(false), maxNumberOfTags);
+    return EsUtils.termsToMap(terms);
+  }
+
+  public List<String> listAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) {
+    Terms terms = listTermsMatching(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, Terms.Order.term(true), maxNumberOfAuthors);
+    return EsUtils.termsKeys(terms);
+  }
+
+  private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int maxNumberOfTags) {
+    SearchRequestBuilder requestBuilder = client
+      .prepareSearch(INDEX_TYPE_ISSUE)
+      // Avoids returning search hits
+      .setSize(0);
+
+    requestBuilder.setQuery(boolQuery().must(QueryBuilders.matchAllQuery()).filter(createBoolFilter(query)));
+
+    TermsAggregationBuilder aggreg = AggregationBuilders.terms("_ref")
+      .field(fieldName)
+      .size(maxNumberOfTags)
+      .order(termsOrder)
+      .minDocCount(1L);
+    if (textQuery != null) {
+      aggreg.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapeSpecialRegexChars(textQuery)), null));
+    }
+
+    SearchResponse searchResponse = requestBuilder.addAggregation(aggreg).get();
+    return searchResponse.getAggregations().get("_ref");
+  }
+
+  private BoolQueryBuilder createBoolFilter(IssueQuery query) {
+    BoolQueryBuilder boolQuery = boolQuery();
+    for (QueryBuilder filter : createFilters(query).values()) {
+      if (filter != null) {
+        boolQuery.must(filter);
+      }
+    }
+    return boolQuery;
+  }
+
+  public List<ProjectStatistics> searchProjectStatistics(List<String> projectUuids, List<Long> froms, @Nullable String assigneeUuid) {
+    checkState(projectUuids.size() == froms.size(),
+      "Expected same size for projectUuids (had size %s) and froms (had size %s)", projectUuids.size(), froms.size());
+    if (projectUuids.isEmpty()) {
+      return Collections.emptyList();
+    }
+    SearchRequestBuilder request = client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
+      .setQuery(
+        boolQuery()
+          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION))
+          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, assigneeUuid))
+          .mustNot(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name())))
+      .setSize(0);
+    IntStream.range(0, projectUuids.size()).forEach(i -> {
+      String projectUuid = projectUuids.get(i);
+      long from = froms.get(i);
+      request
+        .addAggregation(AggregationBuilders
+          .filter(projectUuid, boolQuery()
+            .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
+            .filter(rangeQuery(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT).gte(epochMillisToEpochSeconds(from))))
+          .subAggregation(
+            AggregationBuilders.terms("branchUuid").field(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID)
+              .subAggregation(
+                AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY))
+              .subAggregation(
+                AggregationBuilders.max("maxFuncCreatedAt").field(IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT))));
+    });
+    SearchResponse response = request.get();
+    return response.getAggregations().asList().stream()
+      .map(x -> (InternalFilter) x)
+      .flatMap(projectBucket -> ((StringTerms) projectBucket.getAggregations().get("branchUuid")).getBuckets().stream()
+        .flatMap(branchBucket -> {
+          long count = ((InternalValueCount) branchBucket.getAggregations().get(COUNT)).getValue();
+          if (count < 1L) {
+            return Stream.empty();
+          }
+          long lastIssueDate = (long) ((InternalMax) branchBucket.getAggregations().get("maxFuncCreatedAt")).getValue();
+          return Stream.of(new ProjectStatistics(branchBucket.getKeyAsString(), count, lastIssueDate));
+        }))
+      .collect(MoreCollectors.toList(projectUuids.size()));
+  }
+
+  public List<BranchStatistics> searchBranchStatistics(String projectUuid, List<String> branchUuids) {
+    if (branchUuids.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    SearchRequestBuilder request = client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
+      .setRouting(projectUuid)
+      .setQuery(
+        boolQuery()
+          .must(termsQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchUuids))
+          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION))
+          .must(termQuery(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(false))))
+      .setSize(0)
+      .addAggregation(AggregationBuilders.terms("branchUuids")
+        .field(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID)
+        .size(branchUuids.size())
+        .subAggregation(AggregationBuilders.terms("types")
+          .field(IssueIndexDefinition.FIELD_ISSUE_TYPE)));
+    SearchResponse response = request.get();
+    return ((StringTerms) response.getAggregations().get("branchUuids")).getBuckets().stream()
+      .map(bucket -> new BranchStatistics(bucket.getKeyAsString(),
+        ((StringTerms) bucket.getAggregations().get("types")).getBuckets()
+          .stream()
+          .collect(uniqueIndex(StringTerms.Bucket::getKeyAsString, InternalTerms.Bucket::getDocCount))))
+      .collect(MoreCollectors.toList(branchUuids.size()));
+  }
+
+  public List<SecurityStandardCategoryStatistics> getSansTop25Report(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
+    SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
+    Stream.of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES).forEach(sansCategory -> {
+      AggregationBuilder sansCategoryAggs = AggregationBuilders
+        .filter(sansCategory, boolQuery()
+          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, sansCategory)));
+      request.addAggregation(addSecurityReportSubAggregations(sansCategoryAggs, includeCwe));
+    });
+    return processSecurityReportSearchResults(request, includeCwe);
+  }
+
+  public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
+    SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
+    Stream.concat(IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i), Stream.of(UNKNOWN_STANDARD)).forEach(owaspCategory -> {
+      AggregationBuilder owaspCategoryAggs = AggregationBuilders
+        .filter(owaspCategory, boolQuery()
+          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, owaspCategory)));
+      request.addAggregation(addSecurityReportSubAggregations(owaspCategoryAggs, includeCwe));
+    });
+    return processSecurityReportSearchResults(request, includeCwe);
+  }
+
+  private static List<SecurityStandardCategoryStatistics> processSecurityReportSearchResults(SearchRequestBuilder request, boolean includeCwe) {
+    SearchResponse response = request.get();
+    return response.getAggregations().asList().stream()
+      .map(c -> processSecurityReportIssueSearchResults((InternalFilter) c, includeCwe))
+      .collect(MoreCollectors.toList());
+  }
+
+  private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(InternalFilter categoryBucket, boolean includeCwe) {
+    List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
+
+    if (includeCwe) {
+      ((StringTerms) categoryBucket.getAggregations().get("cwe")).getBuckets()
+        .forEach(cweBucket -> children.add(processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null)));
+    }
+
+    return processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getName(), children);
+  }
+
+  private static SecurityStandardCategoryStatistics processSecurityReportCategorySearchResults(HasAggregations categoryBucket, String categoryName,
+    @Nullable List<SecurityStandardCategoryStatistics> children) {
+    List<StringTerms.Bucket> severityBuckets = ((StringTerms) ((InternalFilter) categoryBucket.getAggregations().get("vulnerabilities")).getAggregations().get("severity"))
+      .getBuckets();
+    long vulnerabilities = severityBuckets.stream().mapToLong(b -> ((InternalValueCount) b.getAggregations().get(COUNT)).getValue()).sum();
+    // Worst severity having at least one issue
+    OptionalInt severityRating = severityBuckets.stream()
+      .filter(b -> ((InternalValueCount) b.getAggregations().get(COUNT)).getValue() != 0)
+      .mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
+      .max();
+
+    long openSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("openSecurityHotspots")).getAggregations().get(COUNT))
+      .getValue();
+    long toReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("toReviewSecurityHotspots")).getAggregations().get(COUNT))
+      .getValue();
+    long wontFixSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("wontFixSecurityHotspots")).getAggregations().get(COUNT))
+      .getValue();
+
+    return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots, openSecurityHotspots,
+      wontFixSecurityHotspots, children);
+  }
+
+  private static AggregationBuilder addSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe) {
+    AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
+    if (includeCwe) {
+      categoriesAggs
+        .subAggregation(addSecurityReportIssueCountAggregations(AggregationBuilders.terms("cwe").field(IssueIndexDefinition.FIELD_ISSUE_CWE)));
+    }
+    return aggregationBuilder;
+  }
+
+  private static AggregationBuilder addSecurityReportIssueCountAggregations(AggregationBuilder categoryAggs) {
+    return categoryAggs
+      .subAggregation(
+        AggregationBuilders.filter("vulnerabilities", boolQuery()
+          .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.VULNERABILITY.name()))
+          .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
+          .subAggregation(
+            AggregationBuilders.terms("severity").field(IssueIndexDefinition.FIELD_ISSUE_SEVERITY)
+              .subAggregation(
+                AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY))))
+      .subAggregation(AggregationBuilders.filter("openSecurityHotspots", boolQuery()
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+        .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
+        .subAggregation(
+          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
+      .subAggregation(AggregationBuilders.filter("toReviewSecurityHotspots", boolQuery()
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED)))
+        .subAggregation(
+          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
+      .subAggregation(AggregationBuilders.filter("wontFixSecurityHotspots", boolQuery()
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
+        .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_WONT_FIX)))
+        .subAggregation(
+          AggregationBuilders.count(COUNT).field(IssueIndexDefinition.FIELD_ISSUE_KEY)));
+  }
+
+  private SearchRequestBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid, boolean isViewOrApp) {
+    BoolQueryBuilder componentFilter = boolQuery();
+    if (isViewOrApp) {
+      componentFilter.filter(QueryBuilders.termsLookupQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID,
+        new TermsLookup(
+          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
+          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
+          projectUuid,
+          ViewIndexDefinition.FIELD_PROJECTS)));
+    } else {
+      componentFilter.filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, projectUuid));
+    }
+    return client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
+      .setQuery(
+        componentFilter
+          .filter(termsQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name(), RuleType.VULNERABILITY.name()))
+          .mustNot(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_CLOSED)))
+      .setSize(0);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQuery.java
new file mode 100644 (file)
index 0000000..dfb60a7
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.sonar.db.rule.RuleDefinitionDto;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
+
+/**
+ * @since 3.6
+ */
+public class IssueQuery {
+
+  public static final String SORT_BY_CREATION_DATE = "CREATION_DATE";
+  public static final String SORT_BY_UPDATE_DATE = "UPDATE_DATE";
+  public static final String SORT_BY_CLOSE_DATE = "CLOSE_DATE";
+  /**
+   * @deprecated since 7.2, it's no more possible to sort by assignee
+   */
+  @Deprecated
+  public static final String SORT_BY_ASSIGNEE = "ASSIGNEE";
+  public static final String SORT_BY_SEVERITY = "SEVERITY";
+  public static final String SORT_BY_STATUS = "STATUS";
+
+  /**
+   * Sort by project, file path then line id
+   */
+  public static final String SORT_BY_FILE_LINE = "FILE_LINE";
+
+  public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY,
+    SORT_BY_STATUS, SORT_BY_FILE_LINE);
+
+  private final Collection<String> issueKeys;
+  private final Collection<String> severities;
+  private final Collection<String> statuses;
+  private final Collection<String> resolutions;
+  private final Collection<String> components;
+  private final Collection<String> modules;
+  private final Collection<String> moduleRoots;
+  private final Collection<String> projects;
+  private final Collection<String> directories;
+  private final Collection<String> files;
+  private final Collection<String> views;
+  private final Collection<RuleDefinitionDto> rules;
+  private final Collection<String> assignees;
+  private final Collection<String> authors;
+  private final Collection<String> languages;
+  private final Collection<String> tags;
+  private final Collection<String> types;
+  private final Collection<String> owaspTop10;
+  private final Collection<String> sansTop25;
+  private final Collection<String> cwe;
+  private final Map<String, PeriodStart> createdAfterByProjectUuids;
+  private final Boolean onComponentOnly;
+  private final Boolean assigned;
+  private final Boolean resolved;
+  private final Date createdAt;
+  private final PeriodStart createdAfter;
+  private final Date createdBefore;
+  private final String sort;
+  private final Boolean asc;
+  private final String facetMode;
+  private final String organizationUuid;
+  private final String branchUuid;
+  private final boolean mainBranch;
+  private final boolean checkAuthorization;
+
+  private IssueQuery(Builder builder) {
+    this.issueKeys = defaultCollection(builder.issueKeys);
+    this.severities = defaultCollection(builder.severities);
+    this.statuses = defaultCollection(builder.statuses);
+    this.resolutions = defaultCollection(builder.resolutions);
+    this.components = defaultCollection(builder.components);
+    this.modules = defaultCollection(builder.modules);
+    this.moduleRoots = defaultCollection(builder.moduleRoots);
+    this.projects = defaultCollection(builder.projects);
+    this.directories = defaultCollection(builder.directories);
+    this.files = defaultCollection(builder.files);
+    this.views = defaultCollection(builder.views);
+    this.rules = defaultCollection(builder.rules);
+    this.assignees = defaultCollection(builder.assigneeUuids);
+    this.authors = defaultCollection(builder.authors);
+    this.languages = defaultCollection(builder.languages);
+    this.tags = defaultCollection(builder.tags);
+    this.types = defaultCollection(builder.types);
+    this.owaspTop10 = defaultCollection(builder.owaspTop10);
+    this.sansTop25 = defaultCollection(builder.sansTop25);
+    this.cwe = defaultCollection(builder.cwe);
+    this.createdAfterByProjectUuids = defaultMap(builder.createdAfterByProjectUuids);
+    this.onComponentOnly = builder.onComponentOnly;
+    this.assigned = builder.assigned;
+    this.resolved = builder.resolved;
+    this.createdAt = builder.createdAt;
+    this.createdAfter = builder.createdAfter;
+    this.createdBefore = builder.createdBefore;
+    this.sort = builder.sort;
+    this.asc = builder.asc;
+    this.checkAuthorization = builder.checkAuthorization;
+    this.facetMode = builder.facetMode;
+    this.organizationUuid = builder.organizationUuid;
+    this.branchUuid = builder.branchUuid;
+    this.mainBranch = builder.mainBranch;
+  }
+
+  public Collection<String> issueKeys() {
+    return issueKeys;
+  }
+
+  public Collection<String> severities() {
+    return severities;
+  }
+
+  public Collection<String> statuses() {
+    return statuses;
+  }
+
+  public Collection<String> resolutions() {
+    return resolutions;
+  }
+
+  public Collection<String> componentUuids() {
+    return components;
+  }
+
+  public Collection<String> moduleUuids() {
+    return modules;
+  }
+
+  public Collection<String> moduleRootUuids() {
+    return moduleRoots;
+  }
+
+  public Collection<String> projectUuids() {
+    return projects;
+  }
+
+  public Collection<String> directories() {
+    return directories;
+  }
+
+  public Collection<String> fileUuids() {
+    return files;
+  }
+
+  public Collection<String> viewUuids() {
+    return views;
+  }
+
+  public Collection<RuleDefinitionDto> rules() {
+    return rules;
+  }
+
+  public Collection<String> assignees() {
+    return assignees;
+  }
+
+  public Collection<String> authors() {
+    return authors;
+  }
+
+  public Collection<String> languages() {
+    return languages;
+  }
+
+  public Collection<String> tags() {
+    return tags;
+  }
+
+  public Collection<String> types() {
+    return types;
+  }
+
+  public Collection<String> owaspTop10() {
+    return owaspTop10;
+  }
+
+  public Collection<String> sansTop25() {
+    return sansTop25;
+  }
+
+  public Collection<String> cwe() {
+    return cwe;
+  }
+
+  public Map<String, PeriodStart> createdAfterByProjectUuids() {
+    return createdAfterByProjectUuids;
+  }
+
+  @CheckForNull
+  public Boolean onComponentOnly() {
+    return onComponentOnly;
+  }
+
+  @CheckForNull
+  public Boolean assigned() {
+    return assigned;
+  }
+
+  @CheckForNull
+  public Boolean resolved() {
+    return resolved;
+  }
+
+  @CheckForNull
+  public PeriodStart createdAfter() {
+    return createdAfter;
+  }
+
+  @CheckForNull
+  public Date createdAt() {
+    return createdAt == null ? null : new Date(createdAt.getTime());
+  }
+
+  @CheckForNull
+  public Date createdBefore() {
+    return createdBefore == null ? null : new Date(createdBefore.getTime());
+  }
+
+  @CheckForNull
+  public String sort() {
+    return sort;
+  }
+
+  @CheckForNull
+  public Boolean asc() {
+    return asc;
+  }
+
+  public boolean checkAuthorization() {
+    return checkAuthorization;
+  }
+
+  @CheckForNull
+  public String organizationUuid() {
+    return organizationUuid;
+  }
+
+  @CheckForNull
+  public String branchUuid() {
+    return branchUuid;
+  }
+
+  public boolean isMainBranch() {
+    return mainBranch;
+  }
+
+  public String facetMode() {
+    return facetMode;
+  }
+
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private Collection<String> issueKeys;
+    private Collection<String> severities;
+    private Collection<String> statuses;
+    private Collection<String> resolutions;
+    private Collection<String> components;
+    private Collection<String> modules;
+    private Collection<String> moduleRoots;
+    private Collection<String> projects;
+    private Collection<String> directories;
+    private Collection<String> files;
+    private Collection<String> views;
+    private Collection<RuleDefinitionDto> rules;
+    private Collection<String> assigneeUuids;
+    private Collection<String> authors;
+    private Collection<String> languages;
+    private Collection<String> tags;
+    private Collection<String> types;
+    private Collection<String> owaspTop10;
+    private Collection<String> sansTop25;
+    private Collection<String> cwe;
+    private Map<String, PeriodStart> createdAfterByProjectUuids;
+    private Boolean onComponentOnly = false;
+    private Boolean assigned = null;
+    private Boolean resolved = null;
+    private Date createdAt;
+    private PeriodStart createdAfter;
+    private Date createdBefore;
+    private String sort;
+    private Boolean asc = false;
+    private boolean checkAuthorization = true;
+    private String facetMode;
+    private String organizationUuid;
+    private String branchUuid;
+    private boolean mainBranch = true;
+
+    private Builder() {
+
+    }
+
+    public Builder issueKeys(@Nullable Collection<String> l) {
+      this.issueKeys = l;
+      return this;
+    }
+
+    public Builder severities(@Nullable Collection<String> l) {
+      this.severities = l;
+      return this;
+    }
+
+    public Builder statuses(@Nullable Collection<String> l) {
+      this.statuses = l;
+      return this;
+    }
+
+    public Builder resolutions(@Nullable Collection<String> l) {
+      this.resolutions = l;
+      return this;
+    }
+
+    public Builder componentUuids(@Nullable Collection<String> l) {
+      this.components = l;
+      return this;
+    }
+
+    public Builder moduleUuids(@Nullable Collection<String> l) {
+      this.modules = l;
+      return this;
+    }
+
+    public Builder moduleRootUuids(@Nullable Collection<String> l) {
+      this.moduleRoots = l;
+      return this;
+    }
+
+    public Builder projectUuids(@Nullable Collection<String> l) {
+      this.projects = l;
+      return this;
+    }
+
+    public Builder directories(@Nullable Collection<String> l) {
+      this.directories = l;
+      return this;
+    }
+
+    public Builder fileUuids(@Nullable Collection<String> l) {
+      this.files = l;
+      return this;
+    }
+
+    public Builder viewUuids(@Nullable Collection<String> l) {
+      this.views = l;
+      return this;
+    }
+
+    public Builder rules(@Nullable Collection<RuleDefinitionDto> rules) {
+      this.rules = rules;
+      return this;
+    }
+
+    public Builder assigneeUuids(@Nullable Collection<String> l) {
+      this.assigneeUuids = l;
+      return this;
+    }
+
+    public Builder authors(@Nullable Collection<String> l) {
+      this.authors = l;
+      return this;
+    }
+
+    public Builder languages(@Nullable Collection<String> l) {
+      this.languages = l;
+      return this;
+    }
+
+    public Builder tags(@Nullable Collection<String> t) {
+      this.tags = t;
+      return this;
+    }
+
+    public Builder types(@Nullable Collection<String> t) {
+      this.types = t;
+      return this;
+    }
+
+    public Builder owaspTop10(@Nullable Collection<String> o) {
+      this.owaspTop10 = o;
+      return this;
+    }
+
+    public Builder sansTop25(@Nullable Collection<String> s) {
+      this.sansTop25 = s;
+      return this;
+    }
+
+    public Builder cwe(@Nullable Collection<String> cwe) {
+      this.cwe = cwe;
+      return this;
+    }
+
+    public Builder createdAfterByProjectUuids(@Nullable Map<String, PeriodStart> createdAfterByProjectUuids) {
+      this.createdAfterByProjectUuids = createdAfterByProjectUuids;
+      return this;
+    }
+
+    /**
+     * If true, it will return only issues on the passed component(s)
+     * If false, it will return all issues on the passed component(s) and their descendants
+     */
+    public Builder onComponentOnly(@Nullable Boolean b) {
+      this.onComponentOnly = b;
+      return this;
+    }
+
+    /**
+     * If true, it will return all issues assigned to someone
+     * If false, it will return all issues not assigned to someone
+     */
+    public Builder assigned(@Nullable Boolean b) {
+      this.assigned = b;
+      return this;
+    }
+
+    /**
+     * If true, it will return all resolved issues
+     * If false, it will return all none resolved issues
+     */
+    public Builder resolved(@Nullable Boolean resolved) {
+      this.resolved = resolved;
+      return this;
+    }
+
+    public Builder createdAt(@Nullable Date d) {
+      this.createdAt = d == null ? null : new Date(d.getTime());
+      return this;
+    }
+
+    public Builder createdAfter(@Nullable Date d) {
+      this.createdAfter(d, true);
+      return this;
+    }
+
+    public Builder createdAfter(@Nullable Date d, boolean inclusive) {
+      this.createdAfter = d == null ? null : new PeriodStart(new Date(d.getTime()), inclusive);
+      return this;
+    }
+
+    public Builder createdBefore(@Nullable Date d) {
+      this.createdBefore = d == null ? null : new Date(d.getTime());
+      return this;
+    }
+
+    public Builder sort(@Nullable String s) {
+      if (s != null && !SORTS.contains(s)) {
+        throw new IllegalArgumentException("Bad sort field: " + s);
+      }
+      this.sort = s;
+      return this;
+    }
+
+    public Builder asc(@Nullable Boolean asc) {
+      this.asc = asc;
+      return this;
+    }
+
+    public IssueQuery build() {
+      if (issueKeys != null) {
+        checkArgument(issueKeys.size() <= MAX_LIMIT, "Number of issue keys must be less than " + MAX_LIMIT + " (got " + issueKeys.size() + ")");
+      }
+      return new IssueQuery(this);
+    }
+
+    public Builder checkAuthorization(boolean checkAuthorization) {
+      this.checkAuthorization = checkAuthorization;
+      return this;
+    }
+
+    public Builder facetMode(String facetMode) {
+      this.facetMode = facetMode;
+      return this;
+    }
+
+    public Builder organizationUuid(String s) {
+      this.organizationUuid = s;
+      return this;
+    }
+
+    public Builder branchUuid(@Nullable String s) {
+      this.branchUuid = s;
+      return this;
+    }
+
+    public Builder mainBranch(boolean mainBranch) {
+      this.mainBranch = mainBranch;
+      return this;
+    }
+  }
+
+  private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
+    return c == null ? Collections.emptyList() : Collections.unmodifiableCollection(c);
+  }
+
+  private static <K, V> Map<K, V> defaultMap(@Nullable Map<K, V> map) {
+    return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
+  }
+
+  public static class PeriodStart {
+    private final Date date;
+    private final boolean inclusive;
+
+    public PeriodStart(Date date, boolean inclusive) {
+      this.date = date;
+      this.inclusive = inclusive;
+
+    }
+
+    public Date date() {
+      return date;
+    }
+
+    public boolean inclusive() {
+      return inclusive;
+    }
+
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
new file mode 100644 (file)
index 0000000..5e89f78
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import java.time.Clock;
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.BooleanUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.issue.SearchRequest;
+import org.sonar.server.issue.index.IssueQuery.PeriodStart;
+import org.sonar.server.user.UserSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Collections2.transform;
+import static java.lang.String.format;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static org.sonar.api.utils.DateUtils.longToDate;
+import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
+import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
+
+/**
+ * This component is used to create an IssueQuery, in order to transform the component and component roots keys into uuid.
+ */
+@ServerSide
+public class IssueQueryFactory {
+
+  public static final String UNKNOWN = "<UNKNOWN>";
+  private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN);
+
+  private final DbClient dbClient;
+  private final Clock clock;
+  private final UserSession userSession;
+
+  public IssueQueryFactory(DbClient dbClient, Clock clock, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.clock = clock;
+    this.userSession = userSession;
+  }
+
+  public IssueQuery create(SearchRequest request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      IssueQuery.Builder builder = IssueQuery.builder()
+        .issueKeys(request.getIssues())
+        .severities(request.getSeverities())
+        .statuses(request.getStatuses())
+        .resolutions(request.getResolutions())
+        .resolved(request.getResolved())
+        .rules(ruleKeysToRuleId(dbSession, request.getRules()))
+        .assigneeUuids(request.getAssigneeUuids())
+        .languages(request.getLanguages())
+        .tags(request.getTags())
+        .types(request.getTypes())
+        .owaspTop10(request.getOwaspTop10())
+        .sansTop25(request.getSansTop25())
+        .cwe(request.getCwe())
+        .assigned(request.getAssigned())
+        .createdAt(parseDateOrDateTime(request.getCreatedAt()))
+        .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore()))
+        .facetMode(request.getFacetMode())
+        .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));
+
+      List<ComponentDto> allComponents = new ArrayList<>();
+      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
+      addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);
+
+      setCreatedAfterFromRequest(dbSession, builder, request, allComponents);
+      String sort = request.getSort();
+      if (!Strings.isNullOrEmpty(sort)) {
+        builder.sort(sort);
+        builder.asc(request.getAsc());
+      }
+      return builder.build();
+    }
+  }
+
+  private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
+    checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
+
+    Date actualCreatedAfter = createdAfter;
+    if (createdInLast != null) {
+      actualCreatedAfter = Date.from(
+        OffsetDateTime.now(clock)
+          .minus(Period.parse("P" + createdInLast.toUpperCase(Locale.ENGLISH)))
+          .toInstant());
+    }
+    builder.createdAfter(actualCreatedAfter, createdAfterInclusive);
+  }
+
+  @CheckForNull
+  private String convertOrganizationKeyToUuid(DbSession dbSession, @Nullable String organizationKey) {
+    if (organizationKey == null) {
+      return null;
+    }
+    Optional<OrganizationDto> organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
+    return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
+  }
+
+  private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids) {
+    Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
+    String createdInLast = request.getCreatedInLast();
+
+    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
+      setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
+    } else {
+      checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
+      checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
+      ComponentDto component = componentUuids.iterator().next();
+      Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
+      setCreatedAfterFromDates(builder, createdAfterFromSnapshot, createdInLast, false);
+    }
+  }
+
+  @CheckForNull
+  private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) {
+    Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
+    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
+  }
+
+  private boolean mergeDeprecatedComponentParameters(DbSession session, SearchRequest request, List<ComponentDto> allComponents) {
+    Boolean onComponentOnly = request.getOnComponentOnly();
+    Collection<String> components = request.getComponents();
+    Collection<String> componentUuids = request.getComponentUuids();
+    Collection<String> componentKeys = request.getComponentKeys();
+    Collection<String> componentRootUuids = request.getComponentRootUuids();
+    Collection<String> componentRoots = request.getComponentRoots();
+    String branch = request.getBranch();
+    String pullRequest = request.getPullRequest();
+
+    boolean effectiveOnComponentOnly = false;
+
+    checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
+      "At most one of the following parameters can be provided: %s, %s, %s, %s, %s",
+      PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS, PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_COMPONENT_UUIDS);
+
+    if (componentRootUuids != null) {
+      allComponents.addAll(getComponentsFromUuids(session, componentRootUuids));
+    } else if (componentRoots != null) {
+      allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch, pullRequest));
+    } else if (components != null) {
+      allComponents.addAll(getComponentsFromKeys(session, components, branch, pullRequest));
+      effectiveOnComponentOnly = true;
+    } else if (componentUuids != null) {
+      allComponents.addAll(getComponentsFromUuids(session, componentUuids));
+      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
+    } else if (componentKeys != null) {
+      allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch, pullRequest));
+      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
+    }
+
+    return effectiveOnComponentOnly;
+  }
+
+  private static boolean atMostOneNonNullElement(Object... objects) {
+    return Arrays.stream(objects)
+      .filter(Objects::nonNull)
+      .count() <= 1;
+  }
+
+  private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly, List<ComponentDto> components, SearchRequest request) {
+    builder.onComponentOnly(onComponentOnly);
+    if (onComponentOnly) {
+      builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
+      setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
+      return;
+    }
+
+    builder.authors(request.getAuthors());
+    List<String> projectUuids = request.getProjectUuids();
+    List<String> projectKeys = request.getProjectKeys();
+    checkArgument(projectUuids == null || projectKeys == null, "Parameters projects and projectUuids cannot be set simultaneously");
+    if (projectUuids != null) {
+      builder.projectUuids(projectUuids);
+    } else if (projectKeys != null) {
+      List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch(), request.getPullRequest());
+      builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList()));
+      setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest());
+    }
+    builder.moduleUuids(request.getModuleUuids());
+    builder.directories(request.getDirectories());
+    builder.fileUuids(request.getFileUuids());
+
+    addComponentsBasedOnQualifier(builder, session, components, request);
+  }
+
+  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, SearchRequest request) {
+    if (components.isEmpty()) {
+      return;
+    }
+    if (components.stream().map(ComponentDto::uuid).anyMatch(uuid -> uuid.equals(UNKNOWN))) {
+      builder.componentUuids(singleton(UNKNOWN));
+      return;
+    }
+
+    Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet());
+    checkArgument(qualifiers.size() == 1, "All components must have the same qualifier, found %s", String.join(",", qualifiers));
+
+    setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
+    String qualifier = qualifiers.iterator().next();
+    switch (qualifier) {
+      case Qualifiers.VIEW:
+      case Qualifiers.SUBVIEW:
+        addViewsOrSubViews(builder, components);
+        break;
+      case Qualifiers.APP:
+        addApplications(builder, dbSession, components, request);
+        break;
+      case Qualifiers.PROJECT:
+        builder.projectUuids(components.stream().map(IssueQueryFactory::toProjectUuid).collect(toList()));
+        break;
+      case Qualifiers.MODULE:
+        builder.moduleRootUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
+        break;
+      case Qualifiers.DIRECTORY:
+        addDirectories(builder, components);
+        break;
+      case Qualifiers.FILE:
+      case Qualifiers.UNIT_TEST_FILE:
+        builder.fileUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
+        break;
+      default:
+        throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(components));
+    }
+  }
+
+  private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<ComponentDto> viewOrSubViewUuids) {
+    List<String> filteredViewUuids = viewOrSubViewUuids.stream()
+      .filter(uuid -> userSession.hasComponentPermission(UserRole.USER, uuid))
+      .map(ComponentDto::uuid)
+      .collect(Collectors.toList());
+    if (filteredViewUuids.isEmpty()) {
+      filteredViewUuids.add(UNKNOWN);
+    }
+    builder.viewUuids(filteredViewUuids);
+  }
+
+  private void addApplications(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> applications, SearchRequest request) {
+    Set<String> authorizedApplicationUuids = applications.stream()
+      .filter(app -> userSession.hasComponentPermission(UserRole.USER, app))
+      .map(ComponentDto::uuid)
+      .collect(toSet());
+
+    builder.viewUuids(authorizedApplicationUuids.isEmpty() ? singleton(UNKNOWN) : authorizedApplicationUuids);
+    addCreatedAfterByProjects(builder, dbSession, request, authorizedApplicationUuids);
+  }
+
+  private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> applicationUuids) {
+    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
+      return;
+    }
+
+    Set<String> projectUuids = applicationUuids.stream()
+      .flatMap(app -> dbClient.componentDao().selectProjectsFromView(dbSession, app, app).stream())
+      .collect(toSet());
+
+    Map<String, PeriodStart> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
+      .stream()
+      .filter(s -> s.getPeriodDate() != null)
+      .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false)));
+    builder.createdAfterByProjectUuids(leakByProjects);
+  }
+
+  private static void addDirectories(IssueQuery.Builder builder, List<ComponentDto> directories) {
+    Collection<String> directoryModuleUuids = new HashSet<>();
+    Collection<String> directoryPaths = new HashSet<>();
+    for (ComponentDto directory : directories) {
+      directoryModuleUuids.add(directory.moduleUuid());
+      directoryPaths.add(directory.path());
+    }
+    builder.moduleUuids(directoryModuleUuids);
+    builder.directories(directoryPaths);
+  }
+
+  private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) {
+    List<ComponentDto> componentDtos;
+    if (branch != null) {
+      componentDtos = dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch);
+    } else if (pullRequest != null) {
+      componentDtos = dbClient.componentDao().selectByKeysAndPullRequest(dbSession, componentKeys, pullRequest);
+    } else {
+      componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys);
+    }
+    if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
+      return singletonList(UNKNOWN_COMPONENT);
+    }
+    return componentDtos;
+  }
+
+  private List<ComponentDto> getComponentsFromUuids(DbSession dbSession, Collection<String> componentUuids) {
+    List<ComponentDto> componentDtos = dbClient.componentDao().selectByUuids(dbSession, componentUuids);
+    if (!componentUuids.isEmpty() && componentDtos.isEmpty()) {
+      return singletonList(UNKNOWN_COMPONENT);
+    }
+    return componentDtos;
+  }
+
+  @CheckForNull
+  private Collection<RuleDefinitionDto> ruleKeysToRuleId(DbSession dbSession, @Nullable Collection<String> rules) {
+    if (rules != null) {
+      return dbClient.ruleDao().selectDefinitionByKeys(dbSession, transform(rules, RuleKey::parse));
+    }
+    return Collections.emptyList();
+  }
+
+  private static String toProjectUuid(ComponentDto componentDto) {
+    String mainBranchProjectUuid = componentDto.getMainBranchProjectUuid();
+    return mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid;
+  }
+
+  private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch, @Nullable String pullRequest) {
+    builder.branchUuid(branch == null && pullRequest == null ? null : component.projectUuid());
+    builder.mainBranch(UNKNOWN_COMPONENT.equals(component)
+      || (branch == null && pullRequest == null)
+      || (branch != null && !branch.equals(component.getBranch()))
+      || (pullRequest != null && !pullRequest.equals(component.getPullRequest())));
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/package-info.java
new file mode 100644 (file)
index 0000000..c4487cb
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.issue.index;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 27fb50a7a6e069b5e75f0a247f8e55af9f2eaf5d..0f22690ef2f774a1c6d621f34be0decd15713ab3 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.NewAction;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.issue.IssueQuery;
+import org.sonar.server.issue.index.IssueQuery;
 import org.sonar.server.issue.index.IssueIndex;
 
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_AUTHORS;
index a63cb6b72cf6b4ffe8ac4a64c050132c8b154567..eab940d4adfcc83530fa75ca468e021cbab52b80 100644 (file)
@@ -26,8 +26,8 @@ import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.NewAction;
 import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQuery;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.SearchRequest;
 
index 981f13099c6d49d350fa110aafaa83cd4842b250..19a817c45b520d7e2f85d6fd02ff56df3bfc5106 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.server.issue.ws;
 import org.sonar.core.platform.Module;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.IssueFinder;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.WebIssueStorage;
 import org.sonar.server.issue.IssueUpdater;
 import org.sonar.server.issue.TransitionService;
index a2cbf329ebcd3db007ee3beaba44f1ab1a70099c..0b03a534e5244a8f1eca9150f8ffbd1df0220ebf 100644 (file)
@@ -39,7 +39,6 @@ import org.elasticsearch.search.SearchHit;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
@@ -57,8 +56,8 @@ import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchOptions;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQuery;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.SearchRequest;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.user.UserSession;
@@ -76,12 +75,12 @@ import static java.util.stream.Collectors.toList;
 import static org.sonar.api.utils.Paging.forPageIndex;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
-import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
-import static org.sonar.server.issue.IssueQuery.SORT_BY_ASSIGNEE;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
-import static org.sonar.server.issue.IssueQueryFactory.UNKNOWN;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
+import static org.sonar.server.issue.index.IssueQueryFactory.UNKNOWN;
 import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
new file mode 100644 (file)
index 0000000..989956a
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.apache.lucene.search.join.ScoreMode;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
+import org.elasticsearch.search.aggregations.bucket.filter.Filter;
+import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregator.KeyedFilter;
+import org.elasticsearch.search.aggregations.bucket.nested.Nested;
+import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.terms.Terms;
+import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude;
+import org.elasticsearch.search.aggregations.metrics.sum.Sum;
+import org.elasticsearch.search.sort.FieldSortBuilder;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.es.DefaultIndexSettingsElement;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Collections.emptyList;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
+import static org.elasticsearch.search.aggregations.AggregationBuilders.sum;
+import static org.elasticsearch.search.sort.SortOrder.ASC;
+import static org.elasticsearch.search.sort.SortOrder.DESC;
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
+import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
+import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
+import static org.sonar.server.es.EsUtils.termsToMap;
+import static org.sonar.server.measure.index.ProjectMeasuresDoc.QUALITY_GATE_STATUS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NCLOC_LANGUAGE_DISTRIBUTION;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ORGANIZATION_UUID;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE_STATUS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_TAGS;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
+
+@ServerSide
+public class ProjectMeasuresIndex {
+
+  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
+    NCLOC_KEY,
+    NEW_LINES_KEY,
+    DUPLICATED_LINES_DENSITY_KEY,
+    NEW_DUPLICATED_LINES_DENSITY_KEY,
+    COVERAGE_KEY,
+    NEW_COVERAGE_KEY,
+    SQALE_RATING_KEY,
+    NEW_MAINTAINABILITY_RATING_KEY,
+    RELIABILITY_RATING_KEY,
+    NEW_RELIABILITY_RATING_KEY,
+    SECURITY_RATING_KEY,
+    NEW_SECURITY_RATING_KEY,
+    ALERT_STATUS_KEY,
+    FILTER_LANGUAGES,
+    FILTER_TAGS);
+
+  private static final Double[] LINES_THRESHOLDS = new Double[] {1_000d, 10_000d, 100_000d, 500_000d};
+  private static final Double[] COVERAGE_THRESHOLDS = new Double[] {30d, 50d, 70d, 80d};
+  private static final Double[] DUPLICATIONS_THRESHOLDS = new Double[] {3d, 5d, 10d, 20d};
+
+  private static final String FIELD_MEASURES_KEY = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
+  private static final String FIELD_MEASURES_VALUE = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
+  private static final String FIELD_DISTRIB_LANGUAGE = FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "." + ProjectMeasuresIndexDefinition.FIELD_DISTRIB_LANGUAGE;
+  private static final String FIELD_DISTRIB_NCLOC = FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "." + ProjectMeasuresIndexDefinition.FIELD_DISTRIB_NCLOC;
+
+  private static final Map<String, FacetSetter> FACET_FACTORIES = ImmutableMap.<String, FacetSetter>builder()
+    .put(NCLOC_KEY, (esSearch, query, facetBuilder) -> addRangeFacet(esSearch, NCLOC_KEY, facetBuilder, LINES_THRESHOLDS))
+    .put(NEW_LINES_KEY, (esSearch, query, facetBuilder) -> addRangeFacet(esSearch, NEW_LINES_KEY, facetBuilder, LINES_THRESHOLDS))
+    .put(DUPLICATED_LINES_DENSITY_KEY,
+      (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, DUPLICATED_LINES_DENSITY_KEY, facetBuilder, DUPLICATIONS_THRESHOLDS))
+    .put(NEW_DUPLICATED_LINES_DENSITY_KEY,
+      (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, NEW_DUPLICATED_LINES_DENSITY_KEY, facetBuilder, DUPLICATIONS_THRESHOLDS))
+    .put(COVERAGE_KEY, (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, COVERAGE_KEY, facetBuilder, COVERAGE_THRESHOLDS))
+    .put(NEW_COVERAGE_KEY, (esSearch, query, facetBuilder) -> addRangeFacetIncludingNoData(esSearch, NEW_COVERAGE_KEY, facetBuilder, COVERAGE_THRESHOLDS))
+    .put(SQALE_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, SQALE_RATING_KEY, facetBuilder))
+    .put(NEW_MAINTAINABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_MAINTAINABILITY_RATING_KEY, facetBuilder))
+    .put(RELIABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, RELIABILITY_RATING_KEY, facetBuilder))
+    .put(NEW_RELIABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_RELIABILITY_RATING_KEY, facetBuilder))
+    .put(SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, SECURITY_RATING_KEY, facetBuilder))
+    .put(NEW_SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_SECURITY_RATING_KEY, facetBuilder))
+    .put(ALERT_STATUS_KEY, (esSearch, query, facetBuilder) -> esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, facetBuilder, createQualityGateFacet())))
+    .put(FILTER_LANGUAGES, ProjectMeasuresIndex::addLanguagesFacet)
+    .put(FIELD_TAGS, ProjectMeasuresIndex::addTagsFacet)
+    .build();
+
+  private final EsClient client;
+  private final WebAuthorizationTypeSupport authorizationTypeSupport;
+  private final System2 system2;
+
+  public ProjectMeasuresIndex(EsClient client, WebAuthorizationTypeSupport authorizationTypeSupport, System2 system2) {
+    this.client = client;
+    this.authorizationTypeSupport = authorizationTypeSupport;
+    this.system2 = system2;
+  }
+
+  public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
+    SearchRequestBuilder requestBuilder = client
+      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .setFetchSource(false)
+      .setFrom(searchOptions.getOffset())
+      .setSize(searchOptions.getLimit());
+
+    BoolQueryBuilder esFilter = boolQuery();
+    Map<String, QueryBuilder> filters = createFilters(query);
+    filters.values().forEach(esFilter::must);
+    requestBuilder.setQuery(esFilter);
+
+    addFacets(requestBuilder, searchOptions, filters, query);
+    addSort(query, requestBuilder);
+    return new SearchIdResult<>(requestBuilder.get(), id -> id, system2.getDefaultTimeZone());
+  }
+
+  public ProjectMeasuresStatistics searchTelemetryStatistics() {
+    SearchRequestBuilder request = client
+      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .setFetchSource(false)
+      .setSize(0);
+
+    BoolQueryBuilder esFilter = boolQuery();
+    request.setQuery(esFilter);
+    request.addAggregation(AggregationBuilders.terms(FIELD_LANGUAGES)
+      .field(FIELD_LANGUAGES)
+      .size(MAX_PAGE_SIZE)
+      .minDocCount(1)
+      .order(Terms.Order.count(false)));
+    request.addAggregation(AggregationBuilders.nested(FIELD_NCLOC_LANGUAGE_DISTRIBUTION, FIELD_NCLOC_LANGUAGE_DISTRIBUTION)
+      .subAggregation(AggregationBuilders.terms(FIELD_NCLOC_LANGUAGE_DISTRIBUTION + "_terms")
+        .field(FIELD_DISTRIB_LANGUAGE)
+        .size(MAX_PAGE_SIZE)
+        .minDocCount(1)
+        .order(Terms.Order.count(false))
+        .subAggregation(sum(FIELD_DISTRIB_NCLOC).field(FIELD_DISTRIB_NCLOC))));
+
+    request.addAggregation(AggregationBuilders.nested(NCLOC_KEY, FIELD_MEASURES)
+      .subAggregation(AggregationBuilders.filter(NCLOC_KEY + "_filter", termQuery(FIELD_MEASURES_KEY, NCLOC_KEY))
+        .subAggregation(sum(NCLOC_KEY + "_filter_sum").field(FIELD_MEASURES_VALUE))));
+
+    ProjectMeasuresStatistics.Builder statistics = ProjectMeasuresStatistics.builder();
+
+    SearchResponse response = request.get();
+    statistics.setProjectCount(response.getHits().getTotalHits());
+    Stream.of(NCLOC_KEY)
+      .map(metric -> (Nested) response.getAggregations().get(metric))
+      .map(nested -> (Filter) nested.getAggregations().get(nested.getName() + "_filter"))
+      .map(filter -> (Sum) filter.getAggregations().get(filter.getName() + "_sum"))
+      .forEach(sum -> {
+        String metric = sum.getName().replace("_filter_sum", "");
+        long value = Math.round(sum.getValue());
+        statistics.setSum(metric, value);
+      });
+    statistics.setProjectCountByLanguage(termsToMap(response.getAggregations().get(FIELD_LANGUAGES)));
+    Function<Terms.Bucket, Long> bucketToNcloc = bucket -> Math.round(((Sum) bucket.getAggregations().get(FIELD_DISTRIB_NCLOC)).getValue());
+    Map<String, Long> nclocByLanguage = Stream.of((Nested) response.getAggregations().get(FIELD_NCLOC_LANGUAGE_DISTRIBUTION))
+      .map(nested -> (Terms) nested.getAggregations().get(nested.getName() + "_terms"))
+      .flatMap(terms -> terms.getBuckets().stream())
+      .collect(MoreCollectors.uniqueIndex(Bucket::getKeyAsString, bucketToNcloc));
+    statistics.setNclocByLanguage(nclocByLanguage);
+
+    return statistics.build();
+  }
+
+  private static void addSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder) {
+    String sort = query.getSort();
+    if (SORT_BY_NAME.equals(sort)) {
+      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), query.isAsc() ? ASC : DESC);
+    } else if (SORT_BY_LAST_ANALYSIS_DATE.equals(sort)) {
+      requestBuilder.addSort(FIELD_ANALYSED_AT, query.isAsc() ? ASC : DESC);
+    } else if (ALERT_STATUS_KEY.equals(sort)) {
+      requestBuilder.addSort(FIELD_QUALITY_GATE_STATUS, query.isAsc() ? ASC : DESC);
+      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
+    } else {
+      addMetricSort(query, requestBuilder, sort);
+      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
+    }
+    // last sort is by key in order to be deterministic when same value
+    requestBuilder.addSort(FIELD_KEY, ASC);
+  }
+
+  private static void addMetricSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder, String sort) {
+    requestBuilder.addSort(
+      new FieldSortBuilder(FIELD_MEASURES_VALUE)
+        .setNestedPath(FIELD_MEASURES)
+        .setNestedFilter(termQuery(FIELD_MEASURES_KEY, sort))
+        .order(query.isAsc() ? ASC : DESC));
+  }
+
+  private static void addRangeFacet(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder, Double... thresholds) {
+    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder, createRangeFacet(metricKey, thresholds)));
+  }
+
+  private static void addRangeFacetIncludingNoData(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder, Double... thresholds) {
+    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder,
+      AggregationBuilders.filter("combined_" + metricKey, matchAllQuery())
+        .subAggregation(createRangeFacet(metricKey, thresholds))
+        .subAggregation(createNoDataFacet(metricKey))));
+  }
+
+  private static void addRatingFacet(SearchRequestBuilder esSearch, String metricKey, StickyFacetBuilder facetBuilder) {
+    esSearch.addAggregation(createStickyFacet(metricKey, facetBuilder, createRatingFacet(metricKey)));
+  }
+
+  private static void addLanguagesFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder) {
+    esSearch.addAggregation(facetBuilder.buildStickyFacet(FIELD_LANGUAGES, FILTER_LANGUAGES, query.getLanguages().map(Set::toArray).orElseGet(() -> new Object[] {})));
+  }
+
+  private static void addTagsFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder) {
+    esSearch.addAggregation(facetBuilder.buildStickyFacet(FIELD_TAGS, FILTER_TAGS, query.getTags().map(Set::toArray).orElseGet(() -> new Object[] {})));
+  }
+
+  private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options, Map<String, QueryBuilder> filters, ProjectMeasuresQuery query) {
+    StickyFacetBuilder facetBuilder = new StickyFacetBuilder(matchAllQuery(), filters);
+    options.getFacets().stream()
+      .filter(FACET_FACTORIES::containsKey)
+      .map(FACET_FACTORIES::get)
+      .forEach(factory -> factory.addFacet(esSearch, query, facetBuilder));
+  }
+
+  private static AbstractAggregationBuilder createStickyFacet(String facetKey, StickyFacetBuilder facetBuilder, AbstractAggregationBuilder aggregationBuilder) {
+    BoolQueryBuilder facetFilter = facetBuilder.getStickyFacetFilter(facetKey);
+    return AggregationBuilders
+      .global(facetKey)
+      .subAggregation(
+        AggregationBuilders
+          .filter("facet_filter_" + facetKey, facetFilter)
+          .subAggregation(aggregationBuilder));
+  }
+
+  private static AbstractAggregationBuilder createRangeFacet(String metricKey, Double... thresholds) {
+    RangeAggregationBuilder rangeAgg = AggregationBuilders.range(metricKey)
+      .field(FIELD_MEASURES_VALUE);
+    final int lastIndex = thresholds.length - 1;
+    IntStream.range(0, thresholds.length)
+      .forEach(i -> {
+        if (i == 0) {
+          rangeAgg.addUnboundedTo(thresholds[0]);
+          rangeAgg.addRange(thresholds[0], thresholds[1]);
+        } else if (i == lastIndex) {
+          rangeAgg.addUnboundedFrom(thresholds[lastIndex]);
+        } else {
+          rangeAgg.addRange(thresholds[i], thresholds[i + 1]);
+        }
+      });
+
+    return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES)
+      .subAggregation(
+        AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_KEY, metricKey))
+          .subAggregation(rangeAgg));
+  }
+
+  private static AbstractAggregationBuilder createNoDataFacet(String metricKey) {
+    return AggregationBuilders.filter(
+      "no_data_" + metricKey,
+      boolQuery().mustNot(nestedQuery(FIELD_MEASURES, termQuery(FIELD_MEASURES_KEY, metricKey), ScoreMode.Avg)));
+  }
+
+  private static AbstractAggregationBuilder createRatingFacet(String metricKey) {
+    return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES)
+      .subAggregation(
+        AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_KEY, metricKey))
+          .subAggregation(filters(metricKey,
+            new KeyedFilter("1", termQuery(FIELD_MEASURES_VALUE, 1d)),
+            new KeyedFilter("2", termQuery(FIELD_MEASURES_VALUE, 2d)),
+            new KeyedFilter("3", termQuery(FIELD_MEASURES_VALUE, 3d)),
+            new KeyedFilter("4", termQuery(FIELD_MEASURES_VALUE, 4d)),
+            new KeyedFilter("5", termQuery(FIELD_MEASURES_VALUE, 5d)))));
+  }
+
+  private static AbstractAggregationBuilder createQualityGateFacet() {
+    return AggregationBuilders.filters(
+      ALERT_STATUS_KEY,
+      QUALITY_GATE_STATUS.entrySet().stream()
+        .map(entry -> new KeyedFilter(entry.getKey(), termQuery(FIELD_QUALITY_GATE_STATUS, entry.getValue())))
+        .toArray(KeyedFilter[]::new));
+  }
+
+  private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
+    Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__authorization", authorizationTypeSupport.createQueryFilter());
+    Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
+    query.getMetricCriteria().forEach(metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
+    metricCriterionMultimap.asMap().forEach((key, value) -> {
+      BoolQueryBuilder metricFilters = boolQuery();
+      value
+        .stream()
+        .map(ProjectMeasuresIndex::toQuery)
+        .forEach(metricFilters::must);
+      filters.put(key, metricFilters);
+    });
+
+    query.getQualityGateStatus()
+      .ifPresent(qualityGateStatus -> filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE_STATUS, QUALITY_GATE_STATUS.get(qualityGateStatus.name()))));
+
+    query.getProjectUuids()
+      .ifPresent(projectUuids -> filters.put("ids", termsQuery("_id", projectUuids)));
+
+    query.getLanguages()
+      .ifPresent(languages -> filters.put(FILTER_LANGUAGES, termsQuery(FIELD_LANGUAGES, languages)));
+
+    query.getOrganizationUuid()
+      .ifPresent(organizationUuid -> filters.put(FIELD_ORGANIZATION_UUID, termQuery(FIELD_ORGANIZATION_UUID, organizationUuid)));
+
+    query.getTags()
+      .ifPresent(tags -> filters.put(FIELD_TAGS, termsQuery(FIELD_TAGS, tags)));
+
+    query.getQueryText()
+      .map(ProjectsTextSearchQueryFactory::createQuery)
+      .ifPresent(queryBuilder -> filters.put("textQuery", queryBuilder));
+    return filters;
+  }
+
+  private static QueryBuilder toQuery(MetricCriterion criterion) {
+    if (criterion.isNoData()) {
+      return boolQuery().mustNot(
+        nestedQuery(
+          FIELD_MEASURES,
+          termQuery(FIELD_MEASURES_KEY, criterion.getMetricKey()),
+          ScoreMode.Avg));
+    }
+    return nestedQuery(
+      FIELD_MEASURES,
+      boolQuery()
+        .filter(termQuery(FIELD_MEASURES_KEY, criterion.getMetricKey()))
+        .filter(toValueQuery(criterion)),
+      ScoreMode.Avg);
+  }
+
+  private static QueryBuilder toValueQuery(MetricCriterion criterion) {
+    String fieldName = FIELD_MEASURES_VALUE;
+
+    switch (criterion.getOperator()) {
+      case GT:
+        return rangeQuery(fieldName).gt(criterion.getValue());
+      case GTE:
+        return rangeQuery(fieldName).gte(criterion.getValue());
+      case LT:
+        return rangeQuery(fieldName).lt(criterion.getValue());
+      case LTE:
+        return rangeQuery(fieldName).lte(criterion.getValue());
+      case EQ:
+        return termQuery(fieldName, criterion.getValue());
+      default:
+        throw new IllegalStateException("Metric criteria non supported: " + criterion.getOperator().name());
+    }
+  }
+
+  public List<String> searchTags(@Nullable String textQuery, int size) {
+    int maxPageSize = 500;
+    checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize);
+    if (size <= 0) {
+      return emptyList();
+    }
+
+    TermsAggregationBuilder tagFacet = AggregationBuilders.terms(FIELD_TAGS)
+      .field(FIELD_TAGS)
+      .size(size)
+      .minDocCount(1)
+      .order(Terms.Order.term(true));
+    if (textQuery != null) {
+      tagFacet.includeExclude(new IncludeExclude(".*" + escapeSpecialRegexChars(textQuery) + ".*", null));
+    }
+
+    SearchRequestBuilder searchQuery = client
+      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .setQuery(authorizationTypeSupport.createQueryFilter())
+      .setFetchSource(false)
+      .setSize(0)
+      .addAggregation(tagFacet);
+
+    Terms aggregation = searchQuery.get().getAggregations().get(FIELD_TAGS);
+
+    return aggregation.getBuckets().stream()
+      .map(Bucket::getKeyAsString)
+      .collect(MoreCollectors.toList());
+  }
+
+  @FunctionalInterface
+  private interface FacetSetter {
+    void addFacet(SearchRequestBuilder esSearch, ProjectMeasuresQuery query, StickyFacetBuilder facetBuilder);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
new file mode 100644 (file)
index 0000000..17494bc
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.api.measures.Metric;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static java.util.Objects.requireNonNull;
+
+public class ProjectMeasuresQuery {
+
+  public static final String SORT_BY_NAME = "name";
+  public static final String SORT_BY_LAST_ANALYSIS_DATE = "analysisDate";
+
+  private List<MetricCriterion> metricCriteria = new ArrayList<>();
+  private Metric.Level qualityGateStatus;
+  private String organizationUuid;
+  private Set<String> projectUuids;
+  private Set<String> languages;
+  private Set<String> tags;
+  private String sort = SORT_BY_NAME;
+  private boolean asc = true;
+  private String queryText;
+
+  public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
+    this.metricCriteria.add(metricCriterion);
+    return this;
+  }
+
+  public List<MetricCriterion> getMetricCriteria() {
+    return metricCriteria;
+  }
+
+  public ProjectMeasuresQuery setQualityGateStatus(Metric.Level qualityGateStatus) {
+    this.qualityGateStatus = requireNonNull(qualityGateStatus);
+    return this;
+  }
+
+  public Optional<Metric.Level> getQualityGateStatus() {
+    return Optional.ofNullable(qualityGateStatus);
+  }
+
+  public ProjectMeasuresQuery setOrganizationUuid(@Nullable String organizationUuid) {
+    this.organizationUuid = organizationUuid;
+    return this;
+  }
+
+  public Optional<String> getOrganizationUuid() {
+    return Optional.ofNullable(organizationUuid);
+  }
+
+  public ProjectMeasuresQuery setProjectUuids(@Nullable Set<String> projectUuids) {
+    this.projectUuids = projectUuids;
+    return this;
+  }
+
+  public Optional<Set<String>> getProjectUuids() {
+    return Optional.ofNullable(projectUuids);
+  }
+
+  public ProjectMeasuresQuery setLanguages(@Nullable Set<String> languages) {
+    this.languages = languages;
+    return this;
+  }
+
+  public Optional<Set<String>> getLanguages() {
+    return Optional.ofNullable(languages);
+  }
+
+  public ProjectMeasuresQuery setTags(@Nullable Set<String> tags) {
+    this.tags = tags;
+    return this;
+  }
+
+  public Optional<Set<String>> getTags() {
+    return Optional.ofNullable(tags);
+  }
+
+  public Optional<String> getQueryText() {
+    return Optional.ofNullable(queryText);
+  }
+
+  public ProjectMeasuresQuery setQueryText(@Nullable String queryText) {
+    this.queryText = queryText;
+    return this;
+  }
+
+  public String getSort() {
+    return sort;
+  }
+
+  public ProjectMeasuresQuery setSort(String sort) {
+    this.sort = requireNonNull(sort, "Sort cannot be null");
+    return this;
+  }
+
+  public boolean isAsc() {
+    return asc;
+  }
+
+  public ProjectMeasuresQuery setAsc(boolean asc) {
+    this.asc = asc;
+    return this;
+  }
+
+  public static class MetricCriterion {
+    private final String metricKey;
+    private final Operator operator;
+    @Nullable
+    private final Double value;
+
+    private MetricCriterion(String metricKey, @Nullable Operator operator, @Nullable Double value) {
+      this.metricKey = metricKey;
+      this.operator = operator;
+      this.value = value;
+    }
+
+    public String getMetricKey() {
+      return metricKey;
+    }
+
+    public Operator getOperator() {
+      checkDataAvailable();
+      return operator;
+    }
+
+    public double getValue() {
+      checkDataAvailable();
+      return value;
+    }
+
+    public boolean isNoData() {
+      return value == null;
+    }
+
+    public static MetricCriterion createNoData(String metricKey) {
+      return new MetricCriterion(requireNonNull(metricKey), null, null);
+    }
+
+    public static MetricCriterion create(String metricKey, Operator operator, double value) {
+      return new MetricCriterion(requireNonNull(metricKey), requireNonNull(operator), value);
+    }
+
+    private void checkDataAvailable() {
+      checkState(!isNoData(), "The criterion for metric %s has no data", metricKey);
+    }
+  }
+
+  public enum Operator {
+    LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("="), IN("in");
+
+    String value;
+
+    Operator(String value) {
+      this.value = value;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public static Operator getByValue(String value) {
+      return stream(Operator.values())
+        .filter(operator -> operator.getValue().equalsIgnoreCase(value))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException(format("Unknown operator '%s'", value)));
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java
new file mode 100644 (file)
index 0000000..bf6cc6c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import org.sonar.core.platform.Module;
+
+public class ProjectsEsModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      ProjectMeasuresIndexDefinition.class,
+      ProjectMeasuresIndex.class,
+      ProjectMeasuresIndexer.class);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java
new file mode 100644 (file)
index 0000000..7145679
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import org.apache.commons.lang.StringUtils;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.MatchQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.sonar.server.es.DefaultIndexSettings;
+
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
+
+/**
+ * This class is used in order to do some advanced full text search on projects key and name
+ */
+class ProjectsTextSearchQueryFactory {
+
+  private ProjectsTextSearchQueryFactory() {
+    // Only static methods
+  }
+
+  static QueryBuilder createQuery(String queryText) {
+    BoolQueryBuilder featureQuery = boolQuery();
+    Arrays.stream(ComponentTextSearchFeature.values())
+      .map(f -> f.getQuery(queryText))
+      .forEach(featureQuery::should);
+    return featureQuery;
+  }
+
+  private enum ComponentTextSearchFeature {
+
+    EXACT_IGNORE_CASE {
+      @Override
+      QueryBuilder getQuery(String queryText) {
+        return matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), queryText)
+          .boost(2.5f);
+      }
+    },
+    PREFIX {
+      @Override
+      QueryBuilder getQuery(String queryText) {
+        return prefixAndPartialQuery(queryText, FIELD_NAME, FIELD_NAME)
+          .boost(2f);
+      }
+    },
+    PREFIX_IGNORE_CASE {
+      @Override
+      QueryBuilder getQuery(String queryText) {
+        String lowerCaseQueryText = queryText.toLowerCase(Locale.ENGLISH);
+        return prefixAndPartialQuery(lowerCaseQueryText, SORTABLE_ANALYZER.subField(FIELD_NAME), FIELD_NAME)
+          .boost(3f);
+      }
+    },
+    PARTIAL {
+      @Override
+      QueryBuilder getQuery(String queryText) {
+        BoolQueryBuilder queryBuilder = boolQuery();
+        split(queryText)
+          .map(text -> partialTermQuery(text, FIELD_NAME))
+          .forEach(queryBuilder::must);
+        return queryBuilder
+          .boost(0.5f);
+      }
+    },
+    KEY {
+      @Override
+      QueryBuilder getQuery(String queryText) {
+        return matchQuery(SORTABLE_ANALYZER.subField(FIELD_KEY), queryText)
+          .boost(50f);
+      }
+    };
+
+    abstract QueryBuilder getQuery(String queryText);
+
+    protected Stream<String> split(String queryText) {
+      return Arrays.stream(
+        queryText.split(DefaultIndexSettings.SEARCH_TERM_TOKENIZER_PATTERN))
+        .filter(StringUtils::isNotEmpty);
+    }
+
+    protected BoolQueryBuilder prefixAndPartialQuery(String queryText, String fieldName, String originalFieldName) {
+      BoolQueryBuilder queryBuilder = boolQuery();
+      AtomicBoolean first = new AtomicBoolean(true);
+      split(queryText)
+        .map(queryTerm -> {
+          if (first.getAndSet(false)) {
+            return prefixQuery(fieldName, queryTerm);
+          }
+          return partialTermQuery(queryTerm, originalFieldName);
+        })
+        .forEach(queryBuilder::must);
+      return queryBuilder;
+    }
+
+    protected MatchQueryBuilder partialTermQuery(String queryTerm, String fieldName) {
+      // We will truncate the search to the maximum length of nGrams in the index.
+      // Otherwise the search would for sure not find any results.
+      String truncatedQuery = StringUtils.left(queryTerm, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH);
+      return matchQuery(SEARCH_GRAMS_ANALYZER.subField(fieldName), truncatedQuery);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java
new file mode 100644 (file)
index 0000000..ece6681
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.measure.index;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/measure/package-info.java
deleted file mode 100644 (file)
index 16ec248..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.measure;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
new file mode 100644 (file)
index 0000000..7226076
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.elasticsearch.action.index.IndexRequest;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.es.BulkIndexer;
+import org.sonar.server.es.BulkIndexer.Size;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.OneToOneResilientIndexingListener;
+import org.sonar.server.es.ProjectIndexer;
+
+import static java.util.Collections.emptyList;
+import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+/**
+ * Populates the types "authorization" of each index requiring project
+ * authorization.
+ */
+public class PermissionIndexer implements ProjectIndexer {
+
+  private final DbClient dbClient;
+  private final EsClient esClient;
+  private final Collection<AuthorizationScope> authorizationScopes;
+  private final Set<IndexType> indexTypes;
+
+  public PermissionIndexer(DbClient dbClient, EsClient esClient, NeedAuthorizationIndexer... needAuthorizationIndexers) {
+    this(dbClient, esClient, Arrays.stream(needAuthorizationIndexers)
+      .map(NeedAuthorizationIndexer::getAuthorizationScope)
+      .collect(MoreCollectors.toList(needAuthorizationIndexers.length)));
+  }
+
+  @VisibleForTesting
+  public PermissionIndexer(DbClient dbClient, EsClient esClient, Collection<AuthorizationScope> authorizationScopes) {
+    this.dbClient = dbClient;
+    this.esClient = esClient;
+    this.authorizationScopes = authorizationScopes;
+    this.indexTypes = authorizationScopes.stream()
+      .map(AuthorizationScope::getIndexType)
+      .collect(toSet(authorizationScopes.size()));
+  }
+
+  @Override
+  public Set<IndexType> getIndexTypes() {
+    return indexTypes;
+  }
+
+  @Override
+  public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
+    // TODO do not load everything in memory. Db rows should be scrolled.
+    List<IndexPermissions> authorizations = getAllAuthorizations();
+    Stream<AuthorizationScope> scopes = getScopes(uninitializedIndexTypes);
+    index(authorizations, scopes, Size.LARGE);
+  }
+
+  @VisibleForTesting
+  void index(List<IndexPermissions> authorizations) {
+    index(authorizations, authorizationScopes.stream(), Size.REGULAR);
+  }
+
+  @Override
+  public void indexOnAnalysis(String branchUuid) {
+    // nothing to do, permissions don't change during an analysis
+  }
+
+  @Override
+  public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
+    switch (cause) {
+      case MEASURE_CHANGE:
+      case PROJECT_KEY_UPDATE:
+      case PROJECT_TAGS_UPDATE:
+        // nothing to change. Measures, project key and tags are not part of this index
+        return emptyList();
+
+      case PROJECT_CREATION:
+      case PROJECT_DELETION:
+      case PERMISSION_CHANGE:
+        return insertIntoEsQueue(dbSession, projectUuids);
+
+      default:
+        // defensive case
+        throw new IllegalStateException("Unsupported cause: " + cause);
+    }
+  }
+
+  private Collection<EsQueueDto> insertIntoEsQueue(DbSession dbSession, Collection<String> projectUuids) {
+    List<EsQueueDto> items = indexTypes.stream()
+      .flatMap(indexType -> projectUuids.stream().map(projectUuid -> EsQueueDto.create(indexType.format(), projectUuid, null, projectUuid)))
+      .collect(toArrayList());
+
+    dbClient.esQueueDao().insert(dbSession, items);
+    return items;
+  }
+
+  private void index(Collection<IndexPermissions> authorizations, Stream<AuthorizationScope> scopes, Size bulkSize) {
+    if (authorizations.isEmpty()) {
+      return;
+    }
+
+    // index each authorization in each scope
+    scopes.forEach(scope -> {
+      IndexType indexType = scope.getIndexType();
+
+      BulkIndexer bulkIndexer = new BulkIndexer(esClient, indexType, bulkSize);
+      bulkIndexer.start();
+
+      authorizations.stream()
+        .filter(scope.getProjectPredicate())
+        .map(dto -> newIndexRequest(dto, indexType))
+        .forEach(bulkIndexer::add);
+
+      bulkIndexer.stop();
+    });
+  }
+
+  @Override
+  public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
+    IndexingResult result = new IndexingResult();
+
+    List<BulkIndexer> bulkIndexers = items.stream()
+      .map(EsQueueDto::getDocType)
+      .distinct()
+      .map(IndexType::parse)
+      .filter(indexTypes::contains)
+      .map(indexType -> new BulkIndexer(esClient, indexType, Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items)))
+      .collect(Collectors.toList());
+
+    if (bulkIndexers.isEmpty()) {
+      return result;
+    }
+
+    bulkIndexers.forEach(BulkIndexer::start);
+
+    PermissionIndexerDao permissionIndexerDao = new PermissionIndexerDao();
+    Set<String> remainingProjectUuids = items.stream().map(EsQueueDto::getDocId).collect(MoreCollectors.toHashSet());
+    permissionIndexerDao.selectByUuids(dbClient, dbSession, remainingProjectUuids).forEach(p -> {
+      remainingProjectUuids.remove(p.getProjectUuid());
+      bulkIndexers.forEach(bi -> bi.add(newIndexRequest(p, bi.getIndexType())));
+    });
+
+    // the remaining references on projects that don't exist in db. They must
+    // be deleted from index.
+    remainingProjectUuids.forEach(projectUuid -> bulkIndexers.forEach(bi -> bi.addDeletion(bi.getIndexType(), projectUuid, projectUuid)));
+
+    bulkIndexers.forEach(b -> result.add(b.stop()));
+
+    return result;
+  }
+
+  private static IndexRequest newIndexRequest(IndexPermissions dto, IndexType indexType) {
+    Map<String, Object> doc = new HashMap<>();
+    if (dto.isAllowAnyone()) {
+      doc.put(IndexAuthorizationConstants.FIELD_ALLOW_ANYONE, true);
+      // no need to feed users and groups
+    } else {
+      doc.put(IndexAuthorizationConstants.FIELD_ALLOW_ANYONE, false);
+      doc.put(IndexAuthorizationConstants.FIELD_GROUP_IDS, dto.getGroupIds());
+      doc.put(IndexAuthorizationConstants.FIELD_USER_IDS, dto.getUserIds());
+    }
+    return new IndexRequest(indexType.getIndex(), indexType.getType())
+      .id(dto.getProjectUuid())
+      .routing(dto.getProjectUuid())
+      .source(doc);
+  }
+
+  private Stream<AuthorizationScope> getScopes(Set<IndexType> indexTypes) {
+    return authorizationScopes.stream()
+      .filter(scope -> indexTypes.contains(scope.getIndexType()));
+  }
+
+  private List<IndexPermissions> getAllAuthorizations() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      return new PermissionIndexerDao().selectAll(dbClient, dbSession);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java
new file mode 100644 (file)
index 0000000..22af562
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import com.google.common.collect.ImmutableList;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+
+import static org.apache.commons.lang.StringUtils.repeat;
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+
+/**
+ * No streaming because of union of joins -> no need to use ResultSetIterator
+ */
+public class PermissionIndexerDao {
+
+  private enum RowKind {
+    USER, GROUP, ANYONE, NONE
+  }
+
+  private static final String SQL_TEMPLATE = "SELECT " +
+    "  project_authorization.kind as kind, " +
+    "  project_authorization.project as project, " +
+    "  project_authorization.user_id as user_id, " +
+    "  project_authorization.group_id as group_id, " +
+    "  project_authorization.qualifier as qualifier " +
+    "FROM ( " +
+
+    // users
+
+    "      SELECT '" + RowKind.USER + "' as kind," +
+    "      projects.uuid AS project, " +
+    "      projects.qualifier AS qualifier, " +
+    "      user_roles.user_id  AS user_id, " +
+    "      NULL  AS group_id " +
+    "      FROM projects " +
+    "      INNER JOIN user_roles ON user_roles.resource_id = projects.id AND user_roles.role = 'user' " +
+    "      WHERE " +
+    "        (projects.qualifier = 'TRK' " +
+    "         or  projects.qualifier = 'VW' " +
+    "         or  projects.qualifier = 'APP') " +
+    "        AND projects.copy_component_uuid is NULL " +
+    "        {projectsCondition} " +
+    "      UNION " +
+
+    // groups
+
+    "      SELECT '" + RowKind.GROUP + "' as kind," +
+    "      projects.uuid AS project, " +
+    "      projects.qualifier AS qualifier, " +
+    "      NULL  AS user_id, " +
+    "      groups.id  AS group_id " +
+    "      FROM projects " +
+    "      INNER JOIN group_roles ON group_roles.resource_id = projects.id AND group_roles.role = 'user' " +
+    "      INNER JOIN groups ON groups.id = group_roles.group_id " +
+    "      WHERE " +
+    "        (projects.qualifier = 'TRK' " +
+    "         or  projects.qualifier = 'VW' " +
+    "         or  projects.qualifier = 'APP') " +
+    "        AND projects.copy_component_uuid is NULL " +
+    "        {projectsCondition} " +
+    "        AND group_id IS NOT NULL " +
+    "      UNION " +
+
+    // public projects are accessible to any one
+
+    "      SELECT '" + RowKind.ANYONE + "' as kind," +
+    "      projects.uuid AS project, " +
+    "      projects.qualifier AS qualifier, " +
+    "      NULL         AS user_id, " +
+    "      NULL     AS group_id " +
+    "      FROM projects " +
+    "      WHERE " +
+    "        (projects.qualifier = 'TRK' " +
+    "         or  projects.qualifier = 'VW' " +
+    "         or  projects.qualifier = 'APP') " +
+    "        AND projects.copy_component_uuid is NULL " +
+    "        AND projects.private = ? " +
+    "        {projectsCondition} " +
+    "      UNION " +
+
+    // private project is returned when no authorization
+    "      SELECT '" + RowKind.NONE + "' as kind," +
+    "      projects.uuid AS project, " +
+    "      projects.qualifier AS qualifier, " +
+    "      NULL AS user_id, " +
+    "      NULL  AS group_id " +
+    "      FROM projects " +
+    "      WHERE " +
+    "        (projects.qualifier = 'TRK' " +
+    "         or  projects.qualifier = 'VW' " +
+    "         or  projects.qualifier = 'APP') " +
+    "        AND projects.copy_component_uuid is NULL " +
+    "        AND projects.private = ? " +
+    "        {projectsCondition} " +
+
+    "    ) project_authorization";
+
+  List<IndexPermissions> selectAll(DbClient dbClient, DbSession session) {
+    return doSelectByProjects(dbClient, session, Collections.emptyList());
+  }
+
+  List<IndexPermissions> selectByUuids(DbClient dbClient, DbSession session, Collection<String> projectOrViewUuids) {
+    return executeLargeInputs(projectOrViewUuids, subProjectOrViewUuids -> doSelectByProjects(dbClient, session, subProjectOrViewUuids));
+  }
+
+  private static List<IndexPermissions> doSelectByProjects(DbClient dbClient, DbSession session, List<String> projectUuids) {
+    try {
+      Map<String, IndexPermissions> dtosByProjectUuid = new HashMap<>();
+      try (PreparedStatement stmt = createStatement(dbClient, session, projectUuids);
+        ResultSet rs = stmt.executeQuery()) {
+        while (rs.next()) {
+          processRow(rs, dtosByProjectUuid);
+        }
+        return ImmutableList.copyOf(dtosByProjectUuid.values());
+      }
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to select authorizations", e);
+    }
+  }
+
+  private static PreparedStatement createStatement(DbClient dbClient, DbSession session, List<String> projectUuids) throws SQLException {
+    String sql;
+    if (projectUuids.isEmpty()) {
+      sql = StringUtils.replace(SQL_TEMPLATE, "{projectsCondition}", "");
+    } else {
+      sql = StringUtils.replace(SQL_TEMPLATE, "{projectsCondition}", " AND projects.uuid in (" + repeat("?", ", ", projectUuids.size()) + ")");
+    }
+    PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
+    int index = 1;
+    // query for RowKind.USER
+    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
+    // query for RowKind.GROUP
+    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
+    // query for RowKind.ANYONE
+    index = setPrivateProjectPlaceHolder(stmt, index, false);
+    index = populateProjectUuidPlaceholders(stmt, projectUuids, index);
+    // query for RowKind.NONE
+    index = setPrivateProjectPlaceHolder(stmt, index, true);
+    populateProjectUuidPlaceholders(stmt, projectUuids, index);
+    return stmt;
+  }
+
+  private static int populateProjectUuidPlaceholders(PreparedStatement stmt, List<String> projectUuids, int index) throws SQLException {
+    int newIndex = index;
+    for (String projectUuid : projectUuids) {
+      stmt.setString(newIndex, projectUuid);
+      newIndex++;
+    }
+    return newIndex;
+  }
+
+  private static int setPrivateProjectPlaceHolder(PreparedStatement stmt, int index, boolean isPrivate) throws SQLException {
+    int newIndex = index;
+    stmt.setBoolean(newIndex, isPrivate);
+    newIndex++;
+    return newIndex;
+  }
+
+  private static void processRow(ResultSet rs, Map<String, IndexPermissions> dtosByProjectUuid) throws SQLException {
+    RowKind rowKind = RowKind.valueOf(rs.getString(1));
+    String projectUuid = rs.getString(2);
+
+    IndexPermissions dto = dtosByProjectUuid.get(projectUuid);
+    if (dto == null) {
+      String qualifier = rs.getString(5);
+      dto = new IndexPermissions(projectUuid, qualifier);
+      dtosByProjectUuid.put(projectUuid, dto);
+    }
+    switch (rowKind) {
+      case NONE:
+        break;
+      case USER:
+        dto.addUserId(rs.getInt(3));
+        break;
+      case GROUP:
+        dto.addGroupId(rs.getInt(4));
+        break;
+      case ANYONE:
+        dto.allowAnyone();
+        break;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/WebAuthorizationTypeSupport.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/WebAuthorizationTypeSupport.java
new file mode 100644 (file)
index 0000000..3a330a9
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.Optional;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.join.query.JoinQueryBuilders;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.user.GroupDto;
+import org.sonar.server.user.UserSession;
+
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_ALLOW_ANYONE;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_GROUP_IDS;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.FIELD_USER_IDS;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
+
+@ServerSide
+public class WebAuthorizationTypeSupport {
+
+  private final UserSession userSession;
+
+  public WebAuthorizationTypeSupport(UserSession userSession) {
+    this.userSession = userSession;
+  }
+
+  /**
+   * Build a filter to restrict query to the documents on which
+   * user has read access.
+   */
+  public QueryBuilder createQueryFilter() {
+    if (userSession.isRoot()) {
+      return QueryBuilders.matchAllQuery();
+    }
+
+    Integer userId = userSession.getUserId();
+    BoolQueryBuilder filter = boolQuery();
+
+    // anyone
+    filter.should(QueryBuilders.termQuery(FIELD_ALLOW_ANYONE, true));
+
+    // users
+    Optional.ofNullable(userId)
+      .map(Integer::longValue)
+      .ifPresent(id -> filter.should(termQuery(FIELD_USER_IDS, id)));
+
+    // groups
+    userSession.getGroups()
+      .stream()
+      .map(GroupDto::getId)
+      .forEach(groupId -> filter.should(termQuery(FIELD_GROUP_IDS, groupId)));
+
+    return JoinQueryBuilders.hasParentQuery(
+      TYPE_AUTHORIZATION,
+      QueryBuilders.boolQuery().filter(filter),
+      false);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/package-info.java
new file mode 100644 (file)
index 0000000..b09dbe2
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.permission.index;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 1b2276258e52aaa673052df002e553644f5e2ef6..1a33812cd88742e62fadf9157af0aaf0032d67d8 100644 (file)
@@ -48,6 +48,7 @@ import org.sonar.server.app.WebServerProcessLogging;
 import org.sonar.server.config.ConfigurationProvider;
 import org.sonar.server.es.EsModule;
 import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.platform.LogServerVersion;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.platform.ServerFileSystemImpl;
@@ -117,6 +118,7 @@ public class PlatformLevel1 extends PlatformLevel {
       DaoModule.class,
 
       // Elasticsearch
+      WebAuthorizationTypeSupport.class,
       EsModule.class,
 
       // rules/qprofiles
index 9f336ebc6a3508cd1e70ed7498e772bff9f7a95c..7dd42b598baef40aca638eada5939c9c6029dd33 100644 (file)
@@ -29,7 +29,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.ServerSide;
 import org.sonar.db.DbClient;
@@ -40,7 +39,6 @@ import org.sonar.db.qualityprofile.OrgActiveRuleDto;
 import org.sonar.db.qualityprofile.QProfileDto;
 
 @ServerSide
-@ComputeEngineSide
 public class QProfileComparison {
 
   private final DbClient dbClient;
index 280e9100227de1eeea98873f06f5703ddb883949..4875c20dbcafb73bba2d4dc8de595f5b7c51fde3 100644 (file)
@@ -38,7 +38,7 @@ import static java.lang.Integer.parseInt;
 import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.toList;
 import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
index 635499c7ee92156233fc4877a4b7fe4787a4a228..3fb5eec773331d6787aec6daf20f70f3dd2e0dbc 100644 (file)
@@ -23,7 +23,6 @@ import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 import java.util.Map;
 import java.util.function.Function;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.ServerSide;
 import org.sonar.core.platform.PlatformEditionProvider;
@@ -39,7 +38,6 @@ import org.sonar.server.telemetry.TelemetryData.Database;
 import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserQuery;
 
-@ComputeEngineSide
 @ServerSide
 public class TelemetryDataLoader {
   private final Server server;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/BaseUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/BaseUserSession.java
new file mode 100644 (file)
index 0000000..51c591c
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.user;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import org.sonar.core.permission.ProjectPermissions;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.OrganizationPermission;
+
+import static org.apache.commons.lang.StringUtils.defaultString;
+
+public abstract class BaseUserSession implements UserSession {
+  @Override
+  public final boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) {
+    return hasPermission(permission, organization.getUuid());
+  }
+
+  @Override
+  public final boolean hasPermission(OrganizationPermission permission, String organizationUuid) {
+    return isRoot() || hasPermissionImpl(permission, organizationUuid);
+  }
+
+  protected abstract boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid);
+
+  @Override
+  public final boolean hasComponentPermission(String permission, ComponentDto component) {
+    if (isRoot()) {
+      return true;
+    }
+    String projectUuid = defaultString(component.getMainBranchProjectUuid(), component.projectUuid());
+    return hasProjectUuidPermission(permission, projectUuid);
+  }
+
+  @Override
+  public final boolean hasComponentUuidPermission(String permission, String componentUuid) {
+    if (isRoot()) {
+      return true;
+    }
+    Optional<String> projectUuid = componentUuidToProjectUuid(componentUuid);
+    return projectUuid
+      .map(s -> hasProjectUuidPermission(permission, s))
+      .orElse(false);
+  }
+
+  protected abstract Optional<String> componentUuidToProjectUuid(String componentUuid);
+
+  protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid);
+
+  @Override
+  public final boolean hasMembership(OrganizationDto organization) {
+    return isRoot() || hasMembershipImpl(organization);
+  }
+
+  protected abstract boolean hasMembershipImpl(OrganizationDto organization);
+
+  @Override
+  public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
+    if (isRoot()) {
+      return new ArrayList<>(components);
+    }
+    return doKeepAuthorizedComponents(permission, components);
+  }
+
+  /**
+   * Naive implementation, to be overridden if needed
+   */
+  protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
+    boolean allowPublicComponent = ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission);
+    return components.stream()
+      .filter(c -> (allowPublicComponent && !c.isPrivate()) || hasComponentPermission(permission, c))
+      .collect(MoreCollectors.toList());
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
new file mode 100644 (file)
index 0000000..645254a
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.user;
+
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.OrganizationPermission;
+import org.sonar.db.user.GroupDto;
+
+public interface UserSession {
+
+  /**
+   * Login of the authenticated user. Returns {@code null}
+   * if {@link #isLoggedIn()} is {@code false}.
+   */
+  @CheckForNull
+  String getLogin();
+
+  /**
+   * Uuid of the authenticated user. Returns {@code null}
+   * if {@link #isLoggedIn()} is {@code false}.
+   */
+  @CheckForNull
+  String getUuid();
+
+  /**
+   * Name of the authenticated user. Returns {@code null}
+   * if {@link #isLoggedIn()} is {@code false}.
+   */
+  @CheckForNull
+  String getName();
+
+  /**
+   * Database ID of the authenticated user. Returns {@code null}
+   * if {@link #isLoggedIn()} is {@code false}.
+   */
+  @CheckForNull
+  Integer getUserId();
+
+  /**
+   * The groups that the logged-in user is member of. An empty
+   * collection is returned if {@link #isLoggedIn()} is {@code false}.
+   */
+  Collection<GroupDto> getGroups();
+
+  /**
+   * Whether the user is logged-in or anonymous.
+   */
+  boolean isLoggedIn();
+
+  /**
+   * Whether the user has root privileges. If {@code true}, then user automatically
+   * benefits from all the permissions on all organizations and projects.
+   */
+  boolean isRoot();
+
+  /**
+   * Ensures that {@link #isRoot()} returns {@code true} otherwise throws a
+   * {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkIsRoot();
+
+  /**
+   * Ensures that user is logged in otherwise throws {@link org.sonar.server.exceptions.UnauthorizedException}.
+   */
+  UserSession checkLoggedIn();
+
+  /**
+   * Returns {@code true} if the permission is granted on the organization, otherwise {@code false}.
+   *
+   * If the organization does not exist, then returns {@code false}.
+   *
+   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
+   * organization does not exist.
+   */
+  boolean hasPermission(OrganizationPermission permission, OrganizationDto organization);
+
+  boolean hasPermission(OrganizationPermission permission, String organizationUuid);
+
+  /**
+   * Ensures that {@link #hasPermission(OrganizationPermission, OrganizationDto)} is {@code true},
+   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization);
+
+  UserSession checkPermission(OrganizationPermission permission, String organizationUuid);
+
+  /**
+   * Returns {@code true} if the permission is granted to user on the component,
+   * otherwise {@code false}.
+   *
+   * If the component does not exist, then returns {@code false}.
+   *
+   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
+   * component does not exist.
+   *
+   * If the permission is not granted, then the organization permission is _not_ checked.
+   *
+   * @param component non-null component.
+   * @param permission project permission as defined by {@link org.sonar.core.permission.ProjectPermissions}
+   */
+  boolean hasComponentPermission(String permission, ComponentDto component);
+
+  /**
+   * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended
+   * because it does not have to load project if the referenced component
+   * is not a project.
+   *
+   * @deprecated use {@link #hasComponentPermission(String, ComponentDto)} instead
+   */
+  @Deprecated
+  boolean hasComponentUuidPermission(String permission, String componentUuid);
+
+  /**
+   * Return the subset of specified components which the user has granted permission.
+   * An empty list is returned if input is empty or if no components are allowed to be
+   * accessed.
+   * If the input is ordered, then the returned components are in the same order.
+   * The duplicated components are returned duplicated too.
+   */
+  List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components);
+
+  /**
+   * Ensures that {@link #hasComponentPermission(String, ComponentDto)} is {@code true},
+   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkComponentPermission(String projectPermission, ComponentDto component);
+
+  /**
+   * Ensures that {@link #hasComponentUuidPermission(String, String)} is {@code true},
+   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
+   *
+   * @deprecated use {@link #checkComponentPermission(String, ComponentDto)} instead
+   */
+  @Deprecated
+  UserSession checkComponentUuidPermission(String permission, String componentUuid);
+
+  /**
+   * Whether user can administrate system, for example for using cross-organizations services
+   * like update center, system info or management of users.
+   *
+   * Returns {@code true} if:
+   * <ul>
+   *   <li>{@link #isRoot()} is {@code true}</li>
+   *   <li>organization feature is disabled and user is administrator of the (single) default organization</li>
+   * </ul>
+   */
+  boolean isSystemAdministrator();
+
+  /**
+   * Ensures that {@link #isSystemAdministrator()} is {@code true},
+   * otherwise throws {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkIsSystemAdministrator();
+
+  /**
+   * Returns {@code true} if the user is member of the organization, otherwise {@code false}.
+   *
+   * If the organization does not exist, then returns {@code false}.
+   *
+   * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if
+   * organization does not exist.
+   */
+  boolean hasMembership(OrganizationDto organization);
+
+  /**
+   * Ensures that {@link #hasMembership(OrganizationDto)} is {@code true},
+   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkMembership(OrganizationDto organization);
+
+}
index 25aefe358fc8bbc4b124aad4306e88341bb397d3..25d2685a9b1bbb252c1feaacd97432277267b699 100644 (file)
@@ -45,8 +45,8 @@ import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.MediaTypes;
@@ -90,7 +90,7 @@ public class ListActionTest {
 
   private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
   private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer);
 
   public WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), userSession, new ComponentFinder(db.getDbClient(), resourceTypes), issueIndex));
index a865ac049cc8fdb2474556ef7c31c2207ae8288e..94f379c6700e788cdfe4c92179d676a3c2ab0d92 100644 (file)
@@ -40,8 +40,8 @@ import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Common.BranchType;
@@ -83,7 +83,7 @@ public class ListActionTest {
 
   private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
   private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer);
 
   private MetricDto qualityGateStatus;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java
new file mode 100644 (file)
index 0000000..e96e0e3
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.stream.IntStream;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.db.component.ComponentDto;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentIndexCombinationTest extends ComponentIndexTest {
+
+  @Test
+  public void return_empty_list_if_no_fields_match_query() {
+    indexProject("struts", "Apache Struts");
+
+    assertThat(index.searchSuggestions(SuggestionQuery.builder().setQuery("missing").build()).isEmpty()).isTrue();
+  }
+
+  @Test
+  public void should_not_return_components_that_do_not_match_at_all() {
+    indexProject("banana", "Banana Project 1");
+
+    assertNoSearchResults("Apple");
+  }
+
+  @Test
+  public void filter_results_by_qualifier() {
+    ComponentDto project = indexProject("struts", "Apache Struts");
+    indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
+
+    assertSearchResults(SuggestionQuery.builder().setQuery("struts").setQualifiers(singletonList(Qualifiers.PROJECT)).build(), project);
+  }
+
+  @Test
+  public void should_limit_the_number_of_results() {
+    IntStream.rangeClosed(0, 10).forEach(i -> indexProject("sonarqube" + i, "SonarQube" + i));
+
+    assertSearch(SuggestionQuery.builder().setQuery("sonarqube").setLimit(5).setQualifiers(singletonList(Qualifiers.PROJECT)).build()).hasSize(5);
+  }
+
+  @Test
+  public void should_not_support_wildcards() {
+    indexProject("theKey", "the name");
+
+    assertNoSearchResults("*t*");
+    assertNoSearchResults("th?Key");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureExactTest.java
new file mode 100644 (file)
index 0000000..7f781bf
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public class ComponentIndexFeatureExactTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(query -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.EXACT_IGNORE_CASE);
+  }
+
+  @Test
+  public void scoring_cares_about_exact_matches() {
+    ComponentDto project1 = indexProject("project1", "LongNameLongNameLongNameLongNameSonarQube");
+    ComponentDto project2 = indexProject("project2", "LongNameLongNameLongNameLongNameSonarQubeX");
+
+    SuggestionQuery query1 = SuggestionQuery.builder()
+      .setQuery("LongNameLongNameLongNameLongNameSonarQube")
+      .setQualifiers(Collections.singletonList(PROJECT))
+      .build();
+    assertSearch(query1).containsExactly(uuids(project1, project2));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java
new file mode 100644 (file)
index 0000000..7028889
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Collections.singletonList;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
+
+public class ComponentIndexFeatureFavoriteTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(q -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.FAVORITE);
+  }
+
+  @Test
+  public void scoring_cares_about_favorites() {
+    ComponentDto project1 = indexProject("sonarqube", "SonarQube");
+    ComponentDto project2 = indexProject("recent", "SonarQube Recently");
+
+    SuggestionQuery query1 = SuggestionQuery.builder()
+      .setQuery("SonarQube")
+      .setQualifiers(singletonList(PROJECT))
+      .setFavoriteKeys(of(project1.getDbKey()))
+      .build();
+    assertSearch(query1).containsExactly(uuids(project1, project2));
+
+    SuggestionQuery query2 = SuggestionQuery.builder()
+      .setQuery("SonarQube")
+      .setQualifiers(singletonList(PROJECT))
+      .setFavoriteKeys(of(project2.getDbKey()))
+      .build();
+    assertSearch(query2).containsExactly(uuids(project2, project1));
+  }
+
+  @Test
+  public void irrelevant_favorites_are_not_returned() {
+    features.set(q -> termQuery(FIELD_KEY, "non-existing-value"), ComponentTextSearchFeatureRepertoire.FAVORITE);
+    ComponentDto project1 = indexProject("foo", "foo");
+
+    SuggestionQuery query1 = SuggestionQuery.builder()
+      .setQuery("bar")
+      .setQualifiers(singletonList(PROJECT))
+      .setFavoriteKeys(of(project1.getDbKey()))
+      .build();
+    assertSearch(query1).isEmpty();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureKeyTest.java
new file mode 100644 (file)
index 0000000..79c1ba8
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+public class ComponentIndexFeatureKeyTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(ComponentTextSearchFeatureRepertoire.KEY);
+  }
+
+  @Test
+  public void should_search_projects_by_exact_case_insensitive_key() {
+    ComponentDto project1 = indexProject("keyOne", "Project One");
+    indexProject("keyTwo", "Project Two");
+
+    assertSearchResults("keyOne", project1);
+    assertSearchResults("keyone", project1);
+    assertSearchResults("KEYone", project1);
+  }
+
+  @Test
+  public void should_search_project_with_dot_in_key() {
+    ComponentDto project = indexProject("org.sonarqube", "SonarQube");
+
+    assertSearchResults("org.sonarqube", project);
+    assertNoSearchResults("orgsonarqube");
+  }
+
+  @Test
+  public void should_search_project_with_dash_in_key() {
+    ComponentDto project = indexProject("org-sonarqube", "SonarQube");
+
+    assertSearchResults("org-sonarqube", project);
+    assertNoSearchResults("orgsonarqube");
+  }
+
+  @Test
+  public void should_search_project_with_colon_in_key() {
+    ComponentDto project = indexProject("org:sonarqube", "Quality Product");
+
+    assertSearchResults("org:sonarqube", project);
+    assertNoSearchResults("orgsonarqube");
+    assertNoSearchResults("org-sonarqube");
+    assertNoSearchResults("org_sonarqube");
+  }
+
+  @Test
+  public void should_search_project_with_all_special_characters_in_key() {
+    ComponentDto project = indexProject("org.sonarqube:sonar-sérvèr_ç", "SonarQube");
+
+    assertSearchResults("org.sonarqube:sonar-sérvèr_ç", project);
+  }
+
+  @Test
+  public void should_not_return_results_when_searching_by_partial_key() {
+    indexProject("theKey", "some name");
+
+    assertNoSearchResults("theke");
+    assertNoSearchResults("hekey");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePartialTest.java
new file mode 100644 (file)
index 0000000..ee01483
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+public class ComponentIndexFeaturePartialTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
+  }
+
+  @Test
+  public void search_projects_by_exact_name() {
+    ComponentDto struts = indexProject("struts", "Apache Struts");
+    indexProject("sonarqube", "SonarQube");
+
+    assertSearchResults("Apache Struts", struts);
+    assertSearchResults("APACHE STRUTS", struts);
+    assertSearchResults("APACHE struTS", struts);
+  }
+
+  @Test
+  public void search_file_with_long_name() {
+    ComponentDto project = indexProject("struts", "Apache Struts");
+    ComponentDto file1 = indexFile(project, "src/main/java/DefaultRubyComponentServiceTestManagerFactory.java", "DefaultRubyComponentServiceTestManagerFactory.java");
+
+    assertSearchResults("DefaultRubyComponentServiceTestManagerFactory", file1);
+    assertSearchResults("DefaultRubyComponentServiceTestManagerFactory.java", file1);
+    assertSearchResults("RubyComponentServiceTestManager", file1);
+    assertSearchResults("te", file1);
+  }
+
+  @Test
+  public void should_search_by_name_with_two_characters() {
+    ComponentDto project = indexProject("struts", "Apache Struts");
+
+    assertSearchResults("st", project);
+    assertSearchResults("tr", project);
+  }
+
+  @Test
+  public void search_projects_by_partial_name() {
+    ComponentDto struts = indexProject("struts", "Apache Struts");
+
+    assertSearchResults("truts", struts);
+    assertSearchResults("pache", struts);
+    assertSearchResults("apach", struts);
+    assertSearchResults("che stru", struts);
+  }
+
+  @Test
+  public void search_projects_and_files_by_partial_name() {
+    ComponentDto project = indexProject("struts", "Apache Struts");
+    ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
+    indexFile(project, "src/main/java/Foo.java", "Foo.java");
+
+    assertSearchResults("struts", project, file1);
+    assertSearchResults("Struts", project, file1);
+    assertSearchResults("StrutsManager", file1);
+    assertSearchResults("STRUTSMAN", file1);
+    assertSearchResults("utsManag", file1);
+  }
+
+  @Test
+  public void should_find_file_by_file_extension() {
+    ComponentDto project = indexProject("struts", "Apache Struts");
+    ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java");
+    ComponentDto file2 = indexFile(project, "src/main/java/Foo.java", "Foo.java");
+
+    assertSearchResults(".java", file1, file2);
+    assertSearchResults("manager.java", file1);
+
+    // do not match
+    assertNoSearchResults("somethingStrutsManager.java");
+  }
+
+  @Test
+  public void should_search_for_word_and_suffix() {
+    assertFileMatches("plugin java", "AbstractPluginFactory.java");
+  }
+
+  @Test
+  public void should_search_for_word_and_suffix_in_any_order() {
+    assertFileMatches("java plugin", "AbstractPluginFactory.java");
+  }
+
+  @Test
+  public void should_search_for_two_words() {
+    assertFileMatches("abstract factory", "AbstractPluginFactory.java");
+  }
+
+  @Test
+  public void should_search_for_two_words_in_any_order() {
+    assertFileMatches("factory abstract", "AbstractPluginFactory.java");
+  }
+
+  @Test
+  public void should_require_at_least_one_matching_word() {
+    assertNoFileMatches("monitor object", "AbstractPluginFactory.java");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeaturePrefixTest.java
new file mode 100644 (file)
index 0000000..e247352
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+public class ComponentIndexFeaturePrefixTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(ComponentTextSearchFeatureRepertoire.PREFIX, ComponentTextSearchFeatureRepertoire.PREFIX_IGNORE_CASE);
+  }
+
+  @Test
+  public void should_find_prefix() {
+    assertResultOrder("comp", "component");
+  }
+
+  @Test
+  public void should_find_exact_match() {
+    assertResultOrder("component.js", "component.js");
+  }
+
+  @Test
+  public void should_not_find_partially() {
+    assertNoFileMatches("component.js", "my_component.js");
+  }
+
+  @Test
+  public void should_be_able_to_ignore_case() {
+    features.set(ComponentTextSearchFeatureRepertoire.PREFIX_IGNORE_CASE);
+    assertResultOrder("cOmPoNeNt.Js", "CoMpOnEnT.jS");
+  }
+
+  @Test
+  public void should_prefer_matching_case() {
+    assertResultOrder("cOmPoNeNt.Js", "cOmPoNeNt.Js", "CoMpOnEnT.jS");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java
new file mode 100644 (file)
index 0000000..54fa659
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public class ComponentIndexFeatureRecentlyBrowsedTest extends ComponentIndexTest {
+
+  @Before
+  public void before() {
+    features.set(query -> matchAllQuery(), ComponentTextSearchFeatureRepertoire.RECENTLY_BROWSED);
+  }
+
+  @Test
+  public void scoring_cares_about_recently_browsed() {
+    ComponentDto project1 = indexProject("sonarqube", "SonarQube");
+    ComponentDto project2 = indexProject("recent", "SonarQube Recently");
+
+    SuggestionQuery query1 = SuggestionQuery.builder()
+      .setQuery("SonarQube")
+      .setQualifiers(Collections.singletonList(PROJECT))
+      .setRecentlyBrowsedKeys(of(project1.getDbKey()))
+      .build();
+    assertSearch(query1).containsExactly(uuids(project1, project2));
+
+    SuggestionQuery query2 = SuggestionQuery.builder()
+      .setQuery("SonarQube")
+      .setQualifiers(Collections.singletonList(PROJECT))
+      .setRecentlyBrowsedKeys(of(project2.getDbKey()))
+      .build();
+    assertSearch(query2).containsExactly(uuids(project2, project1));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
new file mode 100644 (file)
index 0000000..d0ff940
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentIndexHighlightTest extends ComponentIndexTest {
+
+  @Test
+  public void should_escape_html() {
+    assertHighlighting("quick< brown fox", "brown", "quick&lt; <mark>brown</mark> fox");
+  }
+
+  @Test
+  public void should_highlight_partial_name() {
+    assertHighlighting("quickbrownfox", "brown", "quick<mark>brown</mark>fox");
+  }
+
+  @Test
+  public void should_highlight_prefix() {
+    assertHighlighting("quickbrownfox", "quick", "<mark>quick</mark>brownfox");
+  }
+
+  @Test
+  public void should_highlight_suffix() {
+    assertHighlighting("quickbrownfox", "fox", "quickbrown<mark>fox</mark>");
+  }
+
+  @Test
+  public void should_highlight_multiple_words() {
+    assertHighlighting("quickbrownfox", "fox bro", "quick<mark>bro</mark>wn<mark>fox</mark>");
+  }
+
+  @Test
+  public void should_highlight_multiple_connected_words() {
+    assertHighlighting("quickbrownfox", "fox brown", "quick<mark>brownfox</mark>");
+  }
+
+  private void assertHighlighting(String fileName, String search, String expectedHighlighting) {
+    indexFile(fileName);
+
+    SuggestionQuery query = SuggestionQuery.builder()
+      .setQuery(search)
+      .setQualifiers(Collections.singletonList(Qualifiers.FILE))
+      .build();
+    Stream<ComponentHitsPerQualifier> results = index.searchSuggestions(query, features.get()).getQualifiers();
+
+    assertThat(results).flatExtracting(ComponentHitsPerQualifier::getHits)
+      .extracting(ComponentHit::getHighlightedText)
+      .extracting(Optional::get)
+      .containsExactly(expectedHighlighting);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java
new file mode 100644 (file)
index 0000000..24597b6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+
+import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.db.user.UserTesting.newUserDto;
+
+public class ComponentIndexLoginTest extends ComponentIndexTest {
+
+  @Test
+  public void should_filter_unauthorized_results() {
+    indexer.index(newProject("sonarqube", "Quality Product"));
+
+    // do not give any permissions to that project
+
+    assertNoSearchResults("sonarqube");
+    assertNoSearchResults("Quality Product");
+  }
+
+  @Test
+  public void should_find_project_for_which_the_user_has_direct_permission() {
+    UserDto user = newUserDto();
+    userSession.logIn(user);
+
+    ComponentDto project = newProject("sonarqube", "Quality Product");
+    indexer.index(project);
+
+    assertNoSearchResults("sonarqube");
+
+    // give the user explicit access
+    authorizationIndexerTester.allowOnlyUser(project, user);
+    assertSearchResults("sonarqube", project);
+  }
+
+  @Test
+  public void should_find_project_for_which_the_user_has_indirect_permission_through_group() {
+    GroupDto group = newGroupDto();
+    userSession.logIn().setGroups(group);
+
+    ComponentDto project = newProject("sonarqube", "Quality Product");
+    indexer.index(project);
+
+    assertNoSearchResults("sonarqube");
+
+    // give the user implicit access (though group)
+    authorizationIndexerTester.allowOnlyGroup(project, group);
+    assertSearchResults("sonarqube", project);
+  }
+
+  @Test
+  public void do_not_check_permissions_when_logged_in_user_is_root() {
+    userSession.logIn().setRoot();
+    ComponentDto project = newProject("sonarqube", "Quality Product");
+    indexer.index(project);
+    // do not give any permissions to that project
+
+    assertSearchResults("sonarqube", project);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexMultipleWordsTest.java
new file mode 100644 (file)
index 0000000..6eda6e3
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import org.junit.Test;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+public class ComponentIndexMultipleWordsTest extends ComponentIndexTest {
+
+  @Test
+  public void should_find_perfect_match() {
+    assertResultOrder("struts java",
+      "Struts.java");
+  }
+
+  @Test
+  public void should_find_partial_match() {
+    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
+    assertResultOrder("struts java",
+      "Xstrutsx.Xjavax");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_word1() {
+    assertResultOrder("struts java",
+      "MyStruts.java");
+  }
+
+  @Test
+  public void should_find_partial_match_suffix_word1() {
+    assertResultOrder("struts java",
+      "StrutsObject.java");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_word2() {
+    assertResultOrder("struts java",
+      "MyStruts.xjava");
+  }
+
+  @Test
+  public void should_find_partial_match_suffix_word2() {
+    assertResultOrder("struts java",
+      "MyStruts.javax");
+  }
+
+  @Test
+  public void should_find_partial_match_prefix_and_suffix_everywhere() {
+    assertResultOrder("struts java",
+      "MyStrutsObject.xjavax");
+  }
+
+  @Test
+  public void should_find_subset_of_document_terms() {
+    assertResultOrder("struts java",
+      "Some.Struts.Class.java.old");
+  }
+
+  @Test
+  public void should_require_all_words_to_match() {
+    assertNoFileMatches("struts java",
+      "Struts");
+  }
+
+  @Test
+  public void should_ignore_empty_words() {
+    assertFileMatches("            struts   \n     \n\n",
+      "Struts");
+  }
+
+  @Test
+  public void should_require_all_words_to_match_for_partial() {
+    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
+    assertNoFileMatches("struts java",
+      "Struts");
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java
new file mode 100644 (file)
index 0000000..de165d9
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRepertoire;
+
+import static java.util.Arrays.asList;
+import static org.sonar.api.resources.Qualifiers.FILE;
+import static org.sonar.api.resources.Qualifiers.MODULE;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public class ComponentIndexScoreTest extends ComponentIndexTest {
+
+  @Test
+  public void should_prefer_components_without_prefix() {
+    assertResultOrder("File.java",
+      "File.java",
+      "MyFile.java");
+  }
+
+  @Test
+  public void should_prefer_components_without_suffix() {
+    assertResultOrder("File",
+      "File",
+      "Filex");
+  }
+
+  @Test
+  public void should_prefer_key_matching_over_name_matching() {
+    ComponentDto project1 = indexProject("quality", "SonarQube");
+    ComponentDto project2 = indexProject("sonarqube", "Quality Product");
+
+    assertExactResults("sonarqube", project2, project1);
+  }
+
+  @Test
+  public void should_prefer_prefix_matching_over_partial_matching() {
+    assertResultOrder("corem",
+      "CoreMetrics.java",
+      "ScoreMatrix.java");
+  }
+
+  @Test
+  public void should_prefer_case_sensitive_prefix() {
+    assertResultOrder("caSe",
+      "caSeBla.java",
+      "CaseBla.java");
+  }
+
+  @Test
+  public void scoring_prefix_with_multiple_words() {
+    assertResultOrder("index java",
+      "IndexSomething.java",
+      "MyIndex.java");
+  }
+
+  @Test
+  public void scoring_prefix_with_multiple_words_and_case() {
+    assertResultOrder("Index JAVA",
+      "IndexSomething.java",
+      "index_java.js");
+  }
+
+  @Test
+  public void scoring_long_items() {
+    assertResultOrder("ThisIsAVeryLongNameToSearchForAndItExceeds15Characters.java",
+      "ThisIsAVeryLongNameToSearchForAndItExceeds15Characters.java",
+      "ThisIsAVeryLongNameToSearchForAndItEndsDifferently.java");
+  }
+
+  @Test
+  public void scoring_perfect_match() {
+    assertResultOrder("SonarQube",
+      "SonarQube",
+      "SonarQube SCM Git");
+  }
+
+  @Test
+  public void scoring_perfect_match_dispite_case_changes() {
+    assertResultOrder("sonarqube",
+      "SonarQube",
+      "SonarQube SCM Git");
+  }
+
+  @Test
+  public void scoring_perfect_match_with_matching_case_higher_than_without_matching_case() {
+    assertResultOrder("sonarqube",
+      "sonarqube",
+      "SonarQube");
+  }
+
+  @Test
+  public void should_prefer_favorite_over_recently_browsed() {
+    ComponentDto file1 = db.components().insertPrivateProject(c -> c.setName("File1"));
+    index(file1);
+
+    ComponentDto file2 = db.components().insertPrivateProject(c -> c.setName("File2"));
+    index(file2);
+
+    assertSearch(SuggestionQuery.builder()
+      .setQuery("File")
+      .setQualifiers(asList(PROJECT, MODULE, FILE))
+      .setRecentlyBrowsedKeys(ImmutableSet.of(file1.getDbKey()))
+      .setFavoriteKeys(ImmutableSet.of(file2.getDbKey()))
+      .build()).containsExactly(uuids(file2, file1));
+
+    assertSearch(SuggestionQuery.builder()
+      .setQuery("File")
+      .setQualifiers(asList(PROJECT, MODULE, FILE))
+      .setRecentlyBrowsedKeys(ImmutableSet.of(file2.getDbKey()))
+      .setFavoriteKeys(ImmutableSet.of(file1.getDbKey()))
+      .build()).containsExactly(uuids(file1, file2));
+  }
+
+  @Test
+  public void do_not_match_wrong_file_extension() {
+    ComponentDto file1 = indexFile("MyClass.java");
+    ComponentDto file2 = indexFile("ClassExample.java");
+    ComponentDto file3 = indexFile("Class.java");
+    indexFile("Class.cs");
+    indexFile("Class.js");
+    indexFile("Class.rb");
+
+    assertExactResults("Class java", file3, file2, file1);
+  }
+
+  @Test
+  public void if_relevancy_is_equal_fall_back_to_alphabetical_ordering() {
+    assertResultOrder("sonarqube",
+      "sonarqubeA",
+      "sonarqubeB");
+  }
+
+  @Test
+  public void scoring_test_DbTester() {
+    features.set(ComponentTextSearchFeatureRepertoire.PARTIAL);
+
+    ComponentDto project = indexProject("key-1", "Quality Product");
+
+    index(ComponentTesting.newFileDto(project)
+      .setName("DbTester.java")
+      .setDbKey("java/org/example/DbTester.java")
+      .setUuid("UUID-DbTester"));
+
+    index(ComponentTesting.newFileDto(project)
+      .setName("WebhookDbTesting.java")
+      .setDbKey("java/org/example/WebhookDbTesting.java")
+      .setUuid("UUID-WebhookDbTesting"));
+
+    assertSearch("dbt").containsExactly(
+
+      "UUID-DbTester",
+      "UUID-WebhookDbTesting"
+
+    );
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexSearchTest.java
new file mode 100644 (file)
index 0000000..46bc648
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRule;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+public class ComponentIndexSearchTest {
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public ComponentTextSearchFeatureRule features = new ComponentTextSearchFeatureRule();
+
+  private ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
+
+  private ComponentIndex underTest = new ComponentIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+
+  @Test
+  public void filter_by_language() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto javaFile = db.components().insertComponent(newFileDto(project).setLanguage("java"));
+    ComponentDto jsFile1 = db.components().insertComponent(newFileDto(project).setLanguage("js"));
+    ComponentDto jsFile2 = db.components().insertComponent(newFileDto(project).setLanguage("js"));
+    index(project);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setLanguage("js").build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(jsFile1.uuid(), jsFile2.uuid());
+  }
+
+  @Test
+  public void filter_by_name() {
+    ComponentDto ignoredProject = db.components().insertPrivateProject(p -> p.setName("ignored project"));
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setName("Project Shiny name"));
+    index(ignoredProject, project);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQuery("shiny").build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void filter_by_key_with_exact_match() {
+    ComponentDto ignoredProject = db.components().insertPrivateProject(p -> p.setDbKey("ignored-project"));
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("shiny-project"));
+    ComponentDto anotherIgnoreProject = db.components().insertPrivateProject(p -> p.setDbKey("another-shiny-project"));
+    index(ignoredProject, project);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQuery("shiny-project").build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void filter_by_qualifier() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    index(project);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setQualifiers(singleton(Qualifiers.FILE)).build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(file.uuid());
+  }
+
+  @Test
+  public void filter_by_organization() {
+    OrganizationDto organization = db.organizations().insert();
+    OrganizationDto anotherOrganization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
+    index(project, anotherProject);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().setOrganization(organization.getUuid()).build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void order_by_name_case_insensitive() {
+    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setName("PROJECT 2"));
+    ComponentDto project3 = db.components().insertPrivateProject(p -> p.setName("project 3"));
+    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setName("Project 1"));
+    index(project1, project2, project3);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactly(project1.uuid(), project2.uuid(), project3.uuid());
+  }
+
+  @Test
+  public void paginate_results() {
+    List<ComponentDto> projects = IntStream.range(0, 9)
+      .mapToObj(i -> db.components().insertPrivateProject(p -> p.setName("project " + i)))
+      .collect(Collectors.toList());
+    index(projects.toArray(new ComponentDto[0]));
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions().setPage(2, 3));
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(projects.get(3).uuid(), projects.get(4).uuid(), projects.get(5).uuid());
+  }
+
+  @Test
+  public void filter_unauthorized_components() {
+    ComponentDto unauthorizedProject = db.components().insertPrivateProject();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    indexer.indexOnStartup(emptySet());
+    authorizationIndexerTester.allowOnlyAnyone(project1);
+    authorizationIndexerTester.allowOnlyAnyone(project2);
+
+    SearchIdResult<String> result = underTest.search(ComponentQuery.builder().build(), new SearchOptions());
+
+    assertThat(result.getIds()).containsExactlyInAnyOrder(project1.uuid(), project2.uuid())
+      .doesNotContain(unauthorizedProject.uuid());
+  }
+
+  private void index(ComponentDto... components) {
+    indexer.indexOnStartup(emptySet());
+    Arrays.stream(components).forEach(c -> authorizationIndexerTester.allowOnlyAnyone(c));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java
new file mode 100644 (file)
index 0000000..f3c82a2
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.assertj.core.api.ListAssert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeatureRule;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.resources.Qualifiers.FILE;
+import static org.sonar.api.resources.Qualifiers.MODULE;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public abstract class ComponentIndexTest {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  @Rule
+  public ComponentTextSearchFeatureRule features = new ComponentTextSearchFeatureRule();
+
+  protected ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
+  protected ComponentIndex index = new ComponentIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+  protected PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
+  private OrganizationDto organization;
+
+  @Before
+  public void setUp() {
+    organization = OrganizationTesting.newOrganizationDto();
+  }
+
+  protected void assertFileMatches(String query, String... fileNames) {
+    ComponentDto[] files = Arrays.stream(fileNames)
+      .map(this::indexFile)
+      .toArray(ComponentDto[]::new);
+    assertSearch(query).containsExactlyInAnyOrder(uuids(files));
+  }
+
+  protected void assertNoFileMatches(String query, String... fileNames) {
+    Arrays.stream(fileNames)
+      .forEach(this::indexFile);
+    assertSearch(query).isEmpty();
+  }
+
+  protected void assertResultOrder(String query, String... resultsInOrder) {
+    ComponentDto project = indexProject("key-1", "Quality Product");
+    List<ComponentDto> files = Arrays.stream(resultsInOrder)
+      .map(r -> ComponentTesting.newFileDto(project).setName(r))
+      .peek(f -> f.setUuid(f.uuid() + "_" + f.name().replaceAll("[^a-zA-Z0-9]", "")))
+      .collect(Collectors.toList());
+
+    // index them, but not in the expected order
+    files.stream()
+      .sorted(Comparator.comparing(ComponentDto::uuid).reversed())
+      .forEach(this::index);
+
+    assertExactResults(query, files.toArray(new ComponentDto[0]));
+  }
+
+  protected ListAssert<String> assertSearch(String query) {
+    return assertSearch(SuggestionQuery.builder().setQuery(query).setQualifiers(asList(PROJECT, MODULE, FILE)).build());
+  }
+
+  protected ListAssert<String> assertSearch(SuggestionQuery query) {
+    return (ListAssert<String>)assertThat(index.searchSuggestions(query, features.get()).getQualifiers())
+      .flatExtracting(ComponentHitsPerQualifier::getHits)
+      .extracting(ComponentHit::getUuid);
+  }
+
+  protected void assertSearchResults(String query, ComponentDto... expectedComponents) {
+    assertSearchResults(SuggestionQuery.builder().setQuery(query).setQualifiers(asList(PROJECT, MODULE, FILE)).build(), expectedComponents);
+  }
+
+  protected void assertSearchResults(SuggestionQuery query, ComponentDto... expectedComponents) {
+    assertSearch(query).containsOnly(uuids(expectedComponents));
+  }
+
+  protected void assertExactResults(String query, ComponentDto... expectedComponents) {
+    assertSearch(query).containsExactly(uuids(expectedComponents));
+  }
+
+  protected void assertNoSearchResults(String query) {
+    assertSearchResults(query);
+  }
+
+  protected ComponentDto indexProject(String key, String name) {
+    return index(
+      ComponentTesting.newPrivateProjectDto(organization, "UUID_" + key)
+        .setDbKey(key)
+        .setName(name));
+  }
+
+  protected ComponentDto newProject(String key, String name) {
+    return ComponentTesting.newPrivateProjectDto(organization, "UUID_" + key)
+      .setDbKey(key)
+      .setName(name);
+  }
+
+  protected ComponentDto indexFile(String fileName) {
+    ComponentDto project = indexProject("key-1", "SonarQube");
+    return indexFile(project, "src/main/java/" + fileName, fileName);
+  }
+
+  protected ComponentDto indexFile(ComponentDto project, String fileKey, String fileName) {
+    return index(
+      ComponentTesting.newFileDto(project)
+        .setDbKey(fileKey)
+        .setName(fileName));
+  }
+
+  protected ComponentDto index(ComponentDto dto) {
+    indexer.index(dto);
+    authorizationIndexerTester.allowOnlyAnyone(dto);
+    return dto;
+  }
+
+  protected static String[] uuids(ComponentDto... expectedComponents) {
+    return Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new);
+  }
+}
index 110092b5e16e7f0cff9d31c14e7f84025d3a9468..e95d2ebcb963268a6caa2ddc596baba060f64f18 100644 (file)
@@ -43,8 +43,8 @@ import org.sonar.server.component.ws.SearchAction.SearchRequest;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.l18n.I18nRule;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -93,7 +93,7 @@ public class SearchActionTest {
   private Languages languages = mock(Languages.class);
   private ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
   private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
-  private ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
+  private ComponentIndex index = new ComponentIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
 
   private UserDto user;
 
index a91189f110c052e037e6989858cd71c201f9e0f0..23bd0f1070fa0b340b324cd9f1748adbe04e835c 100644 (file)
@@ -49,8 +49,8 @@ import org.sonar.server.component.ws.SearchProjectsAction.SearchProjectsRequest;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.measure.index.ProjectMeasuresIndex;
 import org.sonar.server.measure.index.ProjectMeasuresIndexer;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -125,7 +125,7 @@ public class SearchProjectsActionTest {
   private DbSession dbSession = db.getSession();
 
   private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, es.client()));
-  private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
+  private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
   private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
 
   private WsActionTester ws = new WsActionTester(
index b3024b62fe143d80da8c044d3d06c0b0ad6bd35e..325784e3c55f5dbbce3f5abd0bb8e6224d487685 100644 (file)
@@ -41,8 +41,8 @@ import org.sonar.server.component.index.ComponentIndex;
 import org.sonar.server.component.index.ComponentIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.favorite.FavoriteFinder;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.TestResponse;
@@ -96,7 +96,7 @@ public class SuggestionsActionTest {
 
   private ComponentIndexer componentIndexer = new ComponentIndexer(db.getDbClient(), es.client());
   private FavoriteFinder favoriteFinder = mock(FavoriteFinder.class);
-  private ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSessionRule), System2.INSTANCE);
+  private ComponentIndex index = new ComponentIndex(es.client(), new WebAuthorizationTypeSupport(userSessionRule), System2.INSTANCE);
   private SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index, favoriteFinder, userSessionRule, resourceTypes);
   private OrganizationDto organization;
   private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, componentIndexer);
index 93f4380b47eea47992cddd48447a39f752f527d5..c7334911c4fe50bde53d7a51156a2607b5363cac 100644 (file)
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.commons.lang.time.DateUtils;
 import org.junit.Test;
 import org.sonar.db.issue.IssueDto;
+import org.sonar.server.issue.index.IssueQuery;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexDebtTest.java
new file mode 100644 (file)
index 0000000..3cafe68
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.util.Map;
+import java.util.TimeZone;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.issue.IssueDocTesting;
+import org.sonar.server.issue.index.IssueQuery.Builder;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
+
+public class IssueIndexDebtTest {
+
+  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(TimeZone.getTimeZone("GMT-01:00"));
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
+  private IssueIndex underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+  }
+
+  @Test
+  public void facets_on_projects() {
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto, "ABCD");
+    ComponentDto project2 = ComponentTesting.newPrivateProjectDto(organizationDto, "EFGH");
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", ComponentTesting.newFileDto(project, null)).setEffort(10L),
+      IssueDocTesting.newDoc("I2", ComponentTesting.newFileDto(project, null)).setEffort(10L),
+      IssueDocTesting.newDoc("I3", ComponentTesting.newFileDto(project2, null)).setEffort(10L));
+
+    Facets facets = search("projectUuids");
+    assertThat(facets.getNames()).containsOnly("projectUuids", FACET_MODE_EFFORT);
+    assertThat(facets.get("projectUuids")).containsOnly(entry("ABCD", 20L), entry("EFGH", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
+  }
+
+  @Test
+  public void facets_on_components() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto(), "A");
+    ComponentDto file1 = ComponentTesting.newFileDto(project, null, "ABCD");
+    ComponentDto file2 = ComponentTesting.newFileDto(project, null, "BCDE");
+    ComponentDto file3 = ComponentTesting.newFileDto(project, null, "CDEF");
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", project).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file1).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file2).setEffort(10L),
+      IssueDocTesting.newDoc("I4", file2).setEffort(10L),
+      IssueDocTesting.newDoc("I5", file3).setEffort(10L));
+
+    Facets facets = search("fileUuids");
+    assertThat(facets.getNames()).containsOnly("fileUuids", FACET_MODE_EFFORT);
+    assertThat(facets.get("fileUuids"))
+      .containsOnly(entry("A", 10L), entry("ABCD", 10L), entry("BCDE", 20L), entry("CDEF", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 50L));
+  }
+
+  @Test
+  public void facets_on_directories() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file1 = ComponentTesting.newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
+    ComponentDto file2 = ComponentTesting.newFileDto(project, null).setPath("F2.xoo");
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file1).setDirectoryPath("/src/main/xoo").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file2).setDirectoryPath("/").setEffort(10L));
+
+    Facets facets = search("directories");
+    assertThat(facets.getNames()).containsOnly("directories", FACET_MODE_EFFORT);
+    assertThat(facets.get("directories")).containsOnly(entry("/src/main/xoo", 10L), entry("/", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 20L));
+  }
+
+  @Test
+  public void facets_on_severities() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file).setSeverity(Severity.INFO).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setSeverity(Severity.INFO).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setSeverity(Severity.MAJOR).setEffort(10L));
+
+    Facets facets = search("severities");
+    assertThat(facets.getNames()).containsOnly("severities", FACET_MODE_EFFORT);
+    assertThat(facets.get("severities")).containsOnly(entry("INFO", 20L), entry("MAJOR", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
+  }
+
+  @Test
+  public void facets_on_statuses() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file).setStatus(STATUS_CLOSED).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setStatus(STATUS_CLOSED).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setStatus(STATUS_OPEN).setEffort(10L));
+
+    Facets facets = search("statuses");
+    assertThat(facets.getNames()).containsOnly("statuses", FACET_MODE_EFFORT);
+    assertThat(facets.get("statuses")).containsOnly(entry("CLOSED", 20L), entry("OPEN", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
+  }
+
+  @Test
+  public void facets_on_resolutions() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED).setEffort(10L));
+
+    Facets facets = search("resolutions");
+    assertThat(facets.getNames()).containsOnly("resolutions", FACET_MODE_EFFORT);
+    assertThat(facets.get("resolutions")).containsOnly(entry("FALSE-POSITIVE", 20L), entry("FIXED", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
+  }
+
+  @Test
+  public void facets_on_languages() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+    RuleKey ruleKey = RuleKey.of("repo", "X1");
+
+    indexIssues(IssueDocTesting.newDoc("I1", file).setLanguage("xoo").setEffort(10L));
+
+    Facets facets = search("languages");
+    assertThat(facets.getNames()).containsOnly("languages", FACET_MODE_EFFORT);
+    assertThat(facets.get("languages")).containsOnly(entry("xoo", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 10L));
+  }
+
+  private Facets search(String additionalFacet) {
+    return new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList(additionalFacet))), system2.getDefaultTimeZone());
+  }
+
+  @Test
+  public void facets_on_assignees() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file).setAssigneeUuid("uuid-steph").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setAssigneeUuid("uuid-simon").setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setAssigneeUuid("uuid-simon").setEffort(10L),
+      IssueDocTesting.newDoc("I4", file).setAssigneeUuid(null).setEffort(10L));
+
+    Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("assignees"))), system2.getDefaultTimeZone());
+    assertThat(facets.getNames()).containsOnly("assignees", FACET_MODE_EFFORT);
+    assertThat(facets.get("assignees")).containsOnly(entry("uuid-steph", 10L), entry("uuid-simon", 20L), entry("", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
+  }
+
+  @Test
+  public void facets_on_authors() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", file).setAuthorLogin("steph").setEffort(10L),
+      IssueDocTesting.newDoc("I2", file).setAuthorLogin("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L),
+      IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L));
+
+    Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("authors"))), system2.getDefaultTimeZone());
+    assertThat(facets.getNames()).containsOnly("authors", FACET_MODE_EFFORT);
+    assertThat(facets.get("authors")).containsOnly(entry("steph", 10L), entry("simon", 20L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
+  }
+
+  @Test
+  public void facet_on_created_at() {
+    SearchOptions searchOptions = fixtureForCreatedAtFacet();
+
+    Builder query = newQueryBuilder().createdBefore(parseDateTime("2016-01-01T00:00:00+0100"));
+    Map<String, Long> createdAt = new Facets(underTest.search(query.build(), searchOptions), system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2011-01-01", 10L),
+      entry("2012-01-01", 0L),
+      entry("2013-01-01", 0L),
+      entry("2014-01-01", 50L),
+      entry("2015-01-01", 10L));
+  }
+
+  @Test
+  public void deprecated_debt_facets() {
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto, "ABCD");
+    ComponentDto project2 = ComponentTesting.newPrivateProjectDto(organizationDto, "EFGH");
+
+    indexIssues(
+      IssueDocTesting.newDoc("I1", ComponentTesting.newFileDto(project, null)).setEffort(10L),
+      IssueDocTesting.newDoc("I2", ComponentTesting.newFileDto(project, null)).setEffort(10L),
+      IssueDocTesting.newDoc("I3", ComponentTesting.newFileDto(project2, null)).setEffort(10L));
+
+    Facets facets = new Facets(underTest.search(IssueQuery.builder().facetMode(DEPRECATED_FACET_MODE_DEBT).build(),
+      new SearchOptions().addFacets(asList("projectUuids"))), system2.getDefaultTimeZone());
+    assertThat(facets.getNames()).containsOnly("projectUuids", FACET_MODE_EFFORT);
+    assertThat(facets.get("projectUuids")).containsOnly(entry("ABCD", 20L), entry("EFGH", 10L));
+    assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 30L));
+  }
+
+  private SearchOptions fixtureForCreatedAtFacet() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = ComponentTesting.newFileDto(project, null);
+
+    IssueDoc issue0 = IssueDocTesting.newDoc("ISSUE0", file).setEffort(10L).setFuncCreationDate(parseDateTime("2011-04-25T01:05:13+0100"));
+    IssueDoc issue1 = IssueDocTesting.newDoc("I1", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-01T12:34:56+0100"));
+    IssueDoc issue2 = IssueDocTesting.newDoc("I2", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-01T23:46:00+0100"));
+    IssueDoc issue3 = IssueDocTesting.newDoc("I3", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-02T12:34:56+0100"));
+    IssueDoc issue4 = IssueDocTesting.newDoc("I4", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-05T12:34:56+0100"));
+    IssueDoc issue5 = IssueDocTesting.newDoc("I5", file).setEffort(10L).setFuncCreationDate(parseDateTime("2014-09-20T12:34:56+0100"));
+    IssueDoc issue6 = IssueDocTesting.newDoc("I6", file).setEffort(10L).setFuncCreationDate(parseDateTime("2015-01-18T12:34:56+0100"));
+
+    indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
+
+    return new SearchOptions().addFacets("createdAt");
+  }
+
+  private void indexIssues(IssueDoc... issues) {
+    issueIndexer.index(asList(issues).iterator());
+    for (IssueDoc issue : issues) {
+      addIssueAuthorization(issue.projectUuid());
+    }
+  }
+
+  private void addIssueAuthorization(String projectUuid) {
+    IndexPermissions access = new IndexPermissions(projectUuid, Qualifiers.PROJECT);
+    access.allowAnyone();
+    authorizationIndexerTester.allow(access);
+  }
+
+  private Builder newQueryBuilder() {
+    return IssueQuery.builder().facetMode(FACET_MODE_EFFORT);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexProjectStatisticsTest.java
new file mode 100644 (file)
index 0000000..b32479c
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.util.Date;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.utils.System2;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.component.ComponentTesting.newBranchDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.component.ComponentTesting.newProjectBranch;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
+import static org.sonar.server.issue.IssueDocTesting.newDoc;
+
+public class IssueIndexProjectStatisticsTest {
+
+  private System2 system2 = mock(System2.class);
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+
+  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), null, new IssueIteratorFactory(null));
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
+
+  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+
+  @Test
+  public void searchProjectStatistics_returns_empty_list_if_no_input() {
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(emptyList(), emptyList(), "unknownUser");
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_empty_list_if_the_input_does_not_match_anything() {
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList("unknownProjectUuid"), singletonList(1_111_234_567_890L), "unknownUser");
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_something() {
+    OrganizationDto organization = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(organization);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
+  }
+
+  @Test
+  public void searchProjectStatistics_does_not_return_results_if_assignee_does_not_match() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String user1Uuid = randomAlphanumeric(40);
+    String user2Uuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(user1Uuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), user2Uuid);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_results_if_assignee_matches() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String user1Uuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(user1Uuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), user1Uuid);
+
+    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_results_if_functional_date_is_strictly_after_from_date() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
+  }
+
+  @Test
+  public void searchProjectStatistics_does_not_return_results_if_functional_date_is_same_as_from_date() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).extracting(ProjectStatistics::getProjectUuid).containsExactly(project.uuid());
+  }
+
+  @Test
+  public void searchProjectStatistics_does_not_return_resolved_issues() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(
+      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
+      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_REMOVED),
+      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)).setResolution(Issue.RESOLUTION_WONT_FIX));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void searchProjectStatistics_does_not_return_results_if_functional_date_is_before_from_date() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from - 1000L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_issue_count() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(
+      newDoc("issue1", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue2", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue3", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result).extracting(ProjectStatistics::getIssueCount).containsExactly(3L);
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_issue_count_for_multiple_projects() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(org1);
+    ComponentDto project2 = newPrivateProjectDto(org1);
+    ComponentDto project3 = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(
+      newDoc("issue1", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue2", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue3", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+
+      newDoc("issue4", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue5", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(
+      asList(project1.uuid(), project2.uuid(), project3.uuid()),
+      asList(from, from, from),
+      userUuid);
+
+    assertThat(result)
+      .extracting(ProjectStatistics::getProjectUuid, ProjectStatistics::getIssueCount)
+      .containsExactlyInAnyOrder(
+        tuple(project1.uuid(), 3L),
+        tuple(project3.uuid(), 2L));
+  }
+
+  @Test
+  public void searchProjectStatistics_returns_max_date_for_multiple_projects() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(org1);
+    ComponentDto project2 = newPrivateProjectDto(org1);
+    ComponentDto project3 = newPrivateProjectDto(org1);
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_000L;
+    indexIssues(
+      newDoc("issue1", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1_000L)),
+      newDoc("issue2", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 2_000L)),
+      newDoc("issue3", project1).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 3_000L)),
+
+      newDoc("issue4", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 4_000L)),
+      newDoc("issue5", project3).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 5_000L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(
+      asList(project1.uuid(), project2.uuid(), project3.uuid()),
+      asList(from, from, from),
+      userUuid);
+
+    assertThat(result)
+      .extracting(ProjectStatistics::getProjectUuid, ProjectStatistics::getLastIssueDate)
+      .containsExactlyInAnyOrder(
+        tuple(project1.uuid(), from + 3_000L),
+        tuple(project3.uuid(), from + 5_000L));
+  }
+
+  @Test
+  public void searchProjectStatistics_return_branch_issues() {
+    OrganizationDto organization = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(organization);
+    ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey("branch"));
+    String userUuid = randomAlphanumeric(40);
+    long from = 1_111_234_567_890L;
+    indexIssues(
+      newDoc("issue1", branch).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)),
+      newDoc("issue2", branch).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 2L)),
+      newDoc("issue3", project).setAssigneeUuid(userUuid).setFuncCreationDate(new Date(from + 1L)));
+
+    List<ProjectStatistics> result = underTest.searchProjectStatistics(singletonList(project.uuid()), singletonList(from), userUuid);
+
+    assertThat(result)
+      .extracting(ProjectStatistics::getIssueCount, ProjectStatistics::getProjectUuid, ProjectStatistics::getLastIssueDate)
+      .containsExactly(
+        tuple(2L, branch.uuid(), from + 2L),
+        tuple(1L, project.uuid(), from + 1L));
+  }
+
+  private void indexIssues(IssueDoc... issues) {
+    issueIndexer.index(asList(issues).iterator());
+    for (IssueDoc issue : issues) {
+      IndexPermissions access = new IndexPermissions(issue.projectUuid(), "TRK");
+      access.allowAnyone();
+      authorizationIndexerTester.allow(access);
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
new file mode 100644 (file)
index 0000000..cc1f50f
--- /dev/null
@@ -0,0 +1,1752 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.assertj.core.api.Fail;
+import org.assertj.core.groups.Tuple;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.search.SearchHit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.rule.index.RuleIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.view.index.ViewDoc;
+import org.sonar.server.view.index.ViewIndexer;
+
+import static com.google.common.collect.ImmutableSortedSet.of;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.TimeZone.getTimeZone;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.rules.ExpectedException.none;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.resources.Qualifiers.APP;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+import static org.sonar.api.utils.DateUtils.addDays;
+import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
+import static org.sonar.db.rule.RuleTesting.newRule;
+import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.issue.IssueDocTesting.newDoc;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD;
+
+public class IssueIndexTest {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = none();
+  private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
+  private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
+  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, issueIndexer);
+
+  private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
+
+  @Test
+  public void filter_by_keys() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+
+    indexIssues(
+      newDoc("I1", newFileDto(project, null)),
+      newDoc("I2", newFileDto(project, null)));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().issueKeys(asList("I1", "I2")), "I1", "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().issueKeys(singletonList("I1")), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().issueKeys(asList("I3", "I4")));
+  }
+
+  @Test
+  public void filter_by_projects() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto module = newModuleDto(project);
+    ComponentDto subModule = newModuleDto(module);
+
+    indexIssues(
+      newDoc("I1", project),
+      newDoc("I2", newFileDto(project, null)),
+      newDoc("I3", module),
+      newDoc("I4", newFileDto(module, null)),
+      newDoc("I5", subModule),
+      newDoc("I6", newFileDto(subModule, null)));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void facet_on_projectUuids() {
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(organizationDto, "ABCD");
+    ComponentDto project2 = newPrivateProjectDto(organizationDto, "EFGH");
+
+    indexIssues(
+      newDoc("I1", newFileDto(project, null)),
+      newDoc("I2", newFileDto(project, null)),
+      newDoc("I3", newFileDto(project2, null)));
+
+    assertThatFacetHasExactly(IssueQuery.builder(), "projectUuids", entry("ABCD", 2L), entry("EFGH", 1L));
+  }
+
+  @Test
+  public void filter_by_modules() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto module = newModuleDto(project);
+    ComponentDto subModule = newModuleDto(module);
+    ComponentDto file = newFileDto(subModule, null);
+
+    indexIssues(
+      newDoc("I3", module),
+      newDoc("I5", subModule),
+      newDoc("I2", file));
+
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(file.uuid())));
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(module.uuid())), "I3");
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(subModule.uuid())), "I2", "I5");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList(project.uuid())));
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project.uuid())).moduleUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void filter_by_components_on_contextualized_search() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto module = newModuleDto(project);
+    ComponentDto subModule = newModuleDto(module);
+    ComponentDto file1 = newFileDto(project, null);
+    ComponentDto file2 = newFileDto(module, null);
+    ComponentDto file3 = newFileDto(subModule, null);
+    String view = "ABCD";
+    indexView(view, asList(project.uuid()));
+
+    indexIssues(
+      newDoc("I1", project),
+      newDoc("I2", file1),
+      newDoc("I3", module),
+      newDoc("I4", file2),
+      newDoc("I5", subModule),
+      newDoc("I6", file3));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(asList(file1.uuid(), file2.uuid(), file3.uuid())), "I2", "I4", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(file1.uuid())), "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().moduleRootUuids(singletonList(subModule.uuid())), "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().moduleRootUuids(singletonList(module.uuid())), "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view)), "I1", "I2", "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void filter_by_components_on_non_contextualized_search() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "project");
+    ComponentDto file1 = newFileDto(project, null, "file1");
+    ComponentDto module = newModuleDto(project).setUuid("module");
+    ComponentDto file2 = newFileDto(module, null, "file2");
+    ComponentDto subModule = newModuleDto(module).setUuid("subModule");
+    ComponentDto file3 = newFileDto(subModule, null, "file3");
+    String view = "ABCD";
+    indexView(view, asList(project.uuid()));
+
+    indexIssues(
+      newDoc("I1", project),
+      newDoc("I2", file1),
+      newDoc("I3", module),
+      newDoc("I4", file2),
+      newDoc("I5", subModule),
+      newDoc("I6", file3));
+
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList("unknown")));
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())), "I1", "I2", "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view)), "I1", "I2", "I3", "I4", "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(module.uuid())), "I3", "I4");
+    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(subModule.uuid())), "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(file1.uuid())), "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(asList(file1.uuid(), file2.uuid(), file3.uuid())), "I2", "I4", "I6");
+  }
+
+  @Test
+  public void facets_on_components() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "A");
+    ComponentDto file1 = newFileDto(project, null, "ABCD");
+    ComponentDto file2 = newFileDto(project, null, "BCDE");
+    ComponentDto file3 = newFileDto(project, null, "CDEF");
+
+    indexIssues(
+      newDoc("I1", project),
+      newDoc("I2", file1),
+      newDoc("I3", file2),
+      newDoc("I4", file2),
+      newDoc("I5", file3));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "fileUuids", entry("A", 1L), entry("ABCD", 1L), entry("BCDE", 2L), entry("CDEF", 1L));
+  }
+
+  @Test
+  public void filter_by_directories() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file1 = newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
+    ComponentDto file2 = newFileDto(project, null).setPath("F2.xoo");
+
+    indexIssues(
+      newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
+      newDoc("I2", file2).setDirectoryPath("/"));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().directories(singletonList("/src/main/xoo")), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().directories(singletonList("/")), "I2");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().directories(singletonList("unknown")));
+  }
+
+  @Test
+  public void facets_on_directories() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file1 = newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
+    ComponentDto file2 = newFileDto(project, null).setPath("F2.xoo");
+
+    indexIssues(
+      newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
+      newDoc("I2", file2).setDirectoryPath("/"));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "directories", entry("/src/main/xoo", 1L), entry("/", 1L));
+  }
+
+  @Test
+  public void filter_by_portfolios() {
+    ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
+
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
+    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())), issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio2.uuid())), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(portfolio1.uuid(), portfolio2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
+      issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void filter_by_portfolios_not_having_projects() {
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(organizationDto);
+    ComponentDto file1 = newFileDto(project1, null);
+    indexIssues(newDoc("I2", file1));
+    String view1 = "ABCD";
+    indexView(view1, emptyList());
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view1)));
+  }
+
+  @Test
+  public void do_not_return_issues_from_project_branch_when_filtering_by_portfolios() {
+    ComponentDto portfolio = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project = db.components().insertMainBranch();
+    ComponentDto projectBranch = db.components().insertProjectBranch(project);
+    ComponentDto fileOnProjectBranch = db.components().insertComponent(newFileDto(projectBranch));
+    indexView(portfolio.uuid(), singletonList(project.uuid()));
+
+    IssueDoc issueOnProject = newDoc(project);
+    IssueDoc issueOnProjectBranch = newDoc(projectBranch);
+    IssueDoc issueOnFileOnProjectBranch = newDoc(fileOnProjectBranch);
+    indexIssues(issueOnProject, issueOnFileOnProjectBranch, issueOnProjectBranch);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())), issueOnProject.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())).projectUuids(singletonList(project.uuid())),
+      issueOnProject.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList(portfolio.uuid())).projectUuids(singletonList(projectBranch.uuid())));
+  }
+
+  @Test
+  public void filter_one_issue_by_project_and_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    ComponentDto anotherbBranch = db.components().insertProjectBranch(project);
+
+    IssueDoc issueOnProject = newDoc(project);
+    IssueDoc issueOnBranch = newDoc(branch);
+    IssueDoc issueOnAnotherBranch = newDoc(anotherbBranch);
+    indexIssues(issueOnProject, issueOnBranch, issueOnAnotherBranch);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(branch.uuid())).branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()).mainBranch(false), issueOnBranch.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().componentUuids(singletonList(branch.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()).mainBranch(false),
+      issueOnBranch.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
+  }
+
+  @Test
+  public void issues_from_branch_component_children() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto projectModule = db.components().insertComponent(newModuleDto(project));
+    ComponentDto projectFile = db.components().insertComponent(newFileDto(projectModule));
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+    ComponentDto branchModule = db.components().insertComponent(newModuleDto(branch));
+    ComponentDto branchFile = db.components().insertComponent(newFileDto(branchModule));
+
+    indexIssues(
+      newDoc("I1", project),
+      newDoc("I2", projectFile),
+      newDoc("I3", projectModule),
+      newDoc("I4", branch),
+      newDoc("I5", branchModule),
+      newDoc("I6", branchFile));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()).mainBranch(false), "I4", "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(branchModule.uuid())).branchUuid(branch.uuid()).mainBranch(false), "I5", "I6");
+    assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).branchUuid(branch.uuid()).mainBranch(false), "I6");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).mainBranch(false).branchUuid("unknown"));
+  }
+
+  @Test
+  public void issues_from_main_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    IssueDoc issueOnProject = newDoc(project);
+    IssueDoc issueOnBranch = newDoc(branch);
+    indexIssues(issueOnProject, issueOnBranch);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true), issueOnProject.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().componentUuids(singletonList(project.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(project.uuid()).mainBranch(true),
+      issueOnProject.key());
+  }
+
+  @Test
+  public void branch_issues_are_ignored_when_no_branch_param() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+
+    IssueDoc projectIssue = newDoc(project);
+    IssueDoc branchIssue = newDoc(branch);
+    indexIssues(projectIssue, branchIssue);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder(), projectIssue.key());
+  }
+
+  @Test
+  public void filter_by_main_application() {
+    ComponentDto application1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto application2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+    indexView(application1.uuid(), singletonList(project1.uuid()));
+    indexView(application2.uuid(), singletonList(project2.uuid()));
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())), issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application2.uuid())), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(application1.uuid(), application2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
+      issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void filter_by_application_branch() {
+    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP));
+    ComponentDto branch1 = db.components().insertProjectBranch(application);
+    ComponentDto branch2 = db.components().insertProjectBranch(application);
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+    indexView(branch1.uuid(), singletonList(project1.uuid()));
+    indexView(branch2.uuid(), singletonList(project2.uuid()));
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
+      issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).projectUuids(singletonList(project1.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
+      issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).fileUuids(singletonList(file.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
+      issueOnFile.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
+  }
+
+  @Test
+  public void filter_by_application_branch_having_project_branches() {
+    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP).setDbKey("app"));
+    ComponentDto applicationBranch1 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch1"));
+    ComponentDto applicationBranch2 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch2"));
+    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setDbKey("prj1"));
+    ComponentDto project1Branch1 = db.components().insertProjectBranch(project1);
+    ComponentDto fileOnProject1Branch1 = db.components().insertComponent(newFileDto(project1Branch1));
+    ComponentDto project1Branch2 = db.components().insertProjectBranch(project1);
+    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setDbKey("prj2"));
+    indexView(applicationBranch1.uuid(), asList(project1Branch1.uuid(), project2.uuid()));
+    indexView(applicationBranch2.uuid(), singletonList(project1Branch2.uuid()));
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnProject1Branch1 = newDoc(project1Branch1);
+    IssueDoc issueOnFileOnProject1Branch1 = newDoc(fileOnProject1Branch1);
+    IssueDoc issueOnProject1Branch2 = newDoc(project1Branch2);
+    IssueDoc issueOnProject2 = newDoc(project2);
+    indexIssues(issueOnProject1, issueOnProject1Branch1, issueOnFileOnProject1Branch1, issueOnProject1Branch2, issueOnProject2);
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).branchUuid(applicationBranch1.uuid()).mainBranch(false),
+      issueOnProject1Branch1.key(), issueOnFileOnProject1Branch1.key(), issueOnProject2.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).projectUuids(singletonList(project1.uuid())).branchUuid(applicationBranch1.uuid()).mainBranch(false),
+      issueOnProject1Branch1.key(), issueOnFileOnProject1Branch1.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).fileUuids(singletonList(fileOnProject1Branch1.uuid())).branchUuid(applicationBranch1.uuid())
+        .mainBranch(false),
+      issueOnFileOnProject1Branch1.key());
+    assertThatSearchReturnsEmpty(
+      IssueQuery.builder().viewUuids(singletonList(applicationBranch1.uuid())).projectUuids(singletonList("unknown")).branchUuid(applicationBranch1.uuid()).mainBranch(false));
+  }
+
+  @Test
+  public void filter_by_created_after_by_projects() {
+    Date now = new Date();
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(organizationDto);
+    IssueDoc project1Issue1 = newDoc(project1).setFuncCreationDate(addDays(now, -10));
+    IssueDoc project1Issue2 = newDoc(project1).setFuncCreationDate(addDays(now, -20));
+    ComponentDto project2 = newPrivateProjectDto(organizationDto);
+    IssueDoc project2Issue1 = newDoc(project2).setFuncCreationDate(addDays(now, -15));
+    IssueDoc project2Issue2 = newDoc(project2).setFuncCreationDate(addDays(now, -30));
+    indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);
+
+    // Search for issues of project 1 having less than 15 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
+      project1Issue1.key());
+
+    // Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
+        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
+      project1Issue1.key(), project2Issue1.key());
+
+    // Search for issues of project 1 having less than 30 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
+      project1Issue1.key(), project1Issue2.key());
+
+    // Search for issues of project 1 and project 2 having less than 5 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
+        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
+  }
+
+  @Test
+  public void filter_by_severities() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setSeverity(Severity.INFO),
+      newDoc("I2", file).setSeverity(Severity.MAJOR));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().severities(asList(Severity.INFO, Severity.MAJOR)), "I1", "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().severities(singletonList(Severity.INFO)), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().severities(singletonList(Severity.BLOCKER)));
+  }
+
+  @Test
+  public void facets_on_severities() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setSeverity(Severity.INFO),
+      newDoc("I2", file).setSeverity(Severity.INFO),
+      newDoc("I3", file).setSeverity(Severity.MAJOR));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 2L), entry("MAJOR", 1L));
+  }
+
+  @Test
+  public void filter_by_statuses() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED),
+      newDoc("I2", file).setStatus(Issue.STATUS_OPEN));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().statuses(asList(Issue.STATUS_CLOSED, Issue.STATUS_OPEN)), "I1", "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().statuses(singletonList(Issue.STATUS_CLOSED)), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().statuses(singletonList(Issue.STATUS_CONFIRMED)));
+  }
+
+  @Test
+  public void facets_on_statuses() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED),
+      newDoc("I2", file).setStatus(Issue.STATUS_CLOSED),
+      newDoc("I3", file).setStatus(Issue.STATUS_OPEN));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "statuses", entry("CLOSED", 2L), entry("OPEN", 1L));
+  }
+
+  @Test
+  public void filter_by_resolutions() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
+      newDoc("I2", file).setResolution(Issue.RESOLUTION_FIXED));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().resolutions(asList(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_FIXED)), "I1", "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().resolutions(singletonList(Issue.RESOLUTION_FALSE_POSITIVE)), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().resolutions(singletonList(Issue.RESOLUTION_REMOVED)));
+  }
+
+  @Test
+  public void facets_on_resolutions() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
+      newDoc("I2", file).setResolution(Issue.RESOLUTION_FALSE_POSITIVE),
+      newDoc("I3", file).setResolution(Issue.RESOLUTION_FIXED));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L));
+  }
+
+  @Test
+  public void filter_by_resolved() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("I2", file).setStatus(Issue.STATUS_OPEN).setResolution(null),
+      newDoc("I3", file).setStatus(Issue.STATUS_OPEN).setResolution(null));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(true), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(false), "I2", "I3");
+    assertThatSearchReturnsOnly(IssueQuery.builder().resolved(null), "I1", "I2", "I3");
+  }
+
+  @Test
+  public void filter_by_rules() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    RuleDefinitionDto ruleDefinitionDto = newRule();
+    db.rules().insert(ruleDefinitionDto);
+
+    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().rules(singletonList(ruleDefinitionDto)), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().rules(singletonList(new RuleDefinitionDto().setId(-1))));
+  }
+
+  @Test
+  public void filter_by_languages() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    RuleDefinitionDto ruleDefinitionDto = newRule();
+    db.rules().insert(ruleDefinitionDto);
+
+    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()).setLanguage("xoo"));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().languages(singletonList("xoo")), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().languages(singletonList("unknown")));
+  }
+
+  @Test
+  public void facets_on_languages() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    RuleDefinitionDto ruleDefinitionDto = newRule();
+    db.rules().insert(ruleDefinitionDto);
+
+    indexIssues(newDoc("I1", file).setRuleId(ruleDefinitionDto.getId()).setLanguage("xoo"));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "languages", entry("xoo", 1L));
+  }
+
+  @Test
+  public void filter_by_assignees() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
+      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
+      newDoc("I3", file).setAssigneeUuid(null));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().assigneeUuids(singletonList("steph-uuid")), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().assigneeUuids(asList("steph-uuid", "marcel-uuid")), "I1", "I2");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().assigneeUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void facets_on_assignees() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
+      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
+      newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
+      newDoc("I4", file).setAssigneeUuid(null));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "assignees", entry("steph-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
+  }
+
+  @Test
+  public void facets_on_assignees_supports_dashes() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAssigneeUuid("j-b-uuid"),
+      newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
+      newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
+      newDoc("I4", file).setAssigneeUuid(null));
+
+    assertThatFacetHasOnly(IssueQuery.builder().assigneeUuids(singletonList("j-b")),
+      "assignees", entry("j-b-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
+  }
+
+  @Test
+  public void filter_by_assigned() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAssigneeUuid("steph-uuid"),
+      newDoc("I2", file).setAssigneeUuid(null),
+      newDoc("I3", file).setAssigneeUuid(null));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(true), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(false), "I2", "I3");
+    assertThatSearchReturnsOnly(IssueQuery.builder().assigned(null), "I1", "I2", "I3");
+  }
+
+  @Test
+  public void filter_by_authors() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAuthorLogin("steph"),
+      newDoc("I2", file).setAuthorLogin("marcel"),
+      newDoc("I3", file).setAssigneeUuid(null));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().authors(singletonList("steph")), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().authors(asList("steph", "marcel")), "I1", "I2");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().authors(singletonList("unknown")));
+  }
+
+  @Test
+  public void facets_on_authors() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setAuthorLogin("steph"),
+      newDoc("I2", file).setAuthorLogin("marcel"),
+      newDoc("I3", file).setAuthorLogin("marcel"),
+      newDoc("I4", file).setAuthorLogin(null));
+
+    assertThatFacetHasOnly(IssueQuery.builder(), "authors", entry("steph", 1L), entry("marcel", 2L));
+  }
+
+  @Test
+  public void filter_by_created_after() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
+      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-19")), "I1", "I2");
+    // Lower bound is included
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-20")), "I1", "I2");
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-21")), "I2");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAfter(parseDate("2014-09-25")));
+  }
+
+  @Test
+  public void filter_by_created_before() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
+      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
+
+    assertThatSearchReturnsEmpty(IssueQuery.builder().createdBefore(parseDate("2014-09-19")));
+    // Upper bound is excluded
+    assertThatSearchReturnsEmpty(IssueQuery.builder().createdBefore(parseDate("2014-09-20")));
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdBefore(parseDate("2014-09-21")), "I1");
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdBefore(parseDate("2014-09-25")), "I1", "I2");
+  }
+
+  @Test
+  public void filter_by_created_after_and_before() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")),
+      newDoc("I2", file).setFuncCreationDate(parseDate("2014-09-23")));
+
+    // 19 < createdAt < 25
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDate("2014-09-19")).createdBefore(parseDate("2014-09-25")),
+      "I1", "I2");
+
+    // 20 < createdAt < 25: excludes first issue
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-25")), "I1", "I2");
+
+    // 21 < createdAt < 25
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-25")), "I2");
+
+    // 21 < createdAt < 24
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-24")), "I2");
+
+    // 21 < createdAt < 23: excludes second issue
+    assertThatSearchReturnsEmpty(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-21")).createdBefore(parseDate("2014-09-23")));
+
+    // 19 < createdAt < 21: only first issue
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-19")).createdBefore(parseDate("2014-09-21")), "I1");
+
+    // 20 < createdAt < 20: exception
+    expectedException.expect(IllegalArgumentException.class);
+    underTest.search(IssueQuery.builder()
+      .createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-20"))
+      .build(), new SearchOptions());
+  }
+
+  @Test
+  public void filter_by_create_after_and_before_take_into_account_timezone() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-20T00:00:00+0100")),
+      newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAfter(parseDateTime("2014-09-19T23:00:00+0000")).createdBefore(parseDateTime("2014-09-22T23:00:01+0000")),
+      "I1", "I2");
+
+    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAfter(parseDateTime("2014-09-19T23:00:01+0000")).createdBefore(parseDateTime("2014-09-22T23:00:00+0000")));
+  }
+
+  @Test
+  public void filter_by_created_before_must_be_lower_than_after() {
+    try {
+      underTest.search(IssueQuery.builder().createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-19")).build(),
+        new SearchOptions());
+      Fail.failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+    } catch (IllegalArgumentException exception) {
+      assertThat(exception.getMessage()).isEqualTo("Start bound cannot be larger or equal to end bound");
+    }
+  }
+
+  @Test
+  public void fail_if_created_before_equals_created_after() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Start bound cannot be larger or equal to end bound");
+
+    underTest.search(IssueQuery.builder().createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-20")).build(), new SearchOptions());
+  }
+
+  @Test
+  public void filter_by_created_after_must_not_be_in_future() {
+    try {
+      underTest.search(IssueQuery.builder().createdAfter(new Date(Long.MAX_VALUE)).build(), new SearchOptions());
+      Fail.failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+    } catch (IllegalArgumentException exception) {
+      assertThat(exception.getMessage()).isEqualTo("Start bound cannot be in the future");
+    }
+  }
+
+  @Test
+  public void filter_by_created_at() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(newDoc("I1", file).setFuncCreationDate(parseDate("2014-09-20")));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().createdAt(parseDate("2014-09-20")), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().createdAt(parseDate("2014-09-21")));
+  }
+
+  @Test
+  public void facet_on_created_at_with_less_than_20_days() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    IssueQuery query = IssueQuery.builder()
+      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2014-09-08T00:00:00+0100"))
+      .checkAuthorization(false)
+      .build();
+    SearchResponse result = underTest.search(query, options);
+    Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(buckets).containsOnly(
+      entry("2014-08-31", 0L),
+      entry("2014-09-01", 2L),
+      entry("2014-09-02", 1L),
+      entry("2014-09-03", 0L),
+      entry("2014-09-04", 0L),
+      entry("2014-09-05", 1L),
+      entry("2014-09-06", 0L),
+      entry("2014-09-07", 0L));
+  }
+
+  @Test
+  public void facet_on_created_at_with_less_than_20_weeks() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
+      options);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2014-08-25", 0L),
+      entry("2014-09-01", 4L),
+      entry("2014-09-08", 0L),
+      entry("2014-09-15", 1L));
+  }
+
+  @Test
+  public void facet_on_created_at_with_less_than_20_months() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
+      options);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2014-08-01", 0L),
+      entry("2014-09-01", 5L),
+      entry("2014-10-01", 0L),
+      entry("2014-11-01", 0L),
+      entry("2014-12-01", 0L),
+      entry("2015-01-01", 1L));
+  }
+
+  @Test
+  public void facet_on_created_at_with_more_than_20_months() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+      options);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2010-01-01", 0L),
+      entry("2011-01-01", 1L),
+      entry("2012-01-01", 0L),
+      entry("2013-01-01", 0L),
+      entry("2014-01-01", 5L),
+      entry("2015-01-01", 1L));
+  }
+
+  @Test
+  public void facet_on_created_at_with_one_day() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
+      .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
+      options);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2014-09-01", 2L));
+  }
+
+  @Test
+  public void facet_on_created_at_with_bounds_outside_of_data() {
+    SearchOptions options = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdAfter(parseDateTime("2009-01-01T00:00:00+0100"))
+      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100"))
+      .build(), options);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2008-01-01", 0L),
+      entry("2009-01-01", 0L),
+      entry("2010-01-01", 0L),
+      entry("2011-01-01", 1L),
+      entry("2012-01-01", 0L),
+      entry("2013-01-01", 0L),
+      entry("2014-01-01", 5L),
+      entry("2015-01-01", 1L));
+  }
+
+  @Test
+  public void facet_on_created_at_without_start_bound() {
+    SearchOptions searchOptions = fixtureForCreatedAtFacet();
+
+    SearchResponse result = underTest.search(IssueQuery.builder()
+      .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
+      searchOptions);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).containsOnly(
+      entry("2011-01-01", 1L),
+      entry("2012-01-01", 0L),
+      entry("2013-01-01", 0L),
+      entry("2014-01-01", 5L),
+      entry("2015-01-01", 1L));
+  }
+
+  @Test
+  public void facet_on_created_at_without_issues() {
+    SearchOptions searchOptions = new SearchOptions().addFacets("createdAt");
+
+    SearchResponse result = underTest.search(IssueQuery.builder().build(), searchOptions);
+    Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+    assertThat(createdAt).isNull();
+  }
+
+  private SearchOptions fixtureForCreatedAtFacet() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    IssueDoc issue0 = newDoc("ISSUE0", file).setFuncCreationDate(parseDateTime("2011-04-25T00:05:13+0000"));
+    IssueDoc issue1 = newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-01T12:34:56+0100"));
+    IssueDoc issue2 = newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-01T10:46:00-1200"));
+    IssueDoc issue3 = newDoc("I3", file).setFuncCreationDate(parseDateTime("2014-09-02T23:34:56+1200"));
+    IssueDoc issue4 = newDoc("I4", file).setFuncCreationDate(parseDateTime("2014-09-05T12:34:56+0100"));
+    IssueDoc issue5 = newDoc("I5", file).setFuncCreationDate(parseDateTime("2014-09-20T12:34:56+0100"));
+    IssueDoc issue6 = newDoc("I6", file).setFuncCreationDate(parseDateTime("2015-01-18T12:34:56+0100"));
+
+    indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
+
+    return new SearchOptions().addFacets("createdAt");
+  }
+
+  @Test
+  public void paging() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    for (int i = 0; i < 12; i++) {
+      indexIssues(newDoc("I" + i, file));
+    }
+
+    IssueQuery.Builder query = IssueQuery.builder();
+    // There are 12 issues in total, with 10 issues per page, the page 2 should only contain 2 elements
+    SearchResponse result = underTest.search(query.build(), new SearchOptions().setPage(2, 10));
+    assertThat(result.getHits().hits()).hasSize(2);
+    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
+
+    result = underTest.search(IssueQuery.builder().build(), new SearchOptions().setOffset(0).setLimit(5));
+    assertThat(result.getHits().hits()).hasSize(5);
+    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
+
+    result = underTest.search(IssueQuery.builder().build(), new SearchOptions().setOffset(2).setLimit(0));
+    assertThat(result.getHits().hits()).hasSize(10);
+    assertThat(result.getHits().getTotalHits()).isEqualTo(12);
+  }
+
+  @Test
+  public void search_with_max_limit() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    List<IssueDoc> issues = new ArrayList<>();
+    for (int i = 0; i < 500; i++) {
+      String key = "I" + i;
+      issues.add(newDoc(key, file));
+    }
+    indexIssues(issues.toArray(new IssueDoc[] {}));
+
+    IssueQuery.Builder query = IssueQuery.builder();
+    SearchResponse result = underTest.search(query.build(), new SearchOptions().setLimit(Integer.MAX_VALUE));
+    assertThat(result.getHits().hits()).hasSize(SearchOptions.MAX_LIMIT);
+  }
+
+  @Test
+  public void sort_by_status() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setStatus(Issue.STATUS_OPEN),
+      newDoc("I2", file).setStatus(Issue.STATUS_CLOSED),
+      newDoc("I3", file).setStatus(Issue.STATUS_REOPENED));
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(true);
+    assertThatSearchReturnsOnly(query, "I2", "I1", "I3");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_STATUS).asc(false);
+    assertThatSearchReturnsOnly(query, "I3", "I1", "I2");
+  }
+
+  @Test
+  public void sort_by_severity() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setSeverity(Severity.BLOCKER),
+      newDoc("I2", file).setSeverity(Severity.INFO),
+      newDoc("I3", file).setSeverity(Severity.MINOR),
+      newDoc("I4", file).setSeverity(Severity.CRITICAL),
+      newDoc("I5", file).setSeverity(Severity.MAJOR));
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(true);
+    assertThatSearchReturnsOnly(query, "I2", "I3", "I5", "I4", "I1");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_SEVERITY).asc(false);
+    assertThatSearchReturnsOnly(query, "I1", "I4", "I5", "I3", "I2");
+  }
+
+  @Test
+  public void sort_by_creation_date() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-24T00:00:00+0100")));
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(true);
+    SearchResponse result = underTest.search(query.build(), new SearchOptions());
+    assertThatSearchReturnsOnly(query, "I1", "I2");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CREATION_DATE).asc(false);
+    assertThatSearchReturnsOnly(query, "I2", "I1");
+  }
+
+  @Test
+  public void sort_by_update_date() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncUpdateDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("I2", file).setFuncUpdateDate(parseDateTime("2014-09-24T00:00:00+0100")));
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(true);
+    SearchResponse result = underTest.search(query.build(), new SearchOptions());
+    assertThatSearchReturnsOnly(query, "I1", "I2");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_UPDATE_DATE).asc(false);
+    assertThatSearchReturnsOnly(query, "I2", "I1");
+  }
+
+  @Test
+  public void sort_by_close_date() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+
+    indexIssues(
+      newDoc("I1", file).setFuncCloseDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("I2", file).setFuncCloseDate(parseDateTime("2014-09-24T00:00:00+0100")),
+      newDoc("I3", file).setFuncCloseDate(null));
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(true);
+    SearchResponse result = underTest.search(query.build(), new SearchOptions());
+    assertThatSearchReturnsOnly(query, "I3", "I1", "I2");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_CLOSE_DATE).asc(false);
+    assertThatSearchReturnsOnly(query, "I2", "I1", "I3");
+  }
+
+  @Test
+  public void sort_by_file_and_line() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file1 = newFileDto(project, null, "F1").setPath("src/main/xoo/org/sonar/samples/File.xoo");
+    ComponentDto file2 = newFileDto(project, null, "F2").setPath("src/main/xoo/org/sonar/samples/File2.xoo");
+
+    indexIssues(
+      // file F1
+      newDoc("F1_2", file1).setLine(20),
+      newDoc("F1_1", file1).setLine(null),
+      newDoc("F1_3", file1).setLine(25),
+
+      // file F2
+      newDoc("F2_1", file2).setLine(9),
+      newDoc("F2_2", file2).setLine(109),
+      // two issues on the same line -> sort by key
+      newDoc("F2_3", file2).setLine(109));
+
+    // ascending sort -> F1 then F2. Line "0" first.
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(true);
+    assertThatSearchReturnsOnly(query, "F1_1", "F1_2", "F1_3", "F2_1", "F2_2", "F2_3");
+
+    // descending sort -> F2 then F1
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_FILE_LINE).asc(false);
+    assertThatSearchReturnsOnly(query, "F2_3", "F2_2", "F2_1", "F1_3", "F1_2", "F1_1");
+  }
+
+  @Test
+  public void default_sort_is_by_creation_date_then_project_then_file_then_line_then_issue_key() {
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(organizationDto, "P1");
+    ComponentDto file1 = newFileDto(project1, null, "F1").setPath("src/main/xoo/org/sonar/samples/File.xoo");
+    ComponentDto file2 = newFileDto(project1, null, "F2").setPath("src/main/xoo/org/sonar/samples/File2.xoo");
+
+    ComponentDto project2 = newPrivateProjectDto(organizationDto, "P2");
+    ComponentDto file3 = newFileDto(project2, null, "F3").setPath("src/main/xoo/org/sonar/samples/File3.xoo");
+
+    indexIssues(
+      // file F1 from project P1
+      newDoc("F1_1", file1).setLine(20).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("F1_2", file1).setLine(null).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("F1_3", file1).setLine(25).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+
+      // file F2 from project P1
+      newDoc("F2_1", file2).setLine(9).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      newDoc("F2_2", file2).setLine(109).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+      // two issues on the same line -> sort by key
+      newDoc("F2_3", file2).setLine(109).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")),
+
+      // file F3 from project P2
+      newDoc("F3_1", file3).setLine(20).setFuncCreationDate(parseDateTime("2014-09-24T00:00:00+0100")),
+      newDoc("F3_2", file3).setLine(20).setFuncCreationDate(parseDateTime("2014-09-23T00:00:00+0100")));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "F3_1", "F1_2", "F1_1", "F1_3", "F2_1", "F2_2", "F2_3", "F3_2");
+  }
+
+  @Test
+  public void authorized_issues_on_groups() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(org);
+    ComponentDto project2 = newPrivateProjectDto(org);
+    ComponentDto project3 = newPrivateProjectDto(org);
+    ComponentDto file1 = newFileDto(project1, null);
+    ComponentDto file2 = newFileDto(project2, null);
+    ComponentDto file3 = newFileDto(project3, null);
+    GroupDto group1 = newGroupDto();
+    GroupDto group2 = newGroupDto();
+
+    // project1 can be seen by group1
+    indexIssue(newDoc("I1", file1));
+    authorizationIndexerTester.allowOnlyGroup(project1, group1);
+    // project2 can be seen by group2
+    indexIssue(newDoc("I2", file2));
+    authorizationIndexerTester.allowOnlyGroup(project2, group2);
+    // project3 can be seen by nobody
+    indexIssue(newDoc("I3", file3));
+
+    userSessionRule.logIn().setGroups(group1);
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
+
+    userSessionRule.logIn().setGroups(group2);
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I2");
+
+    userSessionRule.logIn().setGroups(group1, group2);
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1", "I2");
+
+    GroupDto otherGroup = newGroupDto();
+    userSessionRule.logIn().setGroups(otherGroup);
+    assertThatSearchReturnsEmpty(IssueQuery.builder());
+
+    userSessionRule.logIn().setGroups(group1, group2);
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project3.uuid())));
+  }
+
+  @Test
+  public void authorized_issues_on_user() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(org);
+    ComponentDto project2 = newPrivateProjectDto(org);
+    ComponentDto project3 = newPrivateProjectDto(org);
+    ComponentDto file1 = newFileDto(project1, null);
+    ComponentDto file2 = newFileDto(project2, null);
+    ComponentDto file3 = newFileDto(project3, null);
+    UserDto user1 = newUserDto();
+    UserDto user2 = newUserDto();
+
+    // project1 can be seen by john, project2 by max, project3 cannot be seen by anyone
+    indexIssue(newDoc("I1", file1));
+    authorizationIndexerTester.allowOnlyUser(project1, user1);
+    indexIssue(newDoc("I2", file2));
+    authorizationIndexerTester.allowOnlyUser(project2, user2);
+    indexIssue(newDoc("I3", file3));
+
+    userSessionRule.logIn(user1);
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
+    assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(asList(project3.getDbKey())));
+
+    userSessionRule.logIn(user2);
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I2");
+
+    // another user
+    userSessionRule.logIn(newUserDto());
+    assertThatSearchReturnsEmpty(IssueQuery.builder());
+  }
+
+  @Test
+  public void root_user_is_authorized_to_access_all_issues() {
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    indexIssue(newDoc("I1", project));
+    userSessionRule.logIn().setRoot();
+
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1");
+  }
+
+  @Test
+  public void list_tags() {
+    RuleDefinitionDto r1 = db.rules().insert();
+    RuleDefinitionDto r2 = db.rules().insert();
+    ruleIndexer.commitAndIndex(db.getSession(), asList(r1.getId(), r2.getId()));
+
+    OrganizationDto org = db.organizations().insert();
+    OrganizationDto anotherOrg = db.organizations().insert();
+    ComponentDto project = newPrivateProjectDto(newOrganizationDto());
+    ComponentDto file = newFileDto(project, null);
+    indexIssues(
+      newDoc("I42", file).setOrganizationUuid(anotherOrg.getUuid()).setRuleId(r1.getId()).setTags(of("another")),
+      newDoc("I1", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention", "java8", "bug")),
+      newDoc("I2", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention", "bug")),
+      newDoc("I3", file).setOrganizationUuid(org.getUuid()).setRuleId(r2.getId()),
+      newDoc("I4", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention")));
+
+    assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug");
+    assertThat(underTest.listTags(org, null, 2)).containsOnly("bug", "convention");
+    assertThat(underTest.listTags(org, "vent", 100)).containsOnly("convention");
+    assertThat(underTest.listTags(org, null, 1)).containsOnly("bug");
+    assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug");
+    assertThat(underTest.listTags(org, "invalidRegexp[", 100)).isEmpty();
+    assertThat(underTest.listTags(null, null, 100)).containsExactlyInAnyOrder("another", "convention", "java8", "bug");
+  }
+
+  @Test
+  public void fail_to_list_tags_when_size_greater_than_500() {
+    OrganizationDto organization = db.organizations().insert();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Page size must be lower than or equals to 500");
+
+    underTest.listTags(organization, null, 501);
+  }
+
+  @Test
+  public void test_listAuthors() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("issue1", project).setAuthorLogin("luke.skywalker"),
+      newDoc("issue2", project).setAuthorLogin("luke@skywalker.name"),
+      newDoc("issue3", project).setAuthorLogin(null),
+      newDoc("issue4", project).setAuthorLogin("anakin@skywalker.name"));
+    IssueQuery query = IssueQuery.builder()
+      .checkAuthorization(false)
+      .build();
+
+    assertThat(underTest.listAuthors(query, null, 5)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name");
+    assertThat(underTest.listAuthors(query, null, 2)).containsExactly("anakin@skywalker.name", "luke.skywalker");
+    assertThat(underTest.listAuthors(query, "uke", 5)).containsExactly("luke.skywalker", "luke@skywalker.name");
+    assertThat(underTest.listAuthors(query, null, 1)).containsExactly("anakin@skywalker.name");
+    assertThat(underTest.listAuthors(query, null, Integer.MAX_VALUE)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name");
+  }
+
+  @Test
+  public void listAuthors_escapes_regexp_special_characters() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("issue1", project).setAuthorLogin("name++"));
+    IssueQuery query = IssueQuery.builder()
+      .checkAuthorization(false)
+      .build();
+
+    assertThat(underTest.listAuthors(query, "invalidRegexp[", 5)).isEmpty();
+    assertThat(underTest.listAuthors(query, "nam+", 5)).isEmpty();
+    assertThat(underTest.listAuthors(query, "name+", 5)).containsExactly("name++");
+    assertThat(underTest.listAuthors(query, ".*", 5)).isEmpty();
+  }
+
+  @Test
+  public void filter_by_organization() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto projectInOrg1 = newPrivateProjectDto(org1);
+    OrganizationDto org2 = newOrganizationDto();
+    ComponentDto projectInOrg2 = newPrivateProjectDto(org2);
+
+    indexIssues(newDoc("issueInOrg1", projectInOrg1), newDoc("issue1InOrg2", projectInOrg2), newDoc("issue2InOrg2", projectInOrg2));
+
+    verifyOrganizationFilter(org1.getUuid(), "issueInOrg1");
+    verifyOrganizationFilter(org2.getUuid(), "issue1InOrg2", "issue2InOrg2");
+    verifyOrganizationFilter("does_not_exist");
+  }
+
+  @Test
+  public void filter_by_organization_and_project() {
+    OrganizationDto org1 = newOrganizationDto();
+    ComponentDto projectInOrg1 = newPrivateProjectDto(org1);
+    OrganizationDto org2 = newOrganizationDto();
+    ComponentDto projectInOrg2 = newPrivateProjectDto(org2);
+
+    indexIssues(newDoc("issueInOrg1", projectInOrg1), newDoc("issue1InOrg2", projectInOrg2), newDoc("issue2InOrg2", projectInOrg2));
+
+    // no conflict
+    IssueQuery.Builder query = IssueQuery.builder().organizationUuid(org1.getUuid()).projectUuids(singletonList(projectInOrg1.uuid()));
+    assertThatSearchReturnsOnly(query, "issueInOrg1");
+
+    // conflict
+    query = IssueQuery.builder().organizationUuid(org1.getUuid()).projectUuids(singletonList(projectInOrg2.uuid()));
+    assertThatSearchReturnsEmpty(query);
+  }
+
+  @Test
+  public void countTags() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("issue1", project).setTags(ImmutableSet.of("convention", "java8", "bug")),
+      newDoc("issue2", project).setTags(ImmutableSet.of("convention", "bug")),
+      newDoc("issue3", project).setTags(emptyList()),
+      newDoc("issue4", project).setTags(ImmutableSet.of("convention", "java8", "bug")).setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("issue5", project).setTags(ImmutableSet.of("convention")));
+
+    assertThat(underTest.countTags(projectQuery(project.uuid()), 5)).containsOnly(entry("convention", 3L), entry("bug", 2L), entry("java8", 1L));
+    assertThat(underTest.countTags(projectQuery(project.uuid()), 2)).contains(entry("convention", 3L), entry("bug", 2L)).doesNotContainEntry("java8", 1L);
+    assertThat(underTest.countTags(projectQuery("other"), 10)).isEmpty();
+  }
+
+  @Test
+  public void searchBranchStatistics() {
+    ComponentDto project = db.components().insertMainBranch();
+    ComponentDto branch1 = db.components().insertProjectBranch(project);
+    ComponentDto branch2 = db.components().insertProjectBranch(project);
+    ComponentDto branch3 = db.components().insertProjectBranch(project);
+    ComponentDto fileOnBranch3 = db.components().insertComponent(newFileDto(branch3));
+    indexIssues(newDoc(project),
+      newDoc(branch1).setType(BUG).setResolution(null), newDoc(branch1).setType(VULNERABILITY).setResolution(null), newDoc(branch1).setType(CODE_SMELL).setResolution(null),
+      newDoc(branch1).setType(CODE_SMELL).setResolution(RESOLUTION_FIXED),
+      newDoc(branch3).setType(CODE_SMELL).setResolution(null), newDoc(branch3).setType(CODE_SMELL).setResolution(null),
+      newDoc(fileOnBranch3).setType(CODE_SMELL).setResolution(null), newDoc(fileOnBranch3).setType(CODE_SMELL).setResolution(RESOLUTION_FIXED));
+
+    List<BranchStatistics> branchStatistics = underTest.searchBranchStatistics(project.uuid(), asList(branch1.uuid(), branch2.uuid(), branch3.uuid()));
+
+    assertThat(branchStatistics).extracting(BranchStatistics::getBranchUuid, BranchStatistics::getBugs, BranchStatistics::getVulnerabilities, BranchStatistics::getCodeSmells)
+      .containsExactlyInAnyOrder(
+        tuple(branch1.uuid(), 1L, 1L, 1L),
+        tuple(branch3.uuid(), 0L, 0L, 3L));
+  }
+
+  @Test
+  public void searchBranchStatistics_on_many_branches() {
+    ComponentDto project = db.components().insertMainBranch();
+    List<String> branchUuids = new ArrayList<>();
+    List<Tuple> expectedResult = new ArrayList<>();
+    IntStream.range(0, 15).forEach(i -> {
+      ComponentDto branch = db.components().insertProjectBranch(project);
+      addIssues(branch, 1 + i, 2 + i, 3 + i);
+      expectedResult.add(tuple(branch.uuid(), 1L + i, 2L + i, 3L + i));
+      branchUuids.add(branch.uuid());
+    });
+
+    List<BranchStatistics> branchStatistics = underTest.searchBranchStatistics(project.uuid(), branchUuids);
+
+    assertThat(branchStatistics)
+      .extracting(BranchStatistics::getBranchUuid, BranchStatistics::getBugs, BranchStatistics::getVulnerabilities, BranchStatistics::getCodeSmells)
+      .hasSize(15)
+      .containsAll(expectedResult);
+  }
+
+  @Test
+  public void searchBranchStatistics_on_empty_list() {
+    ComponentDto project = db.components().insertMainBranch();
+
+    assertThat(underTest.searchBranchStatistics(project.uuid(), emptyList())).isEmpty();
+    assertThat(underTest.searchBranchStatistics(project.uuid(), singletonList("unknown"))).isEmpty();
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    ComponentDto another = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
+      newDoc("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+      .contains(
+        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
+
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_dont_count_closed_vulnerabilities() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("openvul1", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
+      newDoc("notopenvul", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
+        .setSeverity(Severity.BLOCKER));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+      .contains(
+        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_dont_count_old_vulnerabilities() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
+      newDoc("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+      .containsOnly(
+        tuple(0L, OptionalInt.empty()));
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    ComponentDto another = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
+      newDoc("anotherProject", another).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
+      .contains(
+        tuple("a1", 1L /* openhotspot1 */));
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_dont_count_closed_hotspots() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
+      newDoc("closedHotspot", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
+        .setResolution(Issue.RESOLUTION_FIXED));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
+      .contains(
+        tuple("a1", 1L /* openhotspot1 */));
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_aggregation_no_cwe() {
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
+
+    assertThat(owaspTop10Report).allMatch(category -> category.getChildren().isEmpty());
+  }
+
+  @Test
+  public void test_getOwaspTop10Report_aggregation_with_cwe() {
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
+
+    Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
+      .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
+
+    assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+      SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+      SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+      .containsExactlyInAnyOrder(
+        tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+        tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+        tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
+    assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+      SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+      SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+      .containsExactlyInAnyOrder(
+        tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+        tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 1L /* toReviewHotspot */, 0L),
+        tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
+  }
+
+  private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    ComponentDto another = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.CRITICAL),
+      newDoc("openhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_OPEN),
+      newDoc("openhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REOPENED),
+      newDoc("toReviewHotspot", project).setOwaspTop10(asList("a5", "a3")).setCwe(asList("456")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("WFHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_WONT_FIX),
+      newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+    List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe);
+    assertThat(owaspTop10Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+      .containsExactlyInAnyOrder(
+        tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
+        tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+        tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */, 1L /* WFHotspot */),
+        tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+        tuple("a5", 0L, OptionalInt.empty(), 0L, 1L/* toReviewHotspot */, 0L),
+        tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* openhotspot2 */, 0L, 0L),
+        tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+        tuple("a8", 0L, OptionalInt.empty(), 0L, 0L, 1L /* WFHotspot */),
+        tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+        tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+        tuple("unknown", 1L /* notowaspvul */, OptionalInt.of(4) /* CRITICAL = D */, 1L /* notowasphotspot */, 0L, 0L));
+    return owaspTop10Report;
+  }
+
+  @Test
+  public void test_getSansTop25Report_aggregation() {
+    OrganizationDto org = newOrganizationDto();
+    ComponentDto project = newPrivateProjectDto(org);
+    indexIssues(
+      newDoc("openvul1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("notopenvul", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
+        .setResolution(Issue.RESOLUTION_FIXED)
+        .setSeverity(Severity.BLOCKER),
+      newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.CRITICAL),
+      newDoc("openhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_OPEN),
+      newDoc("openhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_REOPENED),
+      newDoc("toReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("WFHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_WONT_FIX),
+      newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+    List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false);
+    assertThat(sansTop25Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+      .containsExactlyInAnyOrder(
+        tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
+        tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */,
+          1L /* WFHotspot */),
+        tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L, 0L));
+
+    assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
+  }
+
+  @Test
+  public void test_getSansTop25Report_aggregation_on_portfolio() {
+    ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    indexIssues(
+      newDoc("openvul1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.MAJOR),
+      newDoc("openvul2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+        .setSeverity(Severity.MINOR),
+      newDoc("notopenvul", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
+        .setResolution(Issue.RESOLUTION_FIXED)
+        .setSeverity(Severity.BLOCKER),
+      newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+        .setSeverity(Severity.CRITICAL),
+      newDoc("openhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_OPEN),
+      newDoc("openhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
+        .setStatus(Issue.STATUS_REOPENED),
+      newDoc("toReviewHotspot", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_FIXED),
+      newDoc("WFHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+        .setResolution(Issue.RESOLUTION_WONT_FIX),
+      newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
+    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
+
+    List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(portfolio1.uuid(), true, false);
+    assertThat(sansTop25Report)
+      .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+        SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+        SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+      .containsExactlyInAnyOrder(
+        tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
+        tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* openhotspot1 */, 1L /* toReviewHotspot */, 0L),
+        tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L, 0L));
+
+    assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
+  }
+
+  private void addIssues(ComponentDto component, int bugs, int vulnerabilities, int codeSmelles) {
+    List<IssueDoc> issues = new ArrayList<>();
+    IntStream.range(0, bugs).forEach(b -> issues.add(newDoc(component).setType(BUG).setResolution(null)));
+    IntStream.range(0, vulnerabilities).forEach(v -> issues.add(newDoc(component).setType(VULNERABILITY).setResolution(null)));
+    IntStream.range(0, codeSmelles).forEach(c -> issues.add(newDoc(component).setType(CODE_SMELL).setResolution(null)));
+    indexIssues(issues.toArray(new IssueDoc[issues.size()]));
+  }
+
+  private IssueQuery projectQuery(String projectUuid) {
+    return IssueQuery.builder().projectUuids(singletonList(projectUuid)).resolved(false).build();
+  }
+
+  private void verifyOrganizationFilter(String organizationUuid, String... expectedIssueKeys) {
+    IssueQuery.Builder query = IssueQuery.builder().organizationUuid(organizationUuid);
+    assertThatSearchReturnsOnly(query, expectedIssueKeys);
+  }
+
+  private void indexIssues(IssueDoc... issues) {
+    issueIndexer.index(asList(issues).iterator());
+    for (IssueDoc issue : issues) {
+      IndexPermissions access = new IndexPermissions(issue.projectUuid(), "TRK");
+      access.allowAnyone();
+      authorizationIndexerTester.allow(access);
+    }
+  }
+
+  private void indexIssue(IssueDoc issue) {
+    issueIndexer.index(Iterators.singletonIterator(issue));
+  }
+
+  private void indexView(String viewUuid, List<String> projects) {
+    viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
+  }
+
+  /**
+   * Execute the search request and return the document ids of results.
+   */
+  private List<String> searchAndReturnKeys(IssueQuery.Builder query) {
+    return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits())
+      .map(SearchHit::getId)
+      .collect(Collectors.toList());
+  }
+
+  private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) {
+    List<String> keys = searchAndReturnKeys(query);
+    assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys);
+  }
+
+  private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) {
+    List<String> keys = searchAndReturnKeys(query);
+    assertThat(keys).isEmpty();
+  }
+
+  private void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
+    SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
+    Facets facets = new Facets(result, system2.getDefaultTimeZone());
+    assertThat(facets.getNames()).containsOnly(facet);
+    assertThat(facets.get(facet)).containsExactly(expectedEntries);
+  }
+
+  private void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
+    SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
+    Facets facets = new Facets(result, system2.getDefaultTimeZone());
+    assertThat(facets.getNames()).containsOnly(facet);
+    assertThat(facets.get(facet)).containsOnly(expectedEntries);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
new file mode 100644 (file)
index 0000000..4051092
--- /dev/null
@@ -0,0 +1,549 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDbTester;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.SearchRequest;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.addDays;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+import static org.sonar.db.component.ComponentTesting.newProjectCopy;
+import static org.sonar.db.component.ComponentTesting.newSubView;
+import static org.sonar.db.rule.RuleTesting.newRule;
+
+public class IssueQueryFactoryTest {
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private RuleDbTester ruleDbTester = new RuleDbTester(db);
+
+  private Clock clock = mock(Clock.class);
+  private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), clock, userSession);
+
+  @Test
+  public void create_from_parameters() {
+    UserDto user = db.users().insertUser(u -> u.setLogin("joanna"));
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto module = db.components().insertComponent(newModuleDto(project));
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    RuleDefinitionDto rule1 = ruleDbTester.insert();
+    RuleDefinitionDto rule2 = ruleDbTester.insert();
+    newRule(RuleKey.of("findbugs", "NullReference"));
+    SearchRequest request = new SearchRequest()
+      .setIssues(asList("anIssueKey"))
+      .setSeverities(asList("MAJOR", "MINOR"))
+      .setStatuses(asList("CLOSED"))
+      .setResolutions(asList("FALSE-POSITIVE"))
+      .setResolved(true)
+      .setProjectKeys(asList(project.getDbKey()))
+      .setModuleUuids(asList(module.uuid()))
+      .setDirectories(asList("aDirPath"))
+      .setFileUuids(asList(file.uuid()))
+      .setAssigneesUuid(asList(user.getUuid()))
+      .setLanguages(asList("xoo"))
+      .setTags(asList("tag1", "tag2"))
+      .setOrganization(organization.getKey())
+      .setAssigned(true)
+      .setCreatedAfter("2013-04-16T09:08:24+0200")
+      .setCreatedBefore("2013-04-17T09:08:24+0200")
+      .setRules(asList(rule1.getKey().toString(), rule2.getKey().toString()))
+      .setSort("CREATION_DATE")
+      .setAsc(true);
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.issueKeys()).containsOnly("anIssueKey");
+    assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
+    assertThat(query.statuses()).containsOnly("CLOSED");
+    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
+    assertThat(query.resolved()).isTrue();
+    assertThat(query.projectUuids()).containsOnly(project.uuid());
+    assertThat(query.moduleUuids()).containsOnly(module.uuid());
+    assertThat(query.fileUuids()).containsOnly(file.uuid());
+    assertThat(query.assignees()).containsOnly(user.getUuid());
+    assertThat(query.languages()).containsOnly("xoo");
+    assertThat(query.tags()).containsOnly("tag1", "tag2");
+    assertThat(query.organizationUuid()).isEqualTo(organization.getUuid());
+    assertThat(query.onComponentOnly()).isFalse();
+    assertThat(query.assigned()).isTrue();
+    assertThat(query.rules()).hasSize(2);
+    assertThat(query.directories()).containsOnly("aDirPath");
+    assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+    assertThat(query.createdAfter().inclusive()).isTrue();
+    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
+    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
+    assertThat(query.asc()).isTrue();
+  }
+
+  @Test
+  public void leak_period_start_date_is_exclusive() {
+    long leakPeriodStart = addDays(new Date(), -14).getTime();
+
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    SnapshotDto analysis = db.components().insertSnapshot(project, s -> s.setPeriodDate(leakPeriodStart));
+
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(Collections.singletonList(file.uuid()))
+      .setOnComponentOnly(true)
+      .setSinceLeakPeriod(true);
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.componentUuids()).containsOnly(file.uuid());
+    assertThat(query.createdAfter().date()).isEqualTo(new Date(leakPeriodStart));
+    assertThat(query.createdAfter().inclusive()).isFalse();
+
+  }
+
+  @Test
+  public void dates_are_inclusive() {
+    SearchRequest request = new SearchRequest()
+      .setCreatedAfter("2013-04-16")
+      .setCreatedBefore("2013-04-17");
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+    assertThat(query.createdAfter().inclusive()).isTrue();
+    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
+  }
+
+  @Test
+  public void creation_date_support_localdate() {
+    SearchRequest request = new SearchRequest()
+      .setCreatedAt("2013-04-16");
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.createdAt()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+  }
+
+  @Test
+  public void creation_date_support_zoneddatetime() {
+    SearchRequest request = new SearchRequest()
+      .setCreatedAt("2013-04-16T09:08:24+0200");
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.createdAt()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+  }
+
+  @Test
+  public void add_unknown_when_no_component_found() {
+    SearchRequest request = new SearchRequest()
+      .setComponentKeys(asList("does_not_exist"));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.componentUuids()).containsOnly("<UNKNOWN>");
+  }
+
+  @Test
+  public void query_without_any_parameter() {
+    SearchRequest request = new SearchRequest();
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.componentUuids()).isEmpty();
+    assertThat(query.projectUuids()).isEmpty();
+    assertThat(query.moduleUuids()).isEmpty();
+    assertThat(query.moduleRootUuids()).isEmpty();
+    assertThat(query.directories()).isEmpty();
+    assertThat(query.fileUuids()).isEmpty();
+    assertThat(query.viewUuids()).isEmpty();
+    assertThat(query.organizationUuid()).isNull();
+    assertThat(query.branchUuid()).isNull();
+  }
+
+  @Test
+  public void fail_if_components_and_components_uuid_params_are_set_at_the_same_time() {
+    SearchRequest request = new SearchRequest()
+      .setComponentKeys(asList("foo"))
+      .setComponentUuids(asList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.create(request);
+  }
+
+  @Test
+  public void fail_if_both_projects_and_projectUuids_params_are_set() {
+    SearchRequest request = new SearchRequest()
+      .setProjectKeys(asList("foo"))
+      .setProjectUuids(asList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Parameters projects and projectUuids cannot be set simultaneously");
+
+    underTest.create(request);
+  }
+
+  @Test
+  public void fail_if_both_componentRoots_and_componentRootUuids_params_are_set() {
+    SearchRequest request = new SearchRequest()
+      .setComponentRoots(asList("foo"))
+      .setComponentRootUuids(asList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.create(request);
+  }
+
+  @Test
+  public void fail_if_componentRoots_references_components_with_different_qualifier() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    SearchRequest request = new SearchRequest()
+      .setComponentRoots(asList(project.getDbKey(), file.getDbKey()));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("All components must have the same qualifier, found FIL,TRK");
+
+    underTest.create(request);
+  }
+
+  @Test
+  public void param_componentRootUuids_enables_search_in_view_tree_if_user_has_permission_on_view() {
+    ComponentDto view = db.components().insertView();
+    SearchRequest request = new SearchRequest()
+      .setComponentRootUuids(asList(view.uuid()));
+    userSession.registerComponents(view);
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.viewUuids()).containsOnly(view.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void application_search_project_issues() {
+    ComponentDto project1 = db.components().insertPublicProject();
+    ComponentDto project2 = db.components().insertPublicProject();
+    ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
+    db.components().insertComponents(newProjectCopy("PC1", project1, application));
+    db.components().insertComponents(newProjectCopy("PC2", project2, application));
+    userSession.registerComponents(application, project1, project2);
+
+    IssueQuery result = underTest.create(new SearchRequest().setComponentUuids(singletonList(application.uuid())));
+
+    assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
+  }
+
+  @Test
+  public void application_search_project_issues_on_leak() {
+    Date now = new Date();
+    ComponentDto project1 = db.components().insertPublicProject();
+    SnapshotDto analysis1 = db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
+    ComponentDto project2 = db.components().insertPublicProject();
+    SnapshotDto analysis2 = db.components().insertSnapshot(project2, s -> s.setPeriodDate(null));
+    ComponentDto project3 = db.components().insertPublicProject();
+    ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
+    db.components().insertComponents(newProjectCopy("PC1", project1, application));
+    db.components().insertComponents(newProjectCopy("PC2", project2, application));
+    db.components().insertComponents(newProjectCopy("PC3", project3, application));
+    userSession.registerComponents(application, project1, project2, project3);
+
+    IssueQuery result = underTest.create(new SearchRequest()
+      .setComponentUuids(singletonList(application.uuid()))
+      .setSinceLeakPeriod(true));
+
+    assertThat(result.createdAfterByProjectUuids()).hasSize(1);
+    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).date().getTime()).isEqualTo(analysis1.getPeriodDate());
+    assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).inclusive()).isFalse();
+    assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
+  }
+
+  @Test
+  public void return_empty_results_if_not_allowed_to_search_for_subview() {
+    ComponentDto view = db.components().insertView();
+    ComponentDto subView = db.components().insertComponent(newSubView(view));
+    SearchRequest request = new SearchRequest()
+      .setComponentRootUuids(asList(subView.uuid()));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.viewUuids()).containsOnly("<UNKNOWN>");
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_on_project_tree_by_default() {
+    ComponentDto project = db.components().insertPrivateProject();
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(asList(project.uuid()));
+
+    IssueQuery query = underTest.create(request);
+    assertThat(query.projectUuids()).containsExactly(project.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void onComponentOnly_restricts_search_to_specified_componentKeys() {
+    ComponentDto project = db.components().insertPrivateProject();
+    SearchRequest request = new SearchRequest()
+      .setComponentKeys(asList(project.getDbKey()))
+      .setOnComponentOnly(true);
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.projectUuids()).isEmpty();
+    assertThat(query.componentUuids()).containsExactly(project.uuid());
+    assertThat(query.onComponentOnly()).isTrue();
+  }
+
+  @Test
+  public void should_search_in_tree_with_module_uuid() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto module = db.components().insertComponent(newModuleDto(project));
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(asList(module.uuid()));
+
+    IssueQuery query = underTest.create(request);
+    assertThat(query.moduleRootUuids()).containsExactly(module.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_in_directory_tree() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto dir = db.components().insertComponent(newDirectory(project, "src/main/java/foo"));
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(asList(dir.uuid()));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.moduleUuids()).containsOnly(dir.moduleUuid());
+    assertThat(query.directories()).containsOnly(dir.path());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_by_file() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(asList(file.uuid()));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.fileUuids()).containsExactly(file.uuid());
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_by_test_file() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
+    SearchRequest request = new SearchRequest()
+      .setComponentUuids(asList(file.uuid()));
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.fileUuids()).containsExactly(file.uuid());
+  }
+
+  @Test
+  public void search_issue_from_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    assertThat(underTest.create(new SearchRequest()
+      .setProjectKeys(singletonList(branch.getKey()))
+      .setBranch(branch.getBranch())))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
+
+    assertThat(underTest.create(new SearchRequest()
+      .setComponentKeys(singletonList(branch.getKey()))
+      .setBranch(branch.getBranch())))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(project.uuid()), false);
+  }
+
+  @Test
+  public void search_file_issue_from_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    ComponentDto file = db.components().insertComponent(newFileDto(branch));
+
+    assertThat(underTest.create(new SearchRequest()
+      .setComponentKeys(singletonList(file.getKey()))
+      .setBranch(branch.getBranch())))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+
+    assertThat(underTest.create(new SearchRequest()
+      .setComponentKeys(singletonList(branch.getKey()))
+      .setFileUuids(singletonList(file.uuid()))
+      .setBranch(branch.getBranch())))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+
+    assertThat(underTest.create(new SearchRequest()
+      .setProjectKeys(singletonList(branch.getKey()))
+      .setFileUuids(singletonList(file.uuid()))
+      .setBranch(branch.getBranch())))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+  }
+
+  @Test
+  public void search_issue_on_component_only_from_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    ComponentDto file = db.components().insertComponent(newFileDto(branch));
+
+    assertThat(underTest.create(new SearchRequest()
+      .setComponentKeys(singletonList(file.getKey()))
+      .setBranch(branch.getBranch())
+      .setOnComponentOnly(true)))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch)
+        .containsOnly(branch.uuid(), singletonList(file.uuid()), false);
+  }
+
+  @Test
+  public void search_issues_from_main_branch() {
+    ComponentDto project = db.components().insertMainBranch();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    assertThat(underTest.create(new SearchRequest()
+      .setProjectKeys(singletonList(project.getKey()))
+      .setBranch("master")))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
+    assertThat(underTest.create(new SearchRequest()
+      .setComponentKeys(singletonList(project.getKey()))
+      .setBranch("master")))
+        .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch)
+        .containsOnly(project.uuid(), singletonList(project.uuid()), true);
+  }
+
+  @Test
+  public void fail_if_created_after_and_created_since_are_both_set() {
+    SearchRequest request = new SearchRequest()
+      .setCreatedAfter("2013-07-25T07:35:00+0100")
+      .setCreatedInLast("palap");
+
+    try {
+      underTest.create(request);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Parameters createdAfter and createdInLast cannot be set simultaneously");
+    }
+  }
+
+  @Test
+  public void set_created_after_from_created_since() {
+    Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100");
+    when(clock.instant()).thenReturn(now.toInstant());
+    when(clock.getZone()).thenReturn(ZoneOffset.UTC);
+    SearchRequest request = new SearchRequest()
+      .setCreatedInLast("1y2m3w4d");
+    assertThat(underTest.create(request).createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
+    assertThat(underTest.create(request).createdAfter().inclusive()).isTrue();
+
+  }
+
+  @Test
+  public void fail_if_since_leak_period_and_created_after_set_at_the_same_time() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Parameters 'createdAfter' and 'sinceLeakPeriod' cannot be set simultaneously");
+
+    underTest.create(new SearchRequest()
+      .setSinceLeakPeriod(true)
+      .setCreatedAfter("2013-07-25T07:35:00+0100"));
+  }
+
+  @Test
+  public void fail_if_no_component_provided_with_since_leak_period() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
+
+    underTest.create(new SearchRequest().setSinceLeakPeriod(true));
+  }
+
+  @Test
+  public void fail_if_several_components_provided_with_since_leak_period() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
+
+    underTest.create(new SearchRequest()
+      .setSinceLeakPeriod(true)
+      .setComponentKeys(asList(project1.getKey(), project2.getKey())));
+  }
+
+  @Test
+  public void fail_if_date_is_not_formatted_correctly() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("'unknown-date' cannot be parsed as either a date or date+time");
+
+    underTest.create(new SearchRequest()
+      .setCreatedAfter("unknown-date"));
+  }
+
+  @Test
+  public void return_empty_results_if_organization_with_specified_key_does_not_exist() {
+    SearchRequest request = new SearchRequest()
+      .setOrganization("does_not_exist");
+
+    IssueQuery query = underTest.create(request);
+
+    assertThat(query.organizationUuid()).isEqualTo("<UNKNOWN>");
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
new file mode 100644 (file)
index 0000000..4314de1
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.Date;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.Severity;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.issue.index.IssueQuery.PeriodStart;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+
+public class IssueQueryTest {
+
+  @Test
+  public void build_query() {
+    RuleDefinitionDto rule = new RuleDefinitionDto().setId(nextInt(1000));
+    PeriodStart filterDate = new IssueQuery.PeriodStart(new Date(10_000_000_000L), false);
+    IssueQuery query = IssueQuery.builder()
+      .issueKeys(newArrayList("ABCDE"))
+      .severities(newArrayList(Severity.BLOCKER))
+      .statuses(Lists.newArrayList(Issue.STATUS_RESOLVED))
+      .resolutions(newArrayList(Issue.RESOLUTION_FALSE_POSITIVE))
+      .projectUuids(newArrayList("PROJECT"))
+      .componentUuids(newArrayList("org/struts/Action.java"))
+      .moduleUuids(newArrayList("org.struts:core"))
+      .rules(newArrayList(rule))
+      .assigneeUuids(newArrayList("gargantua"))
+      .languages(newArrayList("xoo"))
+      .tags(newArrayList("tag1", "tag2"))
+      .types(newArrayList("RELIABILITY", "SECURITY"))
+      .owaspTop10(newArrayList("a1", "a2"))
+      .sansTop25(newArrayList("insecure-interaction", "porous-defenses"))
+      .cwe(newArrayList("12", "125"))
+      .organizationUuid("orga")
+      .branchUuid("my_branch")
+      .createdAfterByProjectUuids(ImmutableMap.of("PROJECT", filterDate))
+      .assigned(true)
+      .createdAfter(new Date())
+      .createdBefore(new Date())
+      .createdAt(new Date())
+      .resolved(true)
+      .sort(IssueQuery.SORT_BY_CREATION_DATE)
+      .asc(true)
+      .build();
+    assertThat(query.issueKeys()).containsOnly("ABCDE");
+    assertThat(query.severities()).containsOnly(Severity.BLOCKER);
+    assertThat(query.statuses()).containsOnly(Issue.STATUS_RESOLVED);
+    assertThat(query.resolutions()).containsOnly(Issue.RESOLUTION_FALSE_POSITIVE);
+    assertThat(query.projectUuids()).containsOnly("PROJECT");
+    assertThat(query.componentUuids()).containsOnly("org/struts/Action.java");
+    assertThat(query.moduleUuids()).containsOnly("org.struts:core");
+    assertThat(query.assignees()).containsOnly("gargantua");
+    assertThat(query.languages()).containsOnly("xoo");
+    assertThat(query.tags()).containsOnly("tag1", "tag2");
+    assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
+    assertThat(query.owaspTop10()).containsOnly("a1", "a2");
+    assertThat(query.sansTop25()).containsOnly("insecure-interaction", "porous-defenses");
+    assertThat(query.cwe()).containsOnly("12", "125");
+    assertThat(query.organizationUuid()).isEqualTo("orga");
+    assertThat(query.branchUuid()).isEqualTo("my_branch");
+    assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", filterDate));
+    assertThat(query.assigned()).isTrue();
+    assertThat(query.rules()).containsOnly(rule);
+    assertThat(query.createdAfter()).isNotNull();
+    assertThat(query.createdBefore()).isNotNull();
+    assertThat(query.createdAt()).isNotNull();
+    assertThat(query.resolved()).isTrue();
+    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
+    assertThat(query.asc()).isTrue();
+  }
+
+  @Test
+  public void build_query_without_dates() {
+    IssueQuery query = IssueQuery.builder()
+      .issueKeys(newArrayList("ABCDE"))
+      .createdAfter(null)
+      .createdBefore(null)
+      .createdAt(null)
+      .build();
+
+    assertThat(query.issueKeys()).containsOnly("ABCDE");
+    assertThat(query.createdAfter()).isNull();
+    assertThat(query.createdBefore()).isNull();
+    assertThat(query.createdAt()).isNull();
+  }
+
+  @Test
+  public void throw_exception_if_sort_is_not_valid() {
+    try {
+      IssueQuery.builder()
+        .sort("UNKNOWN")
+        .build();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Bad sort field: UNKNOWN");
+    }
+  }
+
+  @Test
+  public void collection_params_should_not_be_null_but_empty() {
+    IssueQuery query = IssueQuery.builder()
+      .issueKeys(null)
+      .projectUuids(null)
+      .componentUuids(null)
+      .moduleUuids(null)
+      .statuses(null)
+      .assigneeUuids(null)
+      .resolutions(null)
+      .rules(null)
+      .severities(null)
+      .languages(null)
+      .tags(null)
+      .types(null)
+      .owaspTop10(null)
+      .sansTop25(null)
+      .cwe(null)
+      .createdAfterByProjectUuids(null)
+      .build();
+    assertThat(query.issueKeys()).isEmpty();
+    assertThat(query.projectUuids()).isEmpty();
+    assertThat(query.componentUuids()).isEmpty();
+    assertThat(query.moduleUuids()).isEmpty();
+    assertThat(query.statuses()).isEmpty();
+    assertThat(query.assignees()).isEmpty();
+    assertThat(query.resolutions()).isEmpty();
+    assertThat(query.rules()).isEmpty();
+    assertThat(query.severities()).isEmpty();
+    assertThat(query.languages()).isEmpty();
+    assertThat(query.tags()).isEmpty();
+    assertThat(query.types()).isEmpty();
+    assertThat(query.owaspTop10()).isEmpty();
+    assertThat(query.sansTop25()).isEmpty();
+    assertThat(query.cwe()).isEmpty();
+    assertThat(query.createdAfterByProjectUuids()).isEmpty();
+  }
+
+  @Test
+  public void test_default_query() {
+    IssueQuery query = IssueQuery.builder().build();
+    assertThat(query.issueKeys()).isEmpty();
+    assertThat(query.projectUuids()).isEmpty();
+    assertThat(query.componentUuids()).isEmpty();
+    assertThat(query.moduleUuids()).isEmpty();
+    assertThat(query.statuses()).isEmpty();
+    assertThat(query.assignees()).isEmpty();
+    assertThat(query.resolutions()).isEmpty();
+    assertThat(query.rules()).isEmpty();
+    assertThat(query.severities()).isEmpty();
+    assertThat(query.languages()).isEmpty();
+    assertThat(query.tags()).isEmpty();
+    assertThat(query.types()).isEmpty();
+    assertThat(query.organizationUuid()).isNull();
+    assertThat(query.branchUuid()).isNull();
+    assertThat(query.assigned()).isNull();
+    assertThat(query.createdAfter()).isNull();
+    assertThat(query.createdBefore()).isNull();
+    assertThat(query.resolved()).isNull();
+    assertThat(query.sort()).isNull();
+    assertThat(query.createdAfterByProjectUuids()).isEmpty();
+  }
+
+  @Test
+  public void should_accept_null_sort() {
+    IssueQuery query = IssueQuery.builder().sort(null).build();
+    assertThat(query.sort()).isNull();
+  }
+}
index c26a64ce11ab26e151403d4b2fb9770e420330cb..0cb1ee4df4a65ce21943f2c89494e5d85b5df706 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 
@@ -47,7 +47,7 @@ public class AuthorsActionTest {
   public UserSessionRule userSession = UserSessionRule.standalone();
 
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
 
   private WsActionTester ws = new WsActionTester(new AuthorsAction(issueIndex));
 
index a18696cfd0a74c7b43f7b6b2987aba19be84574e..1438cffe5a6e2c3860dfb48d237fa7e0c116cea5 100644 (file)
@@ -26,8 +26,8 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.sonar.api.server.ws.WebService.Action;
 import org.sonar.api.server.ws.WebService.Param;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQuery;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
index 2a601ec3767b428275fde6f8acdd39d6dd0dfbac..3f88cfabadd7b9d6abe96c6623d8be10b17880b4 100644 (file)
@@ -39,15 +39,15 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.IssueFieldsSetter;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.TransitionService;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
 import org.sonar.server.issue.workflow.FunctionExecutor;
 import org.sonar.server.issue.workflow.IssueWorkflow;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.view.index.ViewIndexer;
 import org.sonar.server.ws.WsActionTester;
@@ -94,7 +94,7 @@ public class SearchActionComponentsTest {
   public EsTester es = EsTester.create();
 
   private DbClient dbClient = db.getDbClient();
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
   private ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client());
   private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession);
index 8a55f127529cda7b22e230733be2de5cee6d0f6a..dc5b66f172176f1fcd8c225a533e7977b41baaf8 100644 (file)
@@ -56,16 +56,16 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.StartupIndexer;
 import org.sonar.server.issue.IssueFieldsSetter;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueQuery;
+import org.sonar.server.issue.index.IssueQueryFactory;
 import org.sonar.server.issue.TransitionService;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
 import org.sonar.server.issue.workflow.FunctionExecutor;
 import org.sonar.server.issue.workflow.IssueWorkflow;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexer;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
@@ -109,7 +109,7 @@ public class SearchActionTest {
 
   private DbClient dbClient = db.getDbClient();
   private DbSession session = db.getSession();
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
   private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSessionRule);
   private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
index 8a4809ebda5ef355b7b36d980505fbcc428866a5..100910a2b9313d486ac23e2998839daa0a83cf70 100644 (file)
@@ -35,9 +35,9 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
+import org.sonar.server.permission.index.IndexPermissions;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.tester.UserSessionRule;
@@ -62,7 +62,7 @@ public class TagsActionTest {
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbTester.getDbClient(), new IssueIteratorFactory(dbTester.getDbClient()));
   private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient());
   private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer);
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
   private RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE);
 
   private WsActionTester ws = new WsActionTester(new TagsAction(issueIndex, ruleIndex, dbTester.getDbClient()));
@@ -254,7 +254,7 @@ public class TagsActionTest {
   }
 
   private void grantAccess(IssueDto issue) {
-    PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(issue.getProjectUuid(), "TRK");
+    IndexPermissions access = new IndexPermissions(issue.getProjectUuid(), "TRK");
     access.addUserId(userSession.getUserId());
     permissionIndexerTester.allow(access);
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
new file mode 100644 (file)
index 0000000..670ffb1
--- /dev/null
@@ -0,0 +1,1477 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
+import static org.sonar.api.measures.Metric.Level.ERROR;
+import static org.sonar.api.measures.Metric.Level.OK;
+import static org.sonar.api.measures.Metric.Level.WARN;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
+
+@RunWith(DataProviderRunner.class)
+public class ProjectMeasuresIndexTest {
+
+  private static final String MAINTAINABILITY_RATING = "sqale_rating";
+  private static final String NEW_MAINTAINABILITY_RATING_KEY = "new_maintainability_rating";
+  private static final String RELIABILITY_RATING = "reliability_rating";
+  private static final String NEW_RELIABILITY_RATING = "new_reliability_rating";
+  private static final String SECURITY_RATING = "security_rating";
+  private static final String NEW_SECURITY_RATING = "new_security_rating";
+  private static final String COVERAGE = "coverage";
+  private static final String NEW_COVERAGE = "new_coverage";
+  private static final String DUPLICATION = "duplicated_lines_density";
+  private static final String NEW_DUPLICATION = "new_duplicated_lines_density";
+  private static final String NCLOC = "ncloc";
+  private static final String NEW_LINES = "new_lines";
+  private static final String LANGUAGES = "languages";
+
+  private static final OrganizationDto ORG = OrganizationTesting.newOrganizationDto();
+  private static final ComponentDto PROJECT1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-1").setName("Project 1").setDbKey("key-1");
+  private static final ComponentDto PROJECT2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-2").setName("Project 2").setDbKey("key-2");
+  private static final ComponentDto PROJECT3 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-3").setName("Project 3").setDbKey("key-3");
+  private static final UserDto USER1 = newUserDto();
+  private static final UserDto USER2 = newUserDto();
+  private static final GroupDto GROUP1 = newGroupDto();
+  private static final GroupDto GROUP2 = newGroupDto();
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  @DataProvider
+  public static Object[][] rating_metric_keys() {
+    return new Object[][]{{MAINTAINABILITY_RATING}, {NEW_MAINTAINABILITY_RATING_KEY}, {RELIABILITY_RATING}, {NEW_RELIABILITY_RATING}, {SECURITY_RATING}, {NEW_SECURITY_RATING}};
+  }
+
+  private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, projectMeasureIndexer);
+  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+
+  @Test
+  public void return_empty_if_no_projects() {
+    assertNoResults(new ProjectMeasuresQuery());
+  }
+
+  @Test
+  public void default_sort_is_by_ascending_case_insensitive_name_then_by_key() {
+    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
+    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
+    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
+    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
+    index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
+
+    assertResults(new ProjectMeasuresQuery(), apache1, apache2, apachee, windows);
+  }
+
+  @Test
+  public void sort_by_insensitive_name() {
+    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows");
+    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee");
+    ComponentDto apache = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache").setName("Apache");
+    index(newDoc(windows), newDoc(apachee), newDoc(apache));
+
+    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(true), apache, apachee, windows);
+    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(false), windows, apachee, apache);
+  }
+
+  @Test
+  public void sort_by_ncloc() {
+    index(
+      newDoc(PROJECT1, NCLOC, 15_000d),
+      newDoc(PROJECT2, NCLOC, 30_000d),
+      newDoc(PROJECT3, NCLOC, 1_000d));
+
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), PROJECT3, PROJECT1, PROJECT2);
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), PROJECT2, PROJECT1, PROJECT3);
+  }
+
+  @Test
+  public void sort_by_a_metric_then_by_name_then_by_key() {
+    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
+    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
+    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
+    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
+    index(
+      newDoc(windows, NCLOC, 10_000d),
+      newDoc(apachee, NCLOC, 5_000d),
+      newDoc(apache1, NCLOC, 5_000d),
+      newDoc(apache2, NCLOC, 5_000d));
+
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), apache1, apache2, apachee, windows);
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), windows, apache1, apache2, apachee);
+  }
+
+  @Test
+  public void sort_by_quality_gate_status() {
+    ComponentDto project4 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-4").setName("Project 4").setDbKey("key-4");
+    index(
+      newDoc(PROJECT1).setQualityGateStatus(OK.name()),
+      newDoc(PROJECT2).setQualityGateStatus(ERROR.name()),
+      newDoc(PROJECT3).setQualityGateStatus(WARN.name()),
+      newDoc(project4).setQualityGateStatus(OK.name()));
+
+    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(true), PROJECT1, project4, PROJECT3, PROJECT2);
+    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), PROJECT2, PROJECT3, PROJECT1, project4);
+  }
+
+  @Test
+  public void sort_by_quality_gate_status_then_by_name_then_by_key() {
+    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
+    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
+    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
+    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
+    index(
+      newDoc(windows).setQualityGateStatus(WARN.name()),
+      newDoc(apachee).setQualityGateStatus(OK.name()),
+      newDoc(apache1).setQualityGateStatus(OK.name()),
+      newDoc(apache2).setQualityGateStatus(OK.name()));
+
+    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(true), apache1, apache2, apachee, windows);
+    assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), windows, apache1, apache2, apachee);
+  }
+
+  @Test
+  public void paginate_results() {
+    IntStream.rangeClosed(1, 9)
+      .forEach(i -> index(newDoc(newPrivateProjectDto(ORG, "P" + i))));
+
+    SearchIdResult<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().setPage(2, 3));
+
+    assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
+    assertThat(result.getTotal()).isEqualTo(9);
+  }
+
+  @Test
+  public void filter_with_lower_than() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
+      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 80d));
+
+    assertResults(query, PROJECT1);
+  }
+
+  @Test
+  public void filter_with_lower_than_or_equals() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
+      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LTE, 80d));
+
+    assertResults(query, PROJECT1, PROJECT2);
+  }
+
+  @Test
+  public void filter_with_greater_than() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
+      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 30_000d));
+    assertResults(query, PROJECT2, PROJECT3);
+
+    query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 100_000d));
+    assertNoResults(query);
+  }
+
+  @Test
+  public void filter_with_greater_than_or_equals() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
+      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GTE, 30_001d));
+    assertResults(query, PROJECT2, PROJECT3);
+
+    query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GTE, 100_000d));
+    assertNoResults(query);
+  }
+
+  @Test
+  public void filter_with_equals() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
+      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.EQ, 80d));
+
+    assertResults(query, PROJECT2);
+  }
+
+  @Test
+  public void filter_on_no_data_with_several_projects() {
+    index(
+      newDoc(PROJECT1, NCLOC, 1d),
+      newDoc(PROJECT2, DUPLICATION, 80d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery()
+      .addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
+
+    assertResults(query, PROJECT1);
+  }
+
+  @Test
+  public void filter_on_no_data_should_not_return_projects_with_data_and_other_measures() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
+    index(newDoc(project, DUPLICATION, 80d, NCLOC, 1d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
+
+    assertNoResults(query);
+  }
+
+  @Test
+  public void filter_on_no_data_should_not_return_projects_with_data() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
+    index(newDoc(project, DUPLICATION, 80d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
+
+    assertNoResults(query);
+  }
+
+  @Test
+  public void filter_on_no_data_should_return_projects_with_no_data() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(ORG);
+    index(newDoc(project, NCLOC, 1d));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(MetricCriterion.createNoData(DUPLICATION));
+
+    assertResults(query, project);
+  }
+
+  @Test
+  public void filter_on_several_metrics() {
+    index(
+      newDoc(PROJECT1, COVERAGE, 81d, NCLOC, 10_001d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_001d),
+      newDoc(PROJECT3, COVERAGE, 79d, NCLOC, 10_000d));
+
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
+      .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LTE, 80d))
+      .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.GT, 10_000d))
+      .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.LT, 11_000d));
+    assertResults(esQuery, PROJECT2);
+  }
+
+  @Test
+  public void filter_on_quality_gate_status() {
+    index(
+      newDoc(PROJECT1).setQualityGateStatus(OK.name()),
+      newDoc(PROJECT2).setQualityGateStatus(OK.name()),
+      newDoc(PROJECT3).setQualityGateStatus(WARN.name()));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().setQualityGateStatus(OK);
+    assertResults(query, PROJECT1, PROJECT2);
+  }
+
+  @Test
+  public void filter_on_languages() {
+    ComponentDto project4 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("Project-4").setName("Project 4").setDbKey("key-4");
+    index(
+      newDoc(PROJECT1).setLanguages(singletonList("java")),
+      newDoc(PROJECT2).setLanguages(singletonList("xoo")),
+      newDoc(PROJECT3).setLanguages(singletonList("xoo")),
+      newDoc(project4).setLanguages(asList("<null>", "java", "xoo")));
+
+    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("java", "xoo")), PROJECT1, PROJECT2, PROJECT3, project4);
+    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("java")), PROJECT1, project4);
+    assertResults(new ProjectMeasuresQuery().setLanguages(newHashSet("unknown")));
+  }
+
+  @Test
+  public void filter_on_query_text() {
+    ComponentDto windows = ComponentTesting.newPrivateProjectDto(ORG).setUuid("windows").setName("Windows").setDbKey("project1");
+    ComponentDto apachee = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apachee").setName("apachee").setDbKey("project2");
+    ComponentDto apache1 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-1").setName("Apache").setDbKey("project3");
+    ComponentDto apache2 = ComponentTesting.newPrivateProjectDto(ORG).setUuid("apache-2").setName("Apache").setDbKey("project4");
+    index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
+
+    assertResults(new ProjectMeasuresQuery().setQueryText("windows"), windows);
+    assertResults(new ProjectMeasuresQuery().setQueryText("project2"), apachee);
+    assertResults(new ProjectMeasuresQuery().setQueryText("pAch"), apache1, apache2, apachee);
+  }
+
+  @Test
+  public void filter_on_ids() {
+    index(
+      newDoc(PROJECT1),
+      newDoc(PROJECT2),
+      newDoc(PROJECT3));
+
+    ProjectMeasuresQuery query = new ProjectMeasuresQuery().setProjectUuids(newHashSet(PROJECT1.uuid(), PROJECT3.uuid()));
+    assertResults(query, PROJECT1, PROJECT3);
+  }
+
+  @Test
+  public void filter_on_tags() {
+    index(
+      newDoc(PROJECT1).setTags(newArrayList("finance", "platform")),
+      newDoc(PROJECT2).setTags(newArrayList("marketing", "platform")),
+      newDoc(PROJECT3).setTags(newArrayList("finance", "language")));
+
+    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance")), PROJECT1, PROJECT3);
+    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance", "language")), PROJECT1, PROJECT3);
+    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("finance", "marketing")), PROJECT1, PROJECT2, PROJECT3);
+    assertResults(new ProjectMeasuresQuery().setTags(newHashSet("marketing")), PROJECT2);
+    assertNoResults(new ProjectMeasuresQuery().setTags(newHashSet("tag 42")));
+  }
+
+  @Test
+  public void filter_on_organization() {
+    OrganizationDto org1 = OrganizationTesting.newOrganizationDto();
+    OrganizationDto org2 = OrganizationTesting.newOrganizationDto();
+    ComponentDto projectInOrg1 = ComponentTesting.newPrivateProjectDto(org1);
+    ComponentDto projectInOrg2 = ComponentTesting.newPrivateProjectDto(org2);
+    index(newDoc(projectInOrg1), newDoc(projectInOrg2));
+
+    ProjectMeasuresQuery query1 = new ProjectMeasuresQuery().setOrganizationUuid(org1.getUuid());
+    assertResults(query1, projectInOrg1);
+
+    ProjectMeasuresQuery query2 = new ProjectMeasuresQuery().setOrganizationUuid(org2.getUuid());
+    assertResults(query2, projectInOrg2);
+
+    ProjectMeasuresQuery query3 = new ProjectMeasuresQuery().setOrganizationUuid("another_org");
+    assertNoResults(query3);
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user() {
+    indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2));
+    indexForUser(USER2, newDoc(PROJECT3));
+
+    userSession.logIn(USER1);
+    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2);
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user_groups() {
+    indexForGroup(GROUP1, newDoc(PROJECT1), newDoc(PROJECT2));
+    indexForGroup(GROUP2, newDoc(PROJECT3));
+
+    userSession.logIn().setGroups(GROUP1);
+    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2);
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user_and_groups() {
+    indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2));
+    indexForGroup(GROUP1, newDoc(PROJECT3));
+
+    userSession.logIn(USER1).setGroups(GROUP1);
+    assertResults(new ProjectMeasuresQuery(), PROJECT1, PROJECT2, PROJECT3);
+  }
+
+  @Test
+  public void anonymous_user_can_only_access_projects_authorized_for_anyone() {
+    index(newDoc(PROJECT1));
+    indexForUser(USER1, newDoc(PROJECT2));
+
+    userSession.anonymous();
+    assertResults(new ProjectMeasuresQuery(), PROJECT1);
+  }
+
+  @Test
+  public void root_user_can_access_all_projects() {
+    indexForUser(USER1, newDoc(PROJECT1));
+    // connecting with a root but not USER1
+    userSession.logIn().setRoot();
+
+    assertResults(new ProjectMeasuresQuery(), PROJECT1);
+  }
+
+  @Test
+  public void does_not_return_facet_when_no_facets_in_options() {
+    index(
+      newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
+        .setQualityGateStatus(OK.name()));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getFacets();
+
+    assertThat(facets.getAll()).isEmpty();
+  }
+
+  @Test
+  public void facet_ncloc() {
+    index(
+      // 3 docs with ncloc<1K
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 999d),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d),
+      newDoc(NCLOC, 9_999d),
+      // 4 docs with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 10_000d),
+      newDoc(NCLOC, 10_000d),
+      newDoc(NCLOC, 11_000d),
+      newDoc(NCLOC, 99_000d),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 100_000d),
+      newDoc(NCLOC, 499_000d),
+      // 5 docs with ncloc>= 500K
+      newDoc(NCLOC, 500_000d),
+      newDoc(NCLOC, 100_000_000d),
+      newDoc(NCLOC, 500_000d),
+      newDoc(NCLOC, 1_000_000d),
+      newDoc(NCLOC, 100_000_000_000d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
+
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 4L),
+      entry("100000.0-500000.0", 2L),
+      entry("500000.0-*", 5L));
+  }
+
+  @Test
+  public void facet_ncloc_is_sticky() {
+    index(
+      // 1 docs with ncloc<1K
+      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
+      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
+      // 3 docs with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
+      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
+      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
+      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 0d),
+      // 1 docs with ncloc>= 500K
+      newDoc(NCLOC, 501_000d, COVERAGE, 81d, DUPLICATION, 20d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .addMetricCriterion(MetricCriterion.create(NCLOC, Operator.LT, 10_000d))
+        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d)),
+      new SearchOptions().addFacets(NCLOC, COVERAGE)).getFacets();
+
+    // Sticky facet on ncloc does not take into account ncloc filter
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 3L),
+      entry("100000.0-500000.0", 2L),
+      entry("500000.0-*", 0L));
+    // But facet on coverage does well take into into filters
+    assertThat(facets.get(COVERAGE)).containsOnly(
+      entry("NO_DATA", 0L),
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 0L),
+      entry("50.0-70.0", 0L),
+      entry("70.0-80.0", 0L),
+      entry("80.0-*", 0L));
+  }
+
+  @Test
+  public void facet_ncloc_contains_only_projects_authorized_for_user() {
+    // User can see these projects
+    indexForUser(USER1,
+      // docs with ncloc<1K
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 100d),
+      newDoc(NCLOC, 999d),
+      // docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d),
+      newDoc(NCLOC, 9_999d));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // doc with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 11_000d),
+      // doc with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 499_000d),
+      // doc with ncloc>= 500K
+      newDoc(NCLOC, 501_000d));
+
+    userSession.logIn(USER1);
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
+
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_new_lines() {
+    index(
+      // 3 docs with ncloc<1K
+      newDoc(NEW_LINES, 0d),
+      newDoc(NEW_LINES, 0d),
+      newDoc(NEW_LINES, 999d),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc(NEW_LINES, 1_000d),
+      newDoc(NEW_LINES, 9_999d),
+      // 4 docs with ncloc>=10K and ncloc<100K
+      newDoc(NEW_LINES, 10_000d),
+      newDoc(NEW_LINES, 10_000d),
+      newDoc(NEW_LINES, 11_000d),
+      newDoc(NEW_LINES, 99_000d),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc(NEW_LINES, 100_000d),
+      newDoc(NEW_LINES, 499_000d),
+      // 5 docs with ncloc>= 500K
+      newDoc(NEW_LINES, 500_000d),
+      newDoc(NEW_LINES, 100_000_000d),
+      newDoc(NEW_LINES, 500_000d),
+      newDoc(NEW_LINES, 1_000_000d),
+      newDoc(NEW_LINES, 100_000_000_000d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_LINES)).getFacets();
+
+    assertThat(facets.get(NEW_LINES)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 4L),
+      entry("100000.0-500000.0", 2L),
+      entry("500000.0-*", 5L));
+  }
+
+  @Test
+  public void facet_coverage() {
+    index(
+      // 1 doc with no coverage
+      newDocWithNoMeasure(),
+      // 3 docs with coverage<30%
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 29d),
+      // 2 docs with coverage>=30% and coverage<50%
+      newDoc(COVERAGE, 30d),
+      newDoc(COVERAGE, 49d),
+      // 4 docs with coverage>=50% and coverage<70%
+      newDoc(COVERAGE, 50d),
+      newDoc(COVERAGE, 60d),
+      newDoc(COVERAGE, 60d),
+      newDoc(COVERAGE, 69d),
+      // 2 docs with coverage>=70% and coverage<80%
+      newDoc(COVERAGE, 70d),
+      newDoc(COVERAGE, 79d),
+      // 5 docs with coverage>= 80%
+      newDoc(COVERAGE, 80d),
+      newDoc(COVERAGE, 80d),
+      newDoc(COVERAGE, 90d),
+      newDoc(COVERAGE, 90.5d),
+      newDoc(COVERAGE, 100d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
+
+    assertThat(facets.get(COVERAGE)).containsOnly(
+      entry("NO_DATA", 1L),
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 4L),
+      entry("70.0-80.0", 2L),
+      entry("80.0-*", 5L));
+  }
+
+  @Test
+  public void facet_coverage_is_sticky() {
+    index(
+      // docs with no coverage
+      newDoc(NCLOC, 999d, DUPLICATION, 0d),
+      newDoc(NCLOC, 999d, DUPLICATION, 1d),
+      newDoc(NCLOC, 999d, DUPLICATION, 20d),
+      // docs with coverage<30%
+      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
+      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
+      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
+      // docs with coverage>=30% and coverage<50%
+      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
+      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
+      // docs with coverage>=50% and coverage<70%
+      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
+      // docs with coverage>=70% and coverage<80%
+      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
+      // docs with coverage>= 80%
+      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 15d),
+      newDoc(NCLOC, 501_000d, COVERAGE, 810d, DUPLICATION, 20d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d))
+        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d)),
+      new SearchOptions().addFacets(COVERAGE, NCLOC)).getFacets();
+
+    // Sticky facet on coverage does not take into account coverage filter
+    assertThat(facets.get(COVERAGE)).containsExactly(
+      entry("NO_DATA", 2L),
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 1L),
+      entry("70.0-80.0", 1L),
+      entry("80.0-*", 0L));
+    // But facet on ncloc does well take into into filters
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_coverage_contains_only_projects_authorized_for_user() {
+    // User can see these projects
+    indexForUser(USER1,
+      // 1 doc with no coverage
+      newDocWithNoMeasure(),
+      // docs with coverage<30%
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 29d),
+      // docs with coverage>=30% and coverage<50%
+      newDoc(COVERAGE, 30d),
+      newDoc(COVERAGE, 49d));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // 2 docs with no coverage
+      newDocWithNoMeasure(),
+      newDocWithNoMeasure(),
+      // docs with coverage>=50% and coverage<70%
+      newDoc(COVERAGE, 50d),
+      // docs with coverage>=70% and coverage<80%
+      newDoc(COVERAGE, 70d),
+      // docs with coverage>= 80%
+      newDoc(COVERAGE, 80d));
+
+    userSession.logIn(USER1);
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
+
+    assertThat(facets.get(COVERAGE)).containsExactly(
+      entry("NO_DATA", 1L),
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 0L),
+      entry("70.0-80.0", 0L),
+      entry("80.0-*", 0L));
+  }
+
+  @Test
+  public void facet_new_coverage() {
+    index(
+      // 1 doc with no coverage
+      newDocWithNoMeasure(),
+      // 3 docs with coverage<30%
+      newDoc(NEW_COVERAGE, 0d),
+      newDoc(NEW_COVERAGE, 0d),
+      newDoc(NEW_COVERAGE, 29d),
+      // 2 docs with coverage>=30% and coverage<50%
+      newDoc(NEW_COVERAGE, 30d),
+      newDoc(NEW_COVERAGE, 49d),
+      // 4 docs with coverage>=50% and coverage<70%
+      newDoc(NEW_COVERAGE, 50d),
+      newDoc(NEW_COVERAGE, 60d),
+      newDoc(NEW_COVERAGE, 60d),
+      newDoc(NEW_COVERAGE, 69d),
+      // 2 docs with coverage>=70% and coverage<80%
+      newDoc(NEW_COVERAGE, 70d),
+      newDoc(NEW_COVERAGE, 79d),
+      // 5 docs with coverage>= 80%
+      newDoc(NEW_COVERAGE, 80d),
+      newDoc(NEW_COVERAGE, 80d),
+      newDoc(NEW_COVERAGE, 90d),
+      newDoc(NEW_COVERAGE, 90.5d),
+      newDoc(NEW_COVERAGE, 100d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_COVERAGE)).getFacets();
+
+    assertThat(facets.get(NEW_COVERAGE)).containsOnly(
+      entry("NO_DATA", 1L),
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 4L),
+      entry("70.0-80.0", 2L),
+      entry("80.0-*", 5L));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density() {
+    index(
+      // 1 doc with no duplication
+      newDocWithNoMeasure(),
+      // 3 docs with duplication<3%
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 2.9d),
+      // 2 docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d),
+      newDoc(DUPLICATION, 4.9d),
+      // 4 docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d),
+      newDoc(DUPLICATION, 6d),
+      newDoc(DUPLICATION, 6d),
+      newDoc(DUPLICATION, 9.9d),
+      // 2 docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d),
+      newDoc(DUPLICATION, 19.9d),
+      // 5 docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d),
+      newDoc(DUPLICATION, 20d),
+      newDoc(DUPLICATION, 50d),
+      newDoc(DUPLICATION, 80d),
+      newDoc(DUPLICATION, 100d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
+
+    assertThat(facets.get(DUPLICATION)).containsOnly(
+      entry("NO_DATA", 1L),
+      entry("*-3.0", 3L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 4L),
+      entry("10.0-20.0", 2L),
+      entry("20.0-*", 5L));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density_is_sticky() {
+    index(
+      // docs with no duplication
+      newDoc(NCLOC, 50_001d, COVERAGE, 29d),
+      // docs with duplication<3%
+      newDoc(DUPLICATION, 0d, NCLOC, 999d, COVERAGE, 0d),
+      // docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d, NCLOC, 5000d, COVERAGE, 0d),
+      newDoc(DUPLICATION, 4.9d, NCLOC, 6000d, COVERAGE, 0d),
+      // docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d, NCLOC, 11000d, COVERAGE, 0d),
+      // docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d, NCLOC, 120000d, COVERAGE, 10d),
+      newDoc(DUPLICATION, 19.9d, NCLOC, 130000d, COVERAGE, 20d),
+      // docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d, NCLOC, 1000000d, COVERAGE, 40d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .addMetricCriterion(MetricCriterion.create(DUPLICATION, Operator.LT, 10d))
+        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d)),
+      new SearchOptions().addFacets(DUPLICATION, NCLOC)).getFacets();
+
+    // Sticky facet on duplication does not take into account duplication filter
+    assertThat(facets.get(DUPLICATION)).containsOnly(
+      entry("NO_DATA", 1L),
+      entry("*-3.0", 1L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 1L),
+      entry("10.0-20.0", 2L),
+      entry("20.0-*", 0L));
+    // But facet on ncloc does well take into into filters
+    assertThat(facets.get(NCLOC)).containsOnly(
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 1L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() {
+    // User can see these projects
+    indexForUser(USER1,
+      // docs with no duplication
+      newDocWithNoMeasure(),
+      // docs with duplication<3%
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 2.9d),
+      // docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d),
+      newDoc(DUPLICATION, 4.9d));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // docs with no duplication
+      newDocWithNoMeasure(),
+      newDocWithNoMeasure(),
+      // docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d),
+      // docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d),
+      // docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d));
+
+    userSession.logIn(USER1);
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
+
+    assertThat(facets.get(DUPLICATION)).containsOnly(
+      entry("NO_DATA", 1L),
+      entry("*-3.0", 3L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 0L),
+      entry("10.0-20.0", 0L),
+      entry("20.0-*", 0L));
+  }
+
+  @Test
+  public void facet_new_duplicated_lines_density() {
+    index(
+      // 2 docs with no measure
+      newDocWithNoMeasure(),
+      newDocWithNoMeasure(),
+      // 3 docs with duplication<3%
+      newDoc(NEW_DUPLICATION, 0d),
+      newDoc(NEW_DUPLICATION, 0d),
+      newDoc(NEW_DUPLICATION, 2.9d),
+      // 2 docs with duplication>=3% and duplication<5%
+      newDoc(NEW_DUPLICATION, 3d),
+      newDoc(NEW_DUPLICATION, 4.9d),
+      // 4 docs with duplication>=5% and duplication<10%
+      newDoc(NEW_DUPLICATION, 5d),
+      newDoc(NEW_DUPLICATION, 6d),
+      newDoc(NEW_DUPLICATION, 6d),
+      newDoc(NEW_DUPLICATION, 9.9d),
+      // 2 docs with duplication>=10% and duplication<20%
+      newDoc(NEW_DUPLICATION, 10d),
+      newDoc(NEW_DUPLICATION, 19.9d),
+      // 5 docs with duplication>= 20%
+      newDoc(NEW_DUPLICATION, 20d),
+      newDoc(NEW_DUPLICATION, 20d),
+      newDoc(NEW_DUPLICATION, 50d),
+      newDoc(NEW_DUPLICATION, 80d),
+      newDoc(NEW_DUPLICATION, 100d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NEW_DUPLICATION)).getFacets();
+
+    assertThat(facets.get(NEW_DUPLICATION)).containsExactly(
+      entry("NO_DATA", 2L),
+      entry("*-3.0", 3L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 4L),
+      entry("10.0-20.0", 2L),
+      entry("20.0-*", 5L));
+  }
+
+  @Test
+  @UseDataProvider("rating_metric_keys")
+  public void facet_on_rating(String metricKey) {
+    index(
+      // 3 docs with rating A
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      // 2 docs with rating B
+      newDoc(metricKey, 2d),
+      newDoc(metricKey, 2d),
+      // 4 docs with rating C
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      // 2 docs with rating D
+      newDoc(metricKey, 4d),
+      newDoc(metricKey, 4d),
+      // 5 docs with rating E
+      newDoc(metricKey, 5d),
+      newDoc(metricKey, 5d),
+      newDoc(metricKey, 5d),
+      newDoc(metricKey, 5d),
+      newDoc(metricKey, 5d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
+
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 2L),
+      entry("5", 5L));
+  }
+
+  @Test
+  @UseDataProvider("rating_metric_keys")
+  public void facet_on_rating_is_sticky(String metricKey) {
+    index(
+      // docs with rating A
+      newDoc(metricKey, 1d, NCLOC, 100d, COVERAGE, 0d),
+      newDoc(metricKey, 1d, NCLOC, 200d, COVERAGE, 0d),
+      newDoc(metricKey, 1d, NCLOC, 999d, COVERAGE, 0d),
+      // docs with rating B
+      newDoc(metricKey, 2d, NCLOC, 2000d, COVERAGE, 0d),
+      newDoc(metricKey, 2d, NCLOC, 5000d, COVERAGE, 0d),
+      // docs with rating C
+      newDoc(metricKey, 3d, NCLOC, 20000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 30000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 40000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 50000d, COVERAGE, 0d),
+      // docs with rating D
+      newDoc(metricKey, 4d, NCLOC, 120000d, COVERAGE, 0d),
+      // docs with rating E
+      newDoc(metricKey, 5d, NCLOC, 600000d, COVERAGE, 40d),
+      newDoc(metricKey, 5d, NCLOC, 700000d, COVERAGE, 50d),
+      newDoc(metricKey, 5d, NCLOC, 800000d, COVERAGE, 60d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .addMetricCriterion(MetricCriterion.create(metricKey, Operator.LT, 3d))
+        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d)),
+      new SearchOptions().addFacets(metricKey, NCLOC)).getFacets();
+
+    // Sticky facet on maintainability rating does not take into account maintainability rating filter
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 1L),
+      entry("5", 0L));
+    // But facet on ncloc does well take into into filters
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  @UseDataProvider("rating_metric_keys")
+  public void facet_on_rating_contains_only_projects_authorized_for_user(String metricKey) {
+    // User can see these projects
+    indexForUser(USER1,
+      // 3 docs with rating A
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      // 2 docs with rating B
+      newDoc(metricKey, 2d),
+      newDoc(metricKey, 2d));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // docs with rating C
+      newDoc(metricKey, 3d),
+      // docs with rating D
+      newDoc(metricKey, 4d),
+      // docs with rating E
+      newDoc(metricKey, 5d));
+
+    userSession.logIn(USER1);
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
+
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 0L),
+      entry("4", 0L),
+      entry("5", 0L));
+  }
+
+  @Test
+  public void facet_quality_gate() {
+    index(
+      // 2 docs with QG OK
+      newDoc().setQualityGateStatus(OK.name()),
+      newDoc().setQualityGateStatus(OK.name()),
+      // 3 docs with QG WARN
+      newDoc().setQualityGateStatus(WARN.name()),
+      newDoc().setQualityGateStatus(WARN.name()),
+      newDoc().setQualityGateStatus(WARN.name()),
+      // 4 docs with QG ERROR
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()));
+
+    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
+
+    assertThat(result).containsOnly(
+      entry(ERROR.name(), 4L),
+      entry(WARN.name(), 3L),
+      entry(OK.name(), 2L));
+  }
+
+  @Test
+  public void facet_quality_gate_is_sticky() {
+    index(
+      // 2 docs with QG OK
+      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGateStatus(OK.name()),
+      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGateStatus(OK.name()),
+      // 3 docs with QG WARN
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(WARN.name()),
+      // 4 docs with QG ERROR
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGateStatus(ERROR.name()),
+      newDoc(NCLOC, 5000d, COVERAGE, 40d).setQualityGateStatus(ERROR.name()),
+      newDoc(NCLOC, 12000d, COVERAGE, 50d).setQualityGateStatus(ERROR.name()),
+      newDoc(NCLOC, 13000d, COVERAGE, 60d).setQualityGateStatus(ERROR.name()));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .setQualityGateStatus(ERROR)
+        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 55d)),
+      new SearchOptions().addFacets(ALERT_STATUS_KEY, NCLOC)).getFacets();
+
+    // Sticky facet on quality gate does not take into account quality gate filter
+    assertThat(facets.get(ALERT_STATUS_KEY)).containsOnly(
+      entry(OK.name(), 2L),
+      entry(WARN.name(), 3L),
+      entry(ERROR.name(), 3L));
+    // But facet on ncloc does well take into into filters
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 1L),
+      entry("10000.0-100000.0", 1L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_quality_gate_contains_only_projects_authorized_for_user() {
+    // User can see these projects
+    indexForUser(USER1,
+      // 2 docs with QG OK
+      newDoc().setQualityGateStatus(OK.name()),
+      newDoc().setQualityGateStatus(OK.name()),
+      // 3 docs with QG WARN
+      newDoc().setQualityGateStatus(WARN.name()),
+      newDoc().setQualityGateStatus(WARN.name()),
+      newDoc().setQualityGateStatus(WARN.name()));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // 4 docs with QG ERROR
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()),
+      newDoc().setQualityGateStatus(ERROR.name()));
+
+    userSession.logIn(USER1);
+    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
+
+    assertThat(result).containsOnly(
+      entry(ERROR.name(), 0L),
+      entry(WARN.name(), 3L),
+      entry(OK.name(), 2L));
+  }
+
+  @Test
+  public void facet_languages() {
+    index(
+      newDoc().setLanguages(singletonList("java")),
+      newDoc().setLanguages(singletonList("java")),
+      newDoc().setLanguages(singletonList("xoo")),
+      newDoc().setLanguages(singletonList("xml")),
+      newDoc().setLanguages(asList("<null>", "java")),
+      newDoc().setLanguages(asList("<null>", "java", "xoo")));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets();
+
+    assertThat(facets.get(LANGUAGES)).containsOnly(
+      entry("<null>", 2L),
+      entry("java", 4L),
+      entry("xoo", 2L),
+      entry("xml", 1L));
+  }
+
+  @Test
+  public void facet_languages_is_limited_to_10_languages() {
+    index(
+      newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
+      newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
+      newDoc().setLanguages(asList("js", "scala")));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets();
+
+    assertThat(facets.get(LANGUAGES)).hasSize(10);
+  }
+
+  @Test
+  public void facet_languages_is_sticky() {
+    index(
+      newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
+      newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
+      newDoc(NCLOC, 10d).setLanguages(singletonList("xoo")),
+      newDoc(NCLOC, 100d).setLanguages(singletonList("xml")),
+      newDoc(NCLOC, 100d).setLanguages(asList("<null>", "java")),
+      newDoc(NCLOC, 5000d).setLanguages(asList("<null>", "java", "xoo")));
+
+    Facets facets = underTest.search(
+      new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("java")),
+      new SearchOptions().addFacets(LANGUAGES, NCLOC)).getFacets();
+
+    // Sticky facet on language does not take into account language filter
+    assertThat(facets.get(LANGUAGES)).containsOnly(
+      entry("<null>", 2L),
+      entry("java", 4L),
+      entry("xoo", 2L),
+      entry("xml", 1L));
+    // But facet on ncloc does well take account into filters
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 1L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_languages_returns_more_than_10_languages_when_languages_filter_contains_value_not_in_top_10() {
+    index(
+      newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
+      newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
+      newDoc().setLanguages(asList("js", "scala")));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("xoo", "xml")), new SearchOptions().addFacets(LANGUAGES)).getFacets();
+
+    assertThat(facets.get(LANGUAGES)).containsOnly(
+      entry("<null>", 1L),
+      entry("cpp", 1L),
+      entry("css", 1L),
+      entry("java", 1L),
+      entry("js", 1L),
+      entry("perl", 1L),
+      entry("php", 1L),
+      entry("python", 1L),
+      entry("ruby", 1L),
+      entry("scala", 1L),
+      entry("xoo", 1L),
+      entry("xml", 1L));
+  }
+
+  @Test
+  public void facet_languages_contains_only_projects_authorized_for_user() {
+    // User can see these projects
+    indexForUser(USER1,
+      newDoc().setLanguages(singletonList("java")),
+      newDoc().setLanguages(asList("java", "xoo")));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      newDoc().setLanguages(singletonList("java")),
+      newDoc().setLanguages(asList("java", "xoo")));
+
+    userSession.logIn(USER1);
+    LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(LANGUAGES)).getFacets().get(LANGUAGES);
+
+    assertThat(result).containsOnly(
+      entry("java", 2L),
+      entry("xoo", 1L));
+  }
+
+  @Test
+  public void facet_tags() {
+    index(
+      newDoc().setTags(newArrayList("finance", "offshore", "java")),
+      newDoc().setTags(newArrayList("finance", "javascript")),
+      newDoc().setTags(newArrayList("marketing", "finance")),
+      newDoc().setTags(newArrayList("marketing", "offshore")),
+      newDoc().setTags(newArrayList("finance", "marketing")),
+      newDoc().setTags(newArrayList("finance")));
+
+    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FIELD_TAGS)).getFacets().get(FIELD_TAGS);
+
+    assertThat(result).containsOnly(
+      entry("finance", 5L),
+      entry("marketing", 3L),
+      entry("offshore", 2L),
+      entry("java", 1L),
+      entry("javascript", 1L));
+  }
+
+  @Test
+  public void facet_tags_is_sticky() {
+    index(
+      newDoc().setTags(newArrayList("finance")).setQualityGateStatus(OK.name()),
+      newDoc().setTags(newArrayList("finance")).setQualityGateStatus(ERROR.name()),
+      newDoc().setTags(newArrayList("cpp")).setQualityGateStatus(WARN.name()));
+
+    Facets facets = underTest.search(
+      new ProjectMeasuresQuery().setTags(newHashSet("cpp")),
+      new SearchOptions().addFacets(FIELD_TAGS).addFacets(ALERT_STATUS_KEY))
+      .getFacets();
+
+    assertThat(facets.get(FIELD_TAGS)).containsOnly(
+      entry("finance", 2L),
+      entry("cpp", 1L));
+    assertThat(facets.get(ALERT_STATUS_KEY)).containsOnly(
+      entry(OK.name(), 0L),
+      entry(ERROR.name(), 0L),
+      entry(WARN.name(), 1L));
+  }
+
+  @Test
+  public void facet_tags_returns_10_elements_by_default() {
+    index(
+      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
+      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
+      newDoc().setTags(newArrayList("solo")));
+
+    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(FIELD_TAGS)).getFacets().get(FIELD_TAGS);
+
+    assertThat(result).hasSize(10).containsOnlyKeys("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10");
+  }
+
+  @Test
+  public void facet_tags_returns_more_than_10_tags_when_tags_filter_contains_value_not_in_top_10() {
+    index(
+      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
+      newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
+      newDoc().setTags(newArrayList("solo", "solo2")));
+
+    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery().setTags(ImmutableSet.of("solo", "solo2")), new SearchOptions().addFacets(FIELD_TAGS)).getFacets()
+      .get(FIELD_TAGS);
+
+    assertThat(result).hasSize(12).containsOnlyKeys("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10", "solo",
+      "solo2");
+  }
+
+  @Test
+  public void search_tags() {
+    index(
+      newDoc().setTags(newArrayList("finance", "offshore", "java")),
+      newDoc().setTags(newArrayList("official", "javascript")),
+      newDoc().setTags(newArrayList("marketing", "official")),
+      newDoc().setTags(newArrayList("marketing", "Madhoff")),
+      newDoc().setTags(newArrayList("finance", "offshore")),
+      newDoc().setTags(newArrayList("offshore")));
+
+    List<String> result = underTest.searchTags("off", 10);
+
+    assertThat(result).containsOnly("offshore", "official", "Madhoff");
+  }
+
+  @Test
+  public void search_tags_return_all_tags() {
+    index(
+      newDoc().setTags(newArrayList("finance", "offshore", "java")),
+      newDoc().setTags(newArrayList("official", "javascript")),
+      newDoc().setTags(newArrayList("marketing", "official")),
+      newDoc().setTags(newArrayList("marketing", "Madhoff")),
+      newDoc().setTags(newArrayList("finance", "offshore")),
+      newDoc().setTags(newArrayList("offshore")));
+
+    List<String> result = underTest.searchTags(null, 10);
+
+    assertThat(result).containsOnly("offshore", "official", "Madhoff", "finance", "marketing", "java", "javascript");
+  }
+
+  @Test
+  public void search_tags_in_lexical_order() {
+    index(
+      newDoc().setTags(newArrayList("finance", "offshore", "java")),
+      newDoc().setTags(newArrayList("official", "javascript")),
+      newDoc().setTags(newArrayList("marketing", "official")),
+      newDoc().setTags(newArrayList("marketing", "Madhoff")),
+      newDoc().setTags(newArrayList("finance", "offshore")),
+      newDoc().setTags(newArrayList("offshore")));
+
+    List<String> result = underTest.searchTags(null, 10);
+
+    assertThat(result).containsExactly("Madhoff", "finance", "java", "javascript", "marketing", "official", "offshore");
+  }
+
+  @Test
+  public void search_tags_only_of_authorized_projects() {
+    indexForUser(USER1,
+      newDoc(PROJECT1).setTags(singletonList("finance")),
+      newDoc(PROJECT2).setTags(singletonList("marketing")));
+    indexForUser(USER2,
+      newDoc(PROJECT3).setTags(singletonList("offshore")));
+
+    userSession.logIn(USER1);
+
+    List<String> result = underTest.searchTags(null, 10);
+
+    assertThat(result).containsOnly("finance", "marketing");
+  }
+
+  @Test
+  public void search_tags_with_no_tags() {
+    List<String> result = underTest.searchTags("whatever", 10);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void search_tags_with_page_size_at_0() {
+    index(newDoc().setTags(newArrayList("offshore")));
+
+    List<String> result = underTest.searchTags(null, 0);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void search_statistics() {
+    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES,
+      newDoc("lines", 10, "coverage", 80)
+        .setLanguages(Arrays.asList("java", "cs", "js"))
+        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 200, "cs", 250, "js", 50)),
+      newDoc("lines", 20, "coverage", 80)
+        .setLanguages(Arrays.asList("java", "python", "kotlin"))
+        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)));
+
+    ProjectMeasuresStatistics result = underTest.searchTelemetryStatistics();
+
+    assertThat(result.getProjectCount()).isEqualTo(2);
+    assertThat(result.getProjectCountByLanguage()).containsOnly(
+      entry("java", 2L), entry("cs", 1L), entry("js", 1L), entry("python", 1L), entry("kotlin", 1L));
+    assertThat(result.getNclocByLanguage()).containsOnly(
+      entry("java", 500L), entry("cs", 250L), entry("js", 50L), entry("python", 100L), entry("kotlin", 404L));
+  }
+
+  @Test
+  public void fail_if_page_size_greater_than_500() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Page size must be lower than or equals to 500");
+
+    underTest.searchTags("whatever", 501);
+  }
+
+  private void index(ProjectMeasuresDoc... docs) {
+    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    for (ProjectMeasuresDoc doc : docs) {
+      IndexPermissions access = new IndexPermissions(doc.getId(), Qualifiers.PROJECT);
+      access.allowAnyone();
+      authorizationIndexerTester.allow(access);
+    }
+  }
+
+  private void indexForUser(UserDto user, ProjectMeasuresDoc... docs) {
+    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    for (ProjectMeasuresDoc doc : docs) {
+      IndexPermissions access = new IndexPermissions(doc.getId(), Qualifiers.PROJECT);
+      access.addUserId(user.getId());
+      authorizationIndexerTester.allow(access);
+    }
+  }
+
+  private void indexForGroup(GroupDto group, ProjectMeasuresDoc... docs) {
+    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    for (ProjectMeasuresDoc doc : docs) {
+      IndexPermissions access = new IndexPermissions(doc.getId(), Qualifiers.PROJECT);
+      access.addGroupId(group.getId());
+      authorizationIndexerTester.allow(access);
+    }
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project) {
+    return new ProjectMeasuresDoc()
+      .setOrganizationUuid(project.getOrganizationUuid())
+      .setId(project.uuid())
+      .setKey(project.getDbKey())
+      .setName(project.name());
+  }
+
+  private static ProjectMeasuresDoc newDoc() {
+    return newDoc(ComponentTesting.newPrivateProjectDto(ORG));
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1) {
+    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1)));
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1, String metric2, Object value2) {
+    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1), newMeasure(metric2, value2)));
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1, String metric2, Object value2, String metric3, Object value3) {
+    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1), newMeasure(metric2, value2), newMeasure(metric3, value3)));
+  }
+
+  private static Map<String, Object> newMeasure(String key, Object value) {
+    return ImmutableMap.of("key", key, "value", value);
+  }
+
+  private static ProjectMeasuresDoc newDocWithNoMeasure() {
+    return newDoc(ComponentTesting.newPrivateProjectDto(ORG));
+  }
+
+  private static ProjectMeasuresDoc newDoc(String metric1, Object value1) {
+    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1);
+  }
+
+  private static ProjectMeasuresDoc newDoc(String metric1, Object value1, String metric2, Object value2) {
+    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1, metric2, value2);
+  }
+
+  private static ProjectMeasuresDoc newDoc(String metric1, Object value1, String metric2, Object value2, String metric3, Object value3) {
+    return newDoc(ComponentTesting.newPrivateProjectDto(ORG), metric1, value1, metric2, value2, metric3, value3);
+  }
+
+  private void assertResults(ProjectMeasuresQuery query, ComponentDto... expectedProjects) {
+    List<String> result = underTest.search(query, new SearchOptions()).getIds();
+    assertThat(result).containsExactly(Arrays.stream(expectedProjects).map(ComponentDto::uuid).toArray(String[]::new));
+  }
+
+  private void assertNoResults(ProjectMeasuresQuery query) {
+    assertResults(query);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java
new file mode 100644 (file)
index 0000000..a60a7a8
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.stream;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.GT;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.LT;
+
+public class ProjectMeasuresIndexTextSearchTest {
+
+  private static final String NCLOC = "ncloc";
+
+  private static final OrganizationDto ORG = OrganizationTesting.newOrganizationDto();
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, projectMeasureIndexer);
+  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+
+  @Test
+  public void match_exact_case_insensitive_name() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube")));
+
+    assertTextQueryResults("Apache Struts", "struts");
+    assertTextQueryResults("APACHE STRUTS", "struts");
+    assertTextQueryResults("APACHE struTS", "struts");
+  }
+
+  @Test
+  public void match_from_sub_name() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
+
+    assertTextQueryResults("truts", "struts");
+    assertTextQueryResults("pache", "struts");
+    assertTextQueryResults("apach", "struts");
+    assertTextQueryResults("che stru", "struts");
+  }
+
+  @Test
+  public void match_name_with_dot() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache.Struts")));
+
+    assertTextQueryResults("apache struts", "struts");
+  }
+
+  @Test
+  public void match_partial_name() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("XstrutsxXjavax")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_partial_name_prefix_word1() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.java")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_partial_name_suffix_word1() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("StrutsObject.java")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_partial_name_prefix_word2() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.xjava")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_partial_name_suffix_word2() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStrutsObject.xjavax")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_subset_of_document_terms() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Some.Struts.Project.java.old")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void match_partial_match_prefix_and_suffix_everywhere() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("MyStruts.javax")));
+
+    assertTextQueryResults("struts java", "struts");
+  }
+
+  @Test
+  public void ignore_empty_words() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Struts")));
+
+    assertTextQueryResults("            struts   \n     \n\n", "struts");
+  }
+
+  @Test
+  public void match_name_from_prefix() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
+
+    assertTextQueryResults("apach", "struts");
+    assertTextQueryResults("ApA", "struts");
+    assertTextQueryResults("AP", "struts");
+  }
+
+  @Test
+  public void match_name_from_two_words() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("project").setName("ApacheStrutsFoundation")));
+
+    assertTextQueryResults("apache struts", "project");
+    assertTextQueryResults("struts apache", "project");
+    // Only one word is matching
+    assertNoResults("apache plugin");
+    assertNoResults("project struts");
+  }
+
+  @Test
+  public void match_long_name() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("LongNameLongNameLongNameLongNameSonarQube")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("LongNameLongNameLongNameLongNameSonarQubeX")));
+
+    assertTextQueryResults("LongNameLongNameLongNameLongNameSonarQube", "project1", "project2");
+  }
+
+  @Test
+  public void match_name_with_two_characters() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("struts").setName("Apache Struts")));
+
+    assertTextQueryResults("st", "struts");
+    assertTextQueryResults("tr", "struts");
+  }
+
+  @Test
+  public void match_exact_case_insensitive_key() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("Windows").setDbKey("project1")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("apachee").setDbKey("project2")));
+
+    assertTextQueryResults("project1", "project1");
+    assertTextQueryResults("PROJECT1", "project1");
+    assertTextQueryResults("pRoJecT1", "project1");
+  }
+
+  @Test
+  public void match_key_with_dot() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org.sonarqube")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
+
+    assertTextQueryResults("org.sonarqube", "sonarqube");
+    assertNoResults("orgsonarqube");
+    assertNoResults("org-sonarqube");
+    assertNoResults("org:sonarqube");
+    assertNoResults("org sonarqube");
+  }
+
+  @Test
+  public void match_key_with_dash() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org-sonarqube")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
+
+    assertTextQueryResults("org-sonarqube", "sonarqube");
+    assertNoResults("orgsonarqube");
+    assertNoResults("org.sonarqube");
+    assertNoResults("org:sonarqube");
+    assertNoResults("org sonarqube");
+  }
+
+  @Test
+  public void match_key_with_colon() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org:sonarqube")),
+      newDoc(newPrivateProjectDto(ORG).setUuid("sq").setName("SQ").setDbKey("sonarqube")));
+
+    assertTextQueryResults("org:sonarqube", "sonarqube");
+    assertNoResults("orgsonarqube");
+    assertNoResults("org-sonarqube");
+    assertNoResults("org_sonarqube");
+    assertNoResults("org sonarqube");
+  }
+
+  @Test
+  public void match_key_having_all_special_characters() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("sonarqube").setName("SonarQube").setDbKey("org.sonarqube:sonar-sérvèr_ç")));
+
+    assertTextQueryResults("org.sonarqube:sonar-sérvèr_ç", "sonarqube");
+  }
+
+  @Test
+  public void does_not_match_partial_key() {
+    index(newDoc(newPrivateProjectDto(ORG).setUuid("project").setName("some name").setDbKey("theKey")));
+
+    assertNoResults("theke");
+    assertNoResults("hekey");
+  }
+
+  @Test
+  public void facets_take_into_account_text_search() {
+    index(
+      // docs with ncloc<1K
+      newDoc(newPrivateProjectDto(ORG).setName("Windows").setDbKey("project1"), NCLOC, 0d),
+      newDoc(newPrivateProjectDto(ORG).setName("apachee").setDbKey("project2"), NCLOC, 999d),
+      // docs with ncloc>=1K and ncloc<10K
+      newDoc(newPrivateProjectDto(ORG).setName("Apache").setDbKey("project3"), NCLOC, 1_000d),
+      // docs with ncloc>=100K and ncloc<500K
+      newDoc(newPrivateProjectDto(ORG).setName("Apache Foundation").setDbKey("project4"), NCLOC, 100_000d));
+
+    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("apache"), 1L, 1L, 0L, 1L, 0L);
+    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("PAch"), 1L, 1L, 0L, 1L, 0L);
+    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("apache foundation"), 0L, 0L, 0L, 1L, 0L);
+    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("project3"), 0L, 1L, 0L, 0L, 0L);
+    assertNclocFacet(new ProjectMeasuresQuery().setQueryText("project"), 0L, 0L, 0L, 0L, 0L);
+  }
+
+  @Test
+  public void filter_by_metric_take_into_account_text_search() {
+    index(
+      newDoc(newPrivateProjectDto(ORG).setUuid("project1").setName("Windows").setDbKey("project1"), NCLOC, 30_000d),
+      newDoc(newPrivateProjectDto(ORG).setUuid("project2").setName("apachee").setDbKey("project2"), NCLOC, 40_000d),
+      newDoc(newPrivateProjectDto(ORG).setUuid("project3").setName("Apache").setDbKey("project3"), NCLOC, 50_000d),
+      newDoc(newPrivateProjectDto(ORG).setUuid("project4").setName("Apache").setDbKey("project4"), NCLOC, 60_000d));
+
+    assertResults(new ProjectMeasuresQuery().setQueryText("apache").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 20_000d)), "project3", "project4", "project2");
+    assertResults(new ProjectMeasuresQuery().setQueryText("apache").addMetricCriterion(MetricCriterion.create(NCLOC, LT, 55_000d)), "project3", "project2");
+    assertResults(new ProjectMeasuresQuery().setQueryText("PAC").addMetricCriterion(MetricCriterion.create(NCLOC, LT, 55_000d)), "project3", "project2");
+    assertResults(new ProjectMeasuresQuery().setQueryText("apachee").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 30_000d)), "project2");
+    assertResults(new ProjectMeasuresQuery().setQueryText("unknown").addMetricCriterion(MetricCriterion.create(NCLOC, GT, 20_000d)));
+  }
+
+  private void index(ProjectMeasuresDoc... docs) {
+    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    stream(docs).forEach(doc -> {
+      IndexPermissions access = new IndexPermissions(doc.getId(), Qualifiers.PROJECT);
+      access.allowAnyone();
+      authorizationIndexerTester.allow(access);
+    });
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project) {
+    return new ProjectMeasuresDoc()
+      .setOrganizationUuid(project.getOrganizationUuid())
+      .setId(project.uuid())
+      .setKey(project.getDbKey())
+      .setName(project.name());
+  }
+
+  private static ProjectMeasuresDoc newDoc(ComponentDto project, String metric1, Object value1) {
+    return newDoc(project).setMeasures(newArrayList(newMeasure(metric1, value1)));
+  }
+
+  private static Map<String, Object> newMeasure(String key, Object value) {
+    return ImmutableMap.of("key", key, "value", value);
+  }
+
+  private void assertResults(ProjectMeasuresQuery query, String... expectedProjectUuids) {
+    List<String> result = underTest.search(query, new SearchOptions()).getIds();
+    assertThat(result).containsExactly(expectedProjectUuids);
+  }
+
+  private void assertTextQueryResults(String queryText, String... expectedProjectUuids) {
+    assertResults(new ProjectMeasuresQuery().setQueryText(queryText), expectedProjectUuids);
+  }
+
+  private void assertNoResults(String queryText) {
+    assertTextQueryResults(queryText);
+  }
+
+  private void assertNclocFacet(ProjectMeasuresQuery query, Long... facetExpectedValues) {
+    checkArgument(facetExpectedValues.length == 5, "5 facet values is required");
+    Facets facets = underTest.search(query, new SearchOptions().addFacets(NCLOC)).getFacets();
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", facetExpectedValues[0]),
+      entry("1000.0-10000.0", facetExpectedValues[1]),
+      entry("10000.0-100000.0", facetExpectedValues[2]),
+      entry("100000.0-500000.0", facetExpectedValues[3]),
+      entry("500000.0-*", facetExpectedValues[4]));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java
new file mode 100644 (file)
index 0000000..6da1e29
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric.Level;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.api.measures.Metric.Level.OK;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.EQ;
+
+public class ProjectMeasuresQueryTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private ProjectMeasuresQuery underTest = new ProjectMeasuresQuery();
+
+  @Test
+  public void empty_query() {
+    assertThat(underTest.getMetricCriteria()).isEmpty();
+    assertThat(underTest.getQualityGateStatus()).isEmpty();
+    assertThat(underTest.getOrganizationUuid()).isEmpty();
+  }
+
+  @Test
+  public void add_metric_criterion() {
+    underTest.addMetricCriterion(MetricCriterion.create("coverage", EQ, 10d));
+
+    assertThat(underTest.getMetricCriteria())
+      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
+      .containsOnly(tuple("coverage", EQ, 10d));
+  }
+
+  @Test
+  public void isNoData_returns_true_when_no_data() {
+    underTest.addMetricCriterion(MetricCriterion.createNoData("coverage"));
+
+    assertThat(underTest.getMetricCriteria())
+      .extracting(MetricCriterion::getMetricKey, MetricCriterion::isNoData)
+      .containsOnly(tuple("coverage", true));
+  }
+
+  @Test
+  public void isNoData_returns_false_when_data_exists() {
+    underTest.addMetricCriterion(MetricCriterion.create("coverage", EQ, 10d));
+
+    assertThat(underTest.getMetricCriteria())
+      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::isNoData)
+      .containsOnly(tuple("coverage", EQ, false));
+  }
+
+  @Test
+  public void set_quality_gate_status() {
+    underTest.setQualityGateStatus(OK);
+
+    assertThat(underTest.getQualityGateStatus().get()).isEqualTo(Level.OK);
+  }
+
+  @Test
+  public void default_sort_is_by_name() {
+    assertThat(underTest.getSort()).isEqualTo("name");
+  }
+
+  @Test
+  public void fail_to_set_null_sort() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("Sort cannot be null");
+
+    underTest.setSort(null);
+  }
+
+  @Test
+  public void fail_to_get_value_when_no_data() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("The criterion for metric coverage has no data");
+
+    MetricCriterion.createNoData("coverage").getValue();
+  }
+
+  @Test
+  public void fail_to_get_operator_when_no_data() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("The criterion for metric coverage has no data");
+
+    MetricCriterion.createNoData("coverage").getOperator();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java
new file mode 100644 (file)
index 0000000..0e8613f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class ProjectsEsModuleTest {
+  @Test
+  public void verify_count_of_added_components() {
+    ComponentContainer container = new ComponentContainer();
+    new ProjectsEsModule().configure(container);
+    assertThat(container.size()).isEqualTo(3 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndex.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndex.java
new file mode 100644 (file)
index 0000000..d8c9372
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.Arrays;
+import java.util.List;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHits;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.es.EsClient;
+
+import static org.sonar.server.permission.index.FooIndexDefinition.FOO_INDEX;
+import static org.sonar.server.permission.index.FooIndexDefinition.FOO_TYPE;
+
+public class FooIndex {
+
+  private final EsClient esClient;
+  private final WebAuthorizationTypeSupport authorizationTypeSupport;
+
+  public FooIndex(EsClient esClient, WebAuthorizationTypeSupport authorizationTypeSupport) {
+    this.esClient = esClient;
+    this.authorizationTypeSupport = authorizationTypeSupport;
+  }
+
+  public boolean hasAccessToProject(String projectUuid) {
+    SearchHits hits = esClient.prepareSearch(FOO_INDEX)
+      .setTypes(FOO_TYPE)
+      .setQuery(QueryBuilders.boolQuery()
+        .must(QueryBuilders.termQuery(FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
+        .filter(authorizationTypeSupport.createQueryFilter()))
+      .get()
+      .getHits();
+    List<String> names = Arrays.stream(hits.hits())
+      .map(h -> h.getSource().get(FooIndexDefinition.FIELD_NAME).toString())
+      .collect(MoreCollectors.toList());
+    return names.size() == 2 && names.contains("bar") && names.contains("baz");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java
new file mode 100644 (file)
index 0000000..fa3d294
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.IndexDefinition;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.NewIndex;
+
+import static org.sonar.server.es.NewIndex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+
+public class FooIndexDefinition implements IndexDefinition {
+
+  public static final String FOO_INDEX = "foos";
+  public static final String FOO_TYPE = "foo";
+  public static final IndexType INDEX_TYPE_FOO = new IndexType(FOO_INDEX, FOO_TYPE);
+  public static final String FIELD_NAME = "name";
+  public static final String FIELD_PROJECT_UUID = "projectUuid";
+
+  @Override
+  public void define(IndexDefinitionContext context) {
+    NewIndex index = context.create(FOO_INDEX, newBuilder(new MapSettings().asConfig()).setRefreshInterval(MANUAL_REFRESH_INTERVAL).build());
+
+    NewIndex.NewIndexType type = index.createType(FOO_TYPE)
+      .requireProjectAuthorization();
+
+    type.keywordFieldBuilder(FIELD_NAME).build();
+    type.keywordFieldBuilder(FIELD_PROJECT_UUID).build();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexer.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexer.java
new file mode 100644 (file)
index 0000000..f512268
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Set;
+import org.sonar.db.DbSession;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.ProjectIndexer;
+
+import static org.sonar.server.permission.index.FooIndexDefinition.INDEX_TYPE_FOO;
+
+public class FooIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
+
+  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_FOO, p -> true);
+
+  private final EsClient esClient;
+
+  public FooIndexer(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  @Override
+  public AuthorizationScope getAuthorizationScope() {
+    return AUTHORIZATION_SCOPE;
+  }
+
+  @Override
+  public void indexOnAnalysis(String branchUuid) {
+    addToIndex(branchUuid, "bar");
+    addToIndex(branchUuid, "baz");
+  }
+
+  @Override
+  public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
+    throw new UnsupportedOperationException();
+  }
+
+  private void addToIndex(String projectUuid, String name) {
+    esClient.prepareIndex(INDEX_TYPE_FOO)
+      .setRouting(projectUuid)
+      .setParent(projectUuid)
+      .setSource(ImmutableMap.of(
+        FooIndexDefinition.FIELD_NAME, name,
+        FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
+      .get();
+  }
+
+  @Override
+  public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Set<IndexType> getIndexTypes() {
+    return ImmutableSet.of(INDEX_TYPE_FOO);
+  }
+
+  @Override
+  public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java
new file mode 100644 (file)
index 0000000..df045f6
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.GroupPermissionDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDbTester;
+import org.sonar.db.user.UserDto;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.resources.Qualifiers.APP;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.api.resources.Qualifiers.VIEW;
+import static org.sonar.api.web.UserRole.ADMIN;
+import static org.sonar.api.web.UserRole.USER;
+
+public class PermissionIndexerDaoTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbTester.getSession();
+
+  private ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+  private UserDbTester userDbTester = new UserDbTester(dbTester);
+
+  private OrganizationDto organization;
+  private ComponentDto publicProject;
+  private ComponentDto privateProject1;
+  private ComponentDto privateProject2;
+  private ComponentDto view1;
+  private ComponentDto view2;
+  private ComponentDto application;
+  private UserDto user1;
+  private UserDto user2;
+  private GroupDto group;
+
+  private PermissionIndexerDao underTest = new PermissionIndexerDao();
+
+  @Before
+  public void setUp() {
+    organization = dbTester.organizations().insert();
+    publicProject = componentDbTester.insertPublicProject(organization);
+    privateProject1 = componentDbTester.insertPrivateProject(organization);
+    privateProject2 = componentDbTester.insertPrivateProject(organization);
+    view1 = componentDbTester.insertView(organization);
+    view2 = componentDbTester.insertView(organization);
+    application = componentDbTester.insertApplication(organization);
+    user1 = userDbTester.insertUser();
+    user2 = userDbTester.insertUser();
+    group = userDbTester.insertGroup(organization);
+  }
+
+  @Test
+  public void select_all() {
+    insertTestDataForProjectsAndViews();
+
+    Collection<IndexPermissions> dtos = underTest.selectAll(dbClient, dbSession);
+    Assertions.assertThat(dtos).hasSize(6);
+
+    IndexPermissions publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos);
+    isPublic(publicProjectAuthorization, PROJECT);
+
+    IndexPermissions view1Authorization = getByProjectUuid(view1.uuid(), dtos);
+    isPublic(view1Authorization, VIEW);
+
+    IndexPermissions applicationAuthorization = getByProjectUuid(application.uuid(), dtos);
+    isPublic(applicationAuthorization, APP);
+
+    IndexPermissions privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos);
+    assertThat(privateProject1Authorization.getGroupIds()).containsOnly(group.getId());
+    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject1Authorization.getUserIds()).containsOnly(user1.getId(), user2.getId());
+    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos);
+    assertThat(privateProject2Authorization.getGroupIds()).isEmpty();
+    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject2Authorization.getUserIds()).containsOnly(user1.getId());
+    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions view2Authorization = getByProjectUuid(view2.uuid(), dtos);
+    isPublic(view2Authorization, VIEW);
+  }
+
+  @Test
+  public void selectByUuids() {
+    insertTestDataForProjectsAndViews();
+
+    Map<String, IndexPermissions> dtos = underTest
+      .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid()))
+      .stream()
+      .collect(MoreCollectors.uniqueIndex(IndexPermissions::getProjectUuid, Function.identity()));
+    Assertions.assertThat(dtos).hasSize(6);
+
+    IndexPermissions publicProjectAuthorization = dtos.get(publicProject.uuid());
+    isPublic(publicProjectAuthorization, PROJECT);
+
+    IndexPermissions view1Authorization = dtos.get(view1.uuid());
+    isPublic(view1Authorization, VIEW);
+
+    IndexPermissions applicationAuthorization = dtos.get(application.uuid());
+    isPublic(applicationAuthorization, APP);
+
+    IndexPermissions privateProject1Authorization = dtos.get(privateProject1.uuid());
+    assertThat(privateProject1Authorization.getGroupIds()).containsOnly(group.getId());
+    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject1Authorization.getUserIds()).containsOnly(user1.getId(), user2.getId());
+    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions privateProject2Authorization = dtos.get(privateProject2.uuid());
+    assertThat(privateProject2Authorization.getGroupIds()).isEmpty();
+    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject2Authorization.getUserIds()).containsOnly(user1.getId());
+    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions view2Authorization = dtos.get(view2.uuid());
+    isPublic(view2Authorization, VIEW);
+  }
+
+  @Test
+  public void selectByUuids_returns_empty_list_when_project_does_not_exist() {
+    insertTestDataForProjectsAndViews();
+
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, asList("missing"));
+    Assertions.assertThat(dtos).isEmpty();
+  }
+
+  @Test
+  public void select_by_projects_with_high_number_of_projects() {
+    List<String> projectUuids = new ArrayList<>();
+    for (int i = 0; i < 350; i++) {
+      ComponentDto project = ComponentTesting.newPrivateProjectDto(organization, Integer.toString(i));
+      dbClient.componentDao().insert(dbSession, project);
+      projectUuids.add(project.uuid());
+      GroupPermissionDto dto = new GroupPermissionDto()
+        .setOrganizationUuid(group.getOrganizationUuid())
+        .setGroupId(group.getId())
+        .setRole(USER)
+        .setResourceId(project.getId());
+      dbClient.groupPermissionDao().insert(dbSession, dto);
+    }
+    dbSession.commit();
+
+    assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids))
+      .hasSize(350)
+      .extracting(IndexPermissions::getProjectUuid)
+      .containsAll(projectUuids);
+  }
+
+  @Test
+  public void return_private_project_without_any_permission_when_no_permission_in_DB() {
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    // no permissions
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupIds()).isEmpty();
+    assertThat(dto.getUserIds()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  @Test
+  public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() {
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupIds()).isEmpty();
+    assertThat(dto.getUserIds()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isTrue();
+    assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier());
+  }
+
+  @Test
+  public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() {
+    dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1);
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupIds()).isEmpty();
+    assertThat(dto.getUserIds()).containsOnly(user1.getId());
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  @Test
+  public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() {
+    dbTester.users().insertMember(group, user1);
+    dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1);
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupIds()).containsOnly(group.getId());
+    assertThat(dto.getUserIds()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  private void isPublic(IndexPermissions view1Authorization, String qualifier) {
+    assertThat(view1Authorization.getGroupIds()).isEmpty();
+    assertThat(view1Authorization.isAllowAnyone()).isTrue();
+    assertThat(view1Authorization.getUserIds()).isEmpty();
+    assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier);
+  }
+
+  private static IndexPermissions getByProjectUuid(String projectUuid, Collection<IndexPermissions> dtos) {
+    return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new);
+  }
+
+  private void insertTestDataForProjectsAndViews() {
+    // user1 has USER access on both private projects
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject);
+    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2);
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1);
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application);
+
+    // user2 has USER access on privateProject1 only
+    userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2);
+
+    // group1 has USER access on privateProject1 only
+    userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java
new file mode 100644 (file)
index 0000000..104ce33
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.Collection;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.web.UserRole.ADMIN;
+import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.server.es.ProjectIndexer.Cause.PERMISSION_CHANGE;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
+
+public class PermissionIndexerTest {
+
+  private static final IndexType INDEX_TYPE_FOO_AUTH = new IndexType(FooIndexDefinition.INDEX_TYPE_FOO.getIndex(), TYPE_AUTHORIZATION);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public EsTester es = EsTester.createCustom(new FooIndexDefinition());
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private FooIndex fooIndex = new FooIndex(es.client(), new WebAuthorizationTypeSupport(userSession));
+  private FooIndexer fooIndexer = new FooIndexer(es.client());
+  private PermissionIndexer underTest = new PermissionIndexer(db.getDbClient(), es.client(), fooIndexer);
+
+  @Test
+  public void indexOnStartup_grants_access_to_any_user_and_to_group_Anyone_on_public_projects() {
+    ComponentDto project = createAndIndexPublicProject();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+
+    indexOnStartup();
+
+    verifyAnyoneAuthorized(project);
+    verifyAuthorized(project, user1);
+    verifyAuthorized(project, user2);
+  }
+
+  @Test
+  public void deletion_resilience_will_deindex_projects() {
+    ComponentDto project1 = createUnindexedPublicProject();
+    ComponentDto project2 = createUnindexedPublicProject();
+    // UserDto user1 = db.users().insertUser();
+    indexOnStartup();
+    assertThat(es.countDocuments(INDEX_TYPE_FOO_AUTH)).isEqualTo(2);
+
+    // Simulate a indexation issue
+    db.getDbClient().componentDao().delete(db.getSession(), project1.getId());
+    underTest.prepareForRecovery(db.getSession(), asList(project1.uuid()), ProjectIndexer.Cause.PROJECT_DELETION);
+    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isEqualTo(1);
+    Collection<EsQueueDto> esQueueDtos = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), Long.MAX_VALUE, 2);
+
+    underTest.index(db.getSession(), esQueueDtos);
+
+    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isEqualTo(0);
+    assertThat(es.countDocuments(INDEX_TYPE_FOO_AUTH)).isEqualTo(1);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_to_user() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    db.users().insertProjectPermissionOnUser(user1, USER, project);
+    db.users().insertProjectPermissionOnUser(user2, ADMIN, project);
+
+    indexOnStartup();
+
+    // anonymous
+    verifyAnyoneNotAuthorized(project);
+
+    // user1 has access
+    verifyAuthorized(project, user1);
+
+    // user2 has not access (only USER permission is accepted)
+    verifyNotAuthorized(project, user2);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_to_group_on_private_project() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    GroupDto group1 = db.users().insertGroup();
+    GroupDto group2 = db.users().insertGroup();
+    db.users().insertProjectPermissionOnGroup(group1, USER, project);
+    db.users().insertProjectPermissionOnGroup(group2, ADMIN, project);
+
+    indexOnStartup();
+
+    // anonymous
+    verifyAnyoneNotAuthorized(project);
+
+    // group1 has access
+    verifyAuthorized(project, user1, group1);
+
+    // group2 has not access (only USER permission is accepted)
+    verifyNotAuthorized(project, user2, group2);
+
+    // user3 is not in any group
+    verifyNotAuthorized(project, user3);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_to_user_and_group() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    GroupDto group = db.users().insertGroup();
+    db.users().insertMember(group, user2);
+    db.users().insertProjectPermissionOnUser(user1, USER, project);
+    db.users().insertProjectPermissionOnGroup(group, USER, project);
+
+    indexOnStartup();
+
+    // anonymous
+    verifyAnyoneNotAuthorized(project);
+
+    // has direct access
+    verifyAuthorized(project, user1);
+
+    // has access through group
+    verifyAuthorized(project, user1, group);
+
+    // no access
+    verifyNotAuthorized(project, user2);
+  }
+
+  @Test
+  public void indexOnStartup_does_not_grant_access_to_anybody_on_private_project() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user = db.users().insertUser();
+    GroupDto group = db.users().insertGroup();
+
+    indexOnStartup();
+
+    verifyAnyoneNotAuthorized(project);
+    verifyNotAuthorized(project, user);
+    verifyNotAuthorized(project, user, group);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_to_anybody_on_public_project() {
+    ComponentDto project = createAndIndexPublicProject();
+    UserDto user = db.users().insertUser();
+    GroupDto group = db.users().insertGroup();
+
+    indexOnStartup();
+
+    verifyAnyoneAuthorized(project);
+    verifyAuthorized(project, user);
+    verifyAuthorized(project, user, group);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_to_anybody_on_view() {
+    ComponentDto view = createAndIndexView();
+    UserDto user = db.users().insertUser();
+    GroupDto group = db.users().insertGroup();
+
+    indexOnStartup();
+
+    verifyAnyoneAuthorized(view);
+    verifyAuthorized(view, user);
+    verifyAuthorized(view, user, group);
+  }
+
+  @Test
+  public void indexOnStartup_grants_access_on_many_projects() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    ComponentDto project = null;
+    for (int i = 0; i < 10; i++) {
+      project = createAndIndexPrivateProject();
+      db.users().insertProjectPermissionOnUser(user1, USER, project);
+    }
+
+    indexOnStartup();
+
+    verifyAnyoneNotAuthorized(project);
+    verifyAuthorized(project, user1);
+    verifyNotAuthorized(project, user2);
+  }
+
+  @Test
+  public void public_projects_are_visible_to_anybody_whatever_the_organization() {
+    ComponentDto projectOnOrg1 = createAndIndexPublicProject(db.organizations().insert());
+    ComponentDto projectOnOrg2 = createAndIndexPublicProject(db.organizations().insert());
+    UserDto user = db.users().insertUser();
+
+    indexOnStartup();
+
+    verifyAnyoneAuthorized(projectOnOrg1);
+    verifyAnyoneAuthorized(projectOnOrg2);
+    verifyAuthorized(projectOnOrg1, user);
+    verifyAuthorized(projectOnOrg2, user);
+  }
+
+  @Test
+  public void indexOnAnalysis_does_nothing_because_CE_does_not_touch_permissions() {
+    ComponentDto project = createAndIndexPublicProject();
+
+    underTest.indexOnAnalysis(project.uuid());
+
+    assertThatAuthIndexHasSize(0);
+    verifyAnyoneNotAuthorized(project);
+  }
+
+  @Test
+  public void permissions_are_not_updated_on_project_tags_update() {
+    ComponentDto project = createAndIndexPublicProject();
+
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
+
+    assertThatAuthIndexHasSize(0);
+    verifyAnyoneNotAuthorized(project);
+  }
+
+  @Test
+  public void permissions_are_not_updated_on_project_key_update() {
+    ComponentDto project = createAndIndexPublicProject();
+
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
+
+    assertThatAuthIndexHasSize(0);
+    verifyAnyoneNotAuthorized(project);
+  }
+
+  @Test
+  public void index_permissions_on_project_creation() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user = db.users().insertUser();
+    db.users().insertProjectPermissionOnUser(user, USER, project);
+
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
+
+    assertThatAuthIndexHasSize(1);
+    verifyAuthorized(project, user);
+  }
+
+  @Test
+  public void index_permissions_on_permission_change() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    db.users().insertProjectPermissionOnUser(user1, USER, project);
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
+    verifyAuthorized(project, user1);
+    verifyNotAuthorized(project, user2);
+
+    db.users().insertProjectPermissionOnUser(user2, USER, project);
+    indexPermissions(project, PERMISSION_CHANGE);
+
+    verifyAuthorized(project, user1);
+    verifyAuthorized(project, user1);
+  }
+
+  @Test
+  public void delete_permissions_on_project_deletion() {
+    ComponentDto project = createAndIndexPrivateProject();
+    UserDto user = db.users().insertUser();
+    db.users().insertProjectPermissionOnUser(user, USER, project);
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_CREATION);
+    verifyAuthorized(project, user);
+
+    db.getDbClient().componentDao().delete(db.getSession(), project.getId());
+    indexPermissions(project, ProjectIndexer.Cause.PROJECT_DELETION);
+
+    verifyNotAuthorized(project, user);
+    assertThatAuthIndexHasSize(0);
+  }
+
+  @Test
+  public void errors_during_indexing_are_recovered() {
+    ComponentDto project = createAndIndexPublicProject();
+    es.lockWrites(INDEX_TYPE_FOO_AUTH);
+
+    IndexingResult result = indexPermissions(project, PERMISSION_CHANGE);
+    assertThat(result.getTotal()).isEqualTo(1L);
+    assertThat(result.getFailures()).isEqualTo(1L);
+
+    // index is still read-only, fail to recover
+    result = recover();
+    assertThat(result.getTotal()).isEqualTo(1L);
+    assertThat(result.getFailures()).isEqualTo(1L);
+    assertThatAuthIndexHasSize(0);
+    assertThatEsQueueTableHasSize(1);
+
+    es.unlockWrites(INDEX_TYPE_FOO_AUTH);
+
+    result = recover();
+    assertThat(result.getTotal()).isEqualTo(1L);
+    assertThat(result.getFailures()).isEqualTo(0L);
+    verifyAnyoneAuthorized(project);
+    assertThatEsQueueTableHasSize(0);
+  }
+
+  private void assertThatAuthIndexHasSize(int expectedSize) {
+    IndexType authIndexType = underTest.getIndexTypes().iterator().next();
+    assertThat(es.countDocuments(authIndexType)).isEqualTo(expectedSize);
+  }
+
+  private void indexOnStartup() {
+    underTest.indexOnStartup(underTest.getIndexTypes());
+  }
+
+  private void verifyAuthorized(ComponentDto project, UserDto user) {
+    logIn(user);
+    verifyAuthorized(project, true);
+  }
+
+  private void verifyAuthorized(ComponentDto project, UserDto user, GroupDto group) {
+    logIn(user).setGroups(group);
+    verifyAuthorized(project, true);
+  }
+
+  private void verifyNotAuthorized(ComponentDto project, UserDto user) {
+    logIn(user);
+    verifyAuthorized(project, false);
+  }
+
+  private void verifyNotAuthorized(ComponentDto project, UserDto user, GroupDto group) {
+    logIn(user).setGroups(group);
+    verifyAuthorized(project, false);
+  }
+
+  private void verifyAnyoneAuthorized(ComponentDto project) {
+    userSession.anonymous();
+    verifyAuthorized(project, true);
+  }
+
+  private void verifyAnyoneNotAuthorized(ComponentDto project) {
+    userSession.anonymous();
+    verifyAuthorized(project, false);
+  }
+
+  private void verifyAuthorized(ComponentDto project, boolean expectedAccess) {
+    assertThat(fooIndex.hasAccessToProject(project.uuid())).isEqualTo(expectedAccess);
+  }
+
+  private UserSessionRule logIn(UserDto u) {
+    userSession.logIn(u.getLogin()).setUserId(u.getId());
+    return userSession;
+  }
+
+  private IndexingResult indexPermissions(ComponentDto project, ProjectIndexer.Cause cause) {
+    DbSession dbSession = db.getSession();
+    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
+    dbSession.commit();
+    return underTest.index(dbSession, items);
+  }
+
+  private ComponentDto createUnindexedPublicProject() {
+    ComponentDto project = db.components().insertPublicProject();
+    return project;
+  }
+
+  private ComponentDto createAndIndexPrivateProject() {
+    ComponentDto project = db.components().insertPrivateProject();
+    fooIndexer.indexOnAnalysis(project.uuid());
+    return project;
+  }
+
+  private ComponentDto createAndIndexPublicProject() {
+    ComponentDto project = db.components().insertPublicProject();
+    fooIndexer.indexOnAnalysis(project.uuid());
+    return project;
+  }
+
+  private ComponentDto createAndIndexView() {
+    ComponentDto view = db.components().insertView();
+    fooIndexer.indexOnAnalysis(view.uuid());
+    return view;
+  }
+
+  private ComponentDto createAndIndexPublicProject(OrganizationDto org) {
+    ComponentDto project = db.components().insertPublicProject(org);
+    fooIndexer.indexOnAnalysis(project.uuid());
+    return project;
+  }
+
+  private IndexingResult recover() {
+    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
+    return underTest.index(db.getSession(), items);
+  }
+
+  private void assertThatEsQueueTableHasSize(int expectedSize) {
+    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java
new file mode 100644 (file)
index 0000000..efe1903
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+
+import static java.util.Arrays.asList;
+
+public class PermissionIndexerTester {
+
+  private final PermissionIndexer permissionIndexer;
+
+  public PermissionIndexerTester(EsTester esTester, NeedAuthorizationIndexer indexer, NeedAuthorizationIndexer... others) {
+    NeedAuthorizationIndexer[] indexers = Stream.concat(Stream.of(indexer), Arrays.stream(others)).toArray(NeedAuthorizationIndexer[]::new);
+    this.permissionIndexer = new PermissionIndexer(null, esTester.client(), indexers);
+  }
+
+  public PermissionIndexerTester allowOnlyAnyone(ComponentDto project) {
+    IndexPermissions dto = new IndexPermissions(project.uuid(), project.qualifier());
+    dto.allowAnyone();
+    permissionIndexer.index(asList(dto));
+    return this;
+  }
+
+  public PermissionIndexerTester allowOnlyUser(ComponentDto project, UserDto user) {
+    IndexPermissions dto = new IndexPermissions(project.uuid(), project.qualifier())
+      .addUserId(user.getId());
+    permissionIndexer.index(asList(dto));
+    return this;
+  }
+
+  public PermissionIndexerTester allowOnlyGroup(ComponentDto project, GroupDto group) {
+    IndexPermissions dto = new IndexPermissions(project.uuid(), project.qualifier())
+      .addGroupId(group.getId());
+    permissionIndexer.index(asList(dto));
+    return this;
+  }
+
+  public PermissionIndexerTester allow(IndexPermissions access) {
+    permissionIndexer.index(asList(access));
+    return this;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/WebAuthorizationTypeSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/WebAuthorizationTypeSupportTest.java
new file mode 100644 (file)
index 0000000..ab4d016
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import org.elasticsearch.index.query.MatchAllQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.join.query.HasParentQueryBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.GroupTesting;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class WebAuthorizationTypeSupportTest {
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private WebAuthorizationTypeSupport underTest = new WebAuthorizationTypeSupport(userSession);
+
+  @Test
+  public void createQueryFilter_does_not_include_permission_filters_if_user_is_flagged_as_root() {
+    userSession.logIn().setRoot();
+
+    QueryBuilder filter = underTest.createQueryFilter();
+
+    assertThat(filter).isInstanceOf(MatchAllQueryBuilder.class);
+  }
+
+  @Test
+  public void createQueryFilter_sets_filter_on_anyone_group_if_user_is_anonymous() {
+    userSession.anonymous();
+
+    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+    assertJson(filter.toString()).isSimilarTo("{" +
+        "  \"has_parent\" : {" +
+        "    \"query\" : {" +
+        "      \"bool\" : {" +
+        "        \"filter\" : [{" +
+        "          \"bool\" : {" +
+        "            \"should\" : [{" +
+        "              \"term\" : {" +
+        "                \"allowAnyone\" : {\"value\": true}" +
+        "              }" +
+        "            }]" +
+        "          }" +
+        "        }]" +
+        "      }" +
+        "    }," +
+        "    \"parent_type\" : \"authorization\"" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void createQueryFilter_sets_filter_on_anyone_and_user_id_if_user_is_logged_in_but_has_no_groups() {
+    userSession.logIn().setUserId(1234);
+
+    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+    assertJson(filter.toString()).isSimilarTo("{" +
+        "  \"has_parent\": {" +
+        "    \"query\": {" +
+        "      \"bool\": {" +
+        "        \"filter\": [{" +
+        "          \"bool\": {" +
+        "            \"should\": [" +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"allowAnyone\": {\"value\": true}" +
+        "                }" +
+        "              }," +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"userIds\": {\"value\": 1234}" +
+        "                }" +
+        "              }" +
+        "            ]" +
+        "          }" +
+        "        }]" +
+        "      }" +
+        "    }," +
+        "    \"parent_type\": \"authorization\"" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void createQueryFilter_sets_filter_on_anyone_and_user_id_and_group_ids_if_user_is_logged_in_and_has_groups() {
+    GroupDto group1 = GroupTesting.newGroupDto().setId(10);
+    GroupDto group2 = GroupTesting.newGroupDto().setId(11);
+    userSession.logIn().setUserId(1234).setGroups(group1, group2);
+
+    HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+    assertJson(filter.toString()).isSimilarTo("{" +
+        "  \"has_parent\": {" +
+        "    \"query\": {" +
+        "      \"bool\": {" +
+        "        \"filter\": [{" +
+        "          \"bool\": {" +
+        "            \"should\": [" +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"allowAnyone\": {\"value\": true}" +
+        "                }" +
+        "              }," +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"userIds\": {\"value\": 1234}" +
+        "                }" +
+        "              }," +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"groupIds\": {\"value\": 10}" +
+        "                }" +
+        "              }," +
+        "              {" +
+        "                \"term\": {" +
+        "                  \"groupIds\": {\"value\": 11}" +
+        "                }" +
+        "              }" +
+        "            ]" +
+        "          }" +
+        "        }]" +
+        "      }" +
+        "    }," +
+        "    \"parent_type\": \"authorization\"" +
+        "  }" +
+        "}");
+  }
+}
index b186377f5640d0d84ff038de4fc333a10dfce1ea..c86710a80970fbeb97a11b030b95388adf25951a 100644 (file)
@@ -34,9 +34,9 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.measure.index.ProjectMeasuresDoc;
 import org.sonar.server.measure.index.ProjectMeasuresIndex;
 import org.sonar.server.measure.index.ProjectMeasuresIndexer;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.permission.index.PermissionIndexerDao;
+import org.sonar.server.permission.index.IndexPermissions;
 import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -62,7 +62,7 @@ public class SearchActionTest {
 
   private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
   private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, projectMeasureIndexer);
-  private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new AuthorizationTypeSupport(userSession), System2.INSTANCE);
+  private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
 
   private WsActionTester ws = new WsActionTester(new SearchAction(index));
 
@@ -110,7 +110,7 @@ public class SearchActionTest {
   private void index(ProjectMeasuresDoc... docs) {
     es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
     for (ProjectMeasuresDoc doc : docs) {
-      PermissionIndexerDao.Dto access = new PermissionIndexerDao.Dto(doc.getId(), Qualifiers.PROJECT);
+      IndexPermissions access = new IndexPermissions(doc.getId(), Qualifiers.PROJECT);
       access.allowAnyone();
       authorizationIndexerTester.allow(access);
     }
index d87519555eeefdecbe7fd7db99b8af3337cc8110..b8f6acf4e8fcf5b4238513ed45577b6fb3620f3c 100644 (file)
@@ -46,8 +46,8 @@ import org.sonar.server.es.StartupIndexer;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
 import org.sonar.server.permission.index.PermissionIndexer;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 
@@ -71,7 +71,7 @@ public class ShowActionTest {
 
   private DbClient dbClient = db.getDbClient();
   private DbSession session = db.getSession();
-  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
+  private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
   private WsActionTester ws = new WsActionTester(new ShowAction(userSessionRule, TestComponentFinder.from(db), issueIndex, dbClient));
   private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);