]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11791 use single type ES indices
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 20 Feb 2019 17:46:05 +0000 (18:46 +0100)
committerSonarTech <sonartech@sonarsource.com>
Tue, 19 Mar 2019 19:21:19 +0000 (20:21 +0100)
146 files changed:
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepTest.java
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/component/index/ComponentIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/es/BaseDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/es/BulkIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettings.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/es/DocId.java
server/sonar-server-common/src/main/java/org/sonar/server/es/EsClient.java
server/sonar-server-common/src/main/java/org/sonar/server/es/Index.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/IndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/es/IndexDefinitionHash.java
server/sonar-server-common/src/main/java/org/sonar/server/es/IndexType.java
server/sonar-server-common/src/main/java/org/sonar/server/es/NewIndex.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/es/OneToOneResilientIndexingListener.java
server/sonar-server-common/src/main/java/org/sonar/server/es/metadata/MetadataIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/es/metadata/MetadataIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/BuiltIndex.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettings.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettingsElement.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/FieldAware.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/KeywordFieldBuilder.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NestedFieldBuilder.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewAuthorizedIndex.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewIndex.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewRegularIndex.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/SettingsConfiguration.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/StringFieldBuilder.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TextFieldBuilder.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TypeMapping.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/es/request/ProxyClusterHealthRequestBuilder.java
server/sonar-server-common/src/main/java/org/sonar/server/es/request/ProxyCreateIndexRequestBuilder.java
server/sonar-server-common/src/main/java/org/sonar/server/es/request/ProxyIndicesStatsRequestBuilder.java
server/sonar-server-common/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeatureRepertoire.java
server/sonar-server-common/src/main/java/org/sonar/server/es/textsearch/JavaTokenizer.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
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/IssueIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationDoc.java [new file with mode: 0644]
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/IndexAuthorizationConstants.java
server/sonar-server-common/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleExtensionDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/user/index/UserDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/user/index/UserIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/user/index/UserIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/user/index/UserIndexer.java
server/sonar-server-common/src/main/java/org/sonar/server/view/index/ViewDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/view/index/ViewIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/view/index/ViewIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/view/index/ViewIndexer.java
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/BulkIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/DefaultIndexSettingsTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/DocIdTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/EsClientTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/EsTester.java
server/sonar-server-common/src/test/java/org/sonar/server/es/FakeDoc.java
server/sonar-server-common/src/test/java/org/sonar/server/es/FakeIndexDefinition.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/IndexDefinitionContextTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/IndexDefinitionHashTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/IndexTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/IndexTypeTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/NewIndexSettingsConfigurationTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/NewIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/OneToManyResilientIndexingListenerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/OneToOneResilientIndexingListenerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/metadata/MetadataIndexTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FakeIndexDefinition.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FieldAwareTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewAuthorizedIndexTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewIndexTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewRegularIndexTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/TestNewIndex.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyCreateIndexRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyDeleteRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyGetRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyIndexRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyIndicesExistsRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyIndicesStatsRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyNodesStatsRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyPutMappingRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyRefreshRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxySearchRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxySearchScrollRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/request/ProxyWebServerHealthRequestBuilderTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexDefinitionTest.java
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/ProjectMeasuresIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationDocTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexDefinitionTest.java
server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexTest.java
server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexDefinitionTest.java
server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexTest.java
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
server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
server/sonar-server/src/main/java/org/sonar/server/es/IndexCreator.java
server/sonar-server/src/main/java/org/sonar/server/es/IndexDefinitions.java
server/sonar-server/src/main/java/org/sonar/server/es/IndexerStartupTask.java
server/sonar-server/src/main/java/org/sonar/server/es/RecoveryIndexer.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
server/sonar-server/src/main/java/org/sonar/server/platform/BackendCleanup.java
server/sonar-server/src/test/java/org/sonar/server/es/IndexCreatorTest.java
server/sonar-server/src/test/java/org/sonar/server/es/IndexerStartupTaskTest.java
server/sonar-server/src/test/java/org/sonar/server/es/MigrationEsClientImplTest.java
server/sonar-server/src/test/java/org/sonar/server/es/RecoveryIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/es/metadata/EsDbCompatibilityImplTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTextSearchTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/MemberUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndex.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexDefinition.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/FooIndexer.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/WebAuthorizationTypeSupportTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/BackendCleanupTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImplTest.java
server/sonar-server/src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-server/src/test/java/org/sonar/server/search/BaseDocTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java

index 78da2be751ee3bf04fd2b55f5c99d444c98042ea..e01c4259c913af02fbac528945f638a3d8a65b02 100644 (file)
@@ -94,8 +94,8 @@ public class PersistAdHocRulesStepTest extends BaseStepTest {
     assertThat(reloaded.getSeverity()).isNull();
     assertThat(reloaded.getName()).isEqualTo("eslint:no-cond-assign");
 
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1l);
-    assertThat(es.getDocuments(RuleIndexDefinition.INDEX_TYPE_RULE).iterator().next().getId()).isEqualTo(Integer.toString(reloaded.getId()));
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(1l);
+    assertThat(es.getDocuments(RuleIndexDefinition.TYPE_RULE).iterator().next().getId()).isEqualTo(Integer.toString(reloaded.getId()));
   }
 
   @Test
@@ -108,7 +108,7 @@ public class PersistAdHocRulesStepTest extends BaseStepTest {
 
     RuleDao ruleDao = dbClient.ruleDao();
     assertThat(ruleDao.selectAllDefinitions(dbClient.openSession(false))).hasSize(1);
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isZero();
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isZero();
   }
 
 }
index 0a6128ef1fac6ed74dc7345e1bd44f51c66f1c30..5ed6873b580213b34a86c84bff0ea54552699443 100644 (file)
@@ -24,6 +24,7 @@ import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.server.es.BaseDoc;
+import org.sonar.server.permission.index.AuthorizationDoc;
 
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_KEY;
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_LANGUAGE;
@@ -32,15 +33,16 @@ import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_OR
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_PROJECT_UUID;
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_QUALIFIER;
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_UUID;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
 
 public class ComponentDoc extends BaseDoc {
 
   public ComponentDoc() {
-    super(new HashMap<>(6));
+    super(TYPE_COMPONENT, new HashMap<>(6));
   }
 
   public ComponentDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_COMPONENT, fields);
   }
 
   @Override
@@ -48,16 +50,6 @@ public class ComponentDoc extends BaseDoc {
     return getField(FIELD_UUID);
   }
 
-  @Override
-  public String getRouting() {
-    return getProjectUuid();
-  }
-
-  @Override
-  public String getParent() {
-    return getProjectUuid();
-  }
-
   public ComponentDoc setId(String s) {
     setField(FIELD_UUID, s);
     return this;
@@ -69,6 +61,7 @@ public class ComponentDoc extends BaseDoc {
 
   public ComponentDoc setProjectUuid(String s) {
     setField(FIELD_PROJECT_UUID, s);
+    setParent(AuthorizationDoc.idOf(s));
     return this;
   }
 
index 6593e57f35d48fe2b4b9066c03aeb0f1c815f9ee..42916f12d2906072dff2d60c76bfe3823496a2fa 100644 (file)
 package org.sonar.server.component.index;
 
 import org.sonar.api.config.Configuration;
-import org.sonar.server.es.DefaultIndexSettingsElement;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.newindex.DefaultIndexSettingsElement;
+import org.sonar.server.es.newindex.NewAuthorizedIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_PREFIX_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER;
-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;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_PREFIX_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 public class ComponentIndexDefinition implements IndexDefinition {
 
-  public static final IndexType INDEX_TYPE_COMPONENT = new IndexType("components", "component");
+  public static final Index DESCRIPTOR = Index.withRelations("components");
+  public static final IndexType.IndexRelationType TYPE_COMPONENT = IndexType.relation(IndexType.main(DESCRIPTOR, TYPE_AUTHORIZATION), "component");
   public static final String FIELD_UUID = "uuid";
   public static final String FIELD_PROJECT_UUID = "project_uuid";
   public static final String FIELD_ORGANIZATION_UUID = "organization_uuid";
@@ -48,23 +53,36 @@ public class ComponentIndexDefinition implements IndexDefinition {
   static final DefaultIndexSettingsElement[] NAME_ANALYZERS = {SORTABLE_ANALYZER, SEARCH_PREFIX_ANALYZER, SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER, SEARCH_GRAMS_ANALYZER};
 
   private final Configuration config;
+  private final boolean enableSource;
 
-  public ComponentIndexDefinition(Configuration config) {
+  private ComponentIndexDefinition(Configuration config, boolean enableSource) {
     this.config = config;
+    this.enableSource = enableSource;
+  }
+
+  public ComponentIndexDefinition(Configuration config) {
+    this(config, false);
+  }
+
+  /**
+   * Keep the document sources in index so that indexer tests can verify content
+   * of indexed documents.
+   */
+  public static ComponentIndexDefinition createForTest() {
+    return new ComponentIndexDefinition(new MapSettings().asConfig(), true);
   }
 
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_COMPONENT.getIndex(),
+    NewAuthorizedIndex index = context.createWithAuthorization(
+      DESCRIPTOR,
       newBuilder(config)
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         .setDefaultNbOfShards(DEFAULT_NUMBER_OF_SHARDS)
-        .build());
-
-    NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_COMPONENT.getType())
-      .requireProjectAuthorization();
+        .build())
+      .setEnableSource(enableSource);
 
+    TypeMapping mapping = index.createTypeMapping(TYPE_COMPONENT);
     mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_PROJECT_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_KEY).addSubFields(SORTABLE_ANALYZER).build();
index d4f4901aa9b06fe44cef6f0f4ca5f9e413148dca..15a400b3797ee4c45a9ddac212580b02157828f7 100644 (file)
@@ -27,7 +27,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import javax.annotation.Nullable;
-import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.sonar.core.util.stream.MoreCollectors;
@@ -35,6 +34,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.es.BaseDoc;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.BulkIndexer.Size;
 import org.sonar.server.es.EsClient;
@@ -42,16 +42,17 @@ import org.sonar.server.es.IndexType;
 import org.sonar.server.es.IndexingResult;
 import org.sonar.server.es.OneToManyResilientIndexingListener;
 import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.permission.index.AuthorizationDoc;
 import org.sonar.server.permission.index.AuthorizationScope;
 import org.sonar.server.permission.index.NeedAuthorizationIndexer;
 
 import static java.util.Collections.emptyList;
-import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_TYPE_COMPONENT;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
 
 public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
-  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_COMPONENT, project -> true);
-  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(INDEX_TYPE_COMPONENT);
+  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(TYPE_COMPONENT, project -> true);
+  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(TYPE_COMPONENT);
 
   private final DbClient dbClient;
   private final EsClient esClient;
@@ -94,7 +95,7 @@ public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexe
       case PROJECT_DELETION:
       case PROJECT_KEY_UPDATE:
         List<EsQueueDto> items = projectUuids.stream()
-          .map(branchUuid -> EsQueueDto.create(INDEX_TYPE_COMPONENT.format(), branchUuid, null, branchUuid))
+          .map(branchUuid -> EsQueueDto.create(TYPE_COMPONENT.format(), branchUuid, null, branchUuid))
           .collect(MoreCollectors.toArrayList(projectUuids.size()));
         return dbClient.esQueueDao().insert(dbSession, items);
 
@@ -111,7 +112,7 @@ public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexe
     }
 
     OneToManyResilientIndexingListener listener = new OneToManyResilientIndexingListener(dbClient, dbSession, items);
-    BulkIndexer bulkIndexer = new BulkIndexer(esClient, INDEX_TYPE_COMPONENT, Size.REGULAR, listener);
+    BulkIndexer bulkIndexer = new BulkIndexer(esClient, TYPE_COMPONENT, Size.REGULAR, listener);
     bulkIndexer.start();
     Set<String> branchUuids = items.stream().map(EsQueueDto::getDocId).collect(MoreCollectors.toHashSet(items.size()));
     Set<String> remaining = new HashSet<>(branchUuids);
@@ -120,7 +121,7 @@ public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexe
       // TODO allow scrolling multiple projects at the same time
       dbClient.componentDao().scrollForIndexing(dbSession, branchUuid, context -> {
         ComponentDto dto = context.getResultObject();
-        bulkIndexer.add(newIndexRequest(toDocument(dto)));
+        bulkIndexer.add(toDocument(dto).toIndexRequest());
         remaining.remove(dto.projectUuid());
       });
     }
@@ -137,51 +138,44 @@ public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexe
    * <b>Warning:</b> only use {@code null} during startup.
    */
   private void doIndexByProjectUuid(@Nullable String projectUuid, Size bulkSize) {
-    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_TYPE_COMPONENT, bulkSize);
+    BulkIndexer bulk = new BulkIndexer(esClient, TYPE_COMPONENT, bulkSize);
 
     bulk.start();
     try (DbSession dbSession = dbClient.openSession(false)) {
       dbClient.componentDao()
         .scrollForIndexing(dbSession, projectUuid, context -> {
           ComponentDto dto = context.getResultObject();
-          bulk.add(newIndexRequest(toDocument(dto)));
+          bulk.add(toDocument(dto).toIndexRequest());
         });
     }
     bulk.stop();
   }
 
   private void addProjectDeletionToBulkIndexer(BulkIndexer bulkIndexer, String projectUuid) {
-    SearchRequestBuilder searchRequest = esClient.prepareSearch(INDEX_TYPE_COMPONENT)
+    SearchRequestBuilder searchRequest = esClient.prepareSearch(TYPE_COMPONENT.getMainType())
       .setQuery(QueryBuilders.termQuery(ComponentIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
-      .setRouting(projectUuid);
+      .setRouting(AuthorizationDoc.idOf(projectUuid));
     bulkIndexer.addDeletion(searchRequest);
   }
 
   public void delete(String projectUuid, Collection<String> disabledComponentUuids) {
-    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_TYPE_COMPONENT, Size.REGULAR);
+    BulkIndexer bulk = new BulkIndexer(esClient, TYPE_COMPONENT, Size.REGULAR);
     bulk.start();
-    disabledComponentUuids.forEach(uuid -> bulk.addDeletion(INDEX_TYPE_COMPONENT, uuid, projectUuid));
+    disabledComponentUuids.forEach(uuid -> bulk.addDeletion(TYPE_COMPONENT, uuid, AuthorizationDoc.idOf(projectUuid)));
     bulk.stop();
   }
 
   @VisibleForTesting
   void index(ComponentDto... docs) {
-    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_TYPE_COMPONENT, Size.REGULAR);
+    BulkIndexer bulk = new BulkIndexer(esClient, TYPE_COMPONENT, Size.REGULAR);
     bulk.start();
     Arrays.stream(docs)
       .map(ComponentIndexer::toDocument)
-      .map(ComponentIndexer::newIndexRequest)
+      .map(BaseDoc::toIndexRequest)
       .forEach(bulk::add);
     bulk.stop();
   }
 
-  private static IndexRequest newIndexRequest(ComponentDoc doc) {
-    return new IndexRequest(INDEX_TYPE_COMPONENT.getIndex(), INDEX_TYPE_COMPONENT.getType(), doc.getId())
-      .routing(doc.getRouting())
-      .parent(doc.getParent())
-      .source(doc.getFields());
-  }
-
   public static ComponentDoc toDocument(ComponentDto component) {
     return new ComponentDoc()
       .setId(component.uuid())
index 7fa5f275f68d0ca31a20df2799eb4d7d9e112d14..512f151d0f3e33c98d5bc9593e8ff5cc92a978e1 100644 (file)
  */
 package org.sonar.server.es;
 
+import com.google.common.collect.ImmutableMap;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.elasticsearch.action.index.IndexRequest;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
 
 /**
  * Base implementation for business objects based on elasticsearch document
  */
 public abstract class BaseDoc {
 
+  private static final String SETPARENT_NOT_CALLED = "parent must be set on a doc associated to a IndexRelationType (see BaseDoc#setParent(String))";
+  private final IndexType indexType;
+  private String parentId = null;
   protected final Map<String, Object> fields;
 
-  protected BaseDoc() {
-    this.fields = new HashMap<>();
+  protected BaseDoc(IndexType indexType) {
+    this(indexType, new HashMap<>());
   }
 
-  protected BaseDoc(Map<String, Object> fields) {
+  protected BaseDoc(IndexType indexType, Map<String, Object> fields) {
+    this.indexType = indexType;
     this.fields = fields;
+    if (indexType instanceof IndexMainType) {
+      IndexMainType mainType = (IndexMainType) indexType;
+      if (mainType.getIndex().acceptsRelations()) {
+        setField(mainType.getIndex().getJoinField(), ImmutableMap.of("name", mainType.getType()));
+        setField(FIELD_INDEX_TYPE, mainType.getType());
+      }
+    }
+  }
+
+  protected void setParent(String parentId) {
+    checkState(this.indexType instanceof IndexRelationType, "Doc must be associated to a IndexRelationType to set a parent");
+    checkArgument(parentId != null && !parentId.isEmpty(), "parentId can't be null nor empty");
+    this.parentId = parentId;
+    IndexRelationType indexRelationType = (IndexRelationType) this.indexType;
+    setField(indexRelationType.getMainType().getIndex().getJoinField(), ImmutableMap.of("name", indexRelationType.getName(), "parent", parentId));
+    setField(FIELD_INDEX_TYPE, indexRelationType.getName());
   }
 
   public abstract String getId();
 
-  @CheckForNull
-  public abstract String getRouting();
+  public Optional<String> getRouting() {
+    // when using relations, routing MUST be defined and MUST be the id of the parent
+    if (this.indexType instanceof IndexRelationType) {
+      ensureSetParentCalled();
+      return Optional.of(this.parentId);
+    }
+    if (this.indexType instanceof IndexMainType && indexType.getMainType().getIndex().acceptsRelations()) {
+      return Optional.of(getId());
+    }
+    return getSimpleMainTypeRouting();
+  }
 
-  @CheckForNull
-  public abstract String getParent();
+  /**
+   * Intended to be overridden by subclass which wants to define a routing and which indexType is a {@link IndexMainType}
+   * (if not a {@link IndexMainType}, this method will never be called).
+   */
+  protected Optional<String> getSimpleMainTypeRouting() {
+    return Optional.empty();
+  }
 
   /**
    * Use this method when field value can be null. See warning in {@link #getField(String)}
@@ -64,7 +107,7 @@ public abstract class BaseDoc {
     Object val = getNullableField(key);
     if (val != null) {
       if (val instanceof Date) {
-        return (Date)val;
+        return (Date) val;
       }
       if (val instanceof Number) {
         return epochSecondsToDate((Number) val);
@@ -94,22 +137,37 @@ public abstract class BaseDoc {
   public Date getFieldAsDate(String key) {
     Object value = getField(key);
     if (value instanceof Date) {
-      return (Date)value;
+      return (Date) value;
     }
     if (value instanceof Number) {
       return epochSecondsToDate((Number) value);
     }
-    return EsUtils.parseDateTime((String)value);
+    return EsUtils.parseDateTime((String) value);
   }
 
   public void setField(String key, @Nullable Object value) {
     fields.put(key, value);
   }
 
-  public Map<String, Object> getFields() {
+  public final Map<String, Object> getFields() {
+    if (indexType instanceof IndexRelationType) {
+      ensureSetParentCalled();
+    }
     return fields;
   }
 
+  private void ensureSetParentCalled() {
+    checkState(this.parentId != null, SETPARENT_NOT_CALLED);
+  }
+
+  public IndexRequest toIndexRequest() {
+    IndexMainType mainType = this.indexType.getMainType();
+    return new IndexRequest(mainType.getIndex().getName(), mainType.getType())
+      .id(getId())
+      .routing(getRouting().orElse(null))
+      .source(getFields());
+  }
+
   public static long epochMillisToEpochSeconds(long epochMillis) {
     return epochMillis / 1000L;
   }
index 671530504bd9dd4fd202b12d821120b97706388b..3db7df268b9dd75e7586cacfe81b13ced995604a 100644 (file)
@@ -117,7 +117,7 @@ public class BulkIndexer {
       Thread.currentThread().interrupt();
       throw new IllegalStateException("Elasticsearch bulk requests still being executed after 1 minute", e);
     }
-    client.prepareRefresh(indexType.getIndex()).get();
+    client.prepareRefresh(indexType.getMainType().getIndex()).get();
     sizeHandler.afterStop(this);
     indexingListener.onFinish(result);
     return result;
@@ -384,21 +384,22 @@ public class BulkIndexer {
 
     @Override
     void beforeStart(BulkIndexer bulkIndexer) {
-      this.progress = new ProgressLogger(format("Progress[BulkIndexer[%s]]", bulkIndexer.indexType.getIndex()), bulkIndexer.result.total, LOGGER)
+      String index = bulkIndexer.indexType.getMainType().getIndex().getName();
+      this.progress = new ProgressLogger(format("Progress[BulkIndexer[%s]]", index), bulkIndexer.result.total, LOGGER)
         .setPluralLabel("requests");
       this.progress.start();
       Map<String, Object> temporarySettings = new HashMap<>();
-      GetSettingsResponse settingsResp = bulkIndexer.client.nativeClient().admin().indices().prepareGetSettings(bulkIndexer.indexType.getIndex()).get();
+      GetSettingsResponse settingsResp = bulkIndexer.client.nativeClient().admin().indices().prepareGetSettings(index).get();
 
       // deactivate replicas
-      int initialReplicas = Integer.parseInt(settingsResp.getSetting(bulkIndexer.indexType.getIndex(), IndexMetaData.SETTING_NUMBER_OF_REPLICAS));
+      int initialReplicas = Integer.parseInt(settingsResp.getSetting(index, IndexMetaData.SETTING_NUMBER_OF_REPLICAS));
       if (initialReplicas > 0) {
         initialSettings.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, initialReplicas);
         temporarySettings.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0);
       }
 
       // deactivate periodical refresh
-      String refreshInterval = settingsResp.getSetting(bulkIndexer.indexType.getIndex(), REFRESH_INTERVAL_SETTING);
+      String refreshInterval = settingsResp.getSetting(index, REFRESH_INTERVAL_SETTING);
       initialSettings.put(REFRESH_INTERVAL_SETTING, refreshInterval);
       temporarySettings.put(REFRESH_INTERVAL_SETTING, "-1");
 
@@ -410,14 +411,14 @@ public class BulkIndexer {
       // optimize lucene segments and revert index settings
       // Optimization must be done before re-applying replicas:
       // http://www.elasticsearch.org/blog/performance-considerations-elasticsearch-indexing/
-      bulkIndexer.client.prepareForceMerge(bulkIndexer.indexType.getIndex()).get();
+      bulkIndexer.client.prepareForceMerge(bulkIndexer.indexType.getMainType().getIndex().getName()).get();
 
       updateSettings(bulkIndexer, initialSettings);
       this.progress.stop();
     }
 
     private static void updateSettings(BulkIndexer bulkIndexer, Map<String, Object> settings) {
-      UpdateSettingsRequestBuilder req = bulkIndexer.client.nativeClient().admin().indices().prepareUpdateSettings(bulkIndexer.indexType.getIndex());
+      UpdateSettingsRequestBuilder req = bulkIndexer.client.nativeClient().admin().indices().prepareUpdateSettings(bulkIndexer.indexType.getMainType().getIndex().getName());
       req.setSettings(settings);
       req.get();
     }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettings.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettings.java
deleted file mode 100644 (file)
index 2513e33..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.es;
-
-import java.util.Arrays;
-import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
-import org.elasticsearch.cluster.metadata.IndexMetaData;
-import org.elasticsearch.common.settings.Settings;
-
-public class DefaultIndexSettings {
-
-  /** Minimum length of ngrams. */
-  public static final int MINIMUM_NGRAM_LENGTH = 2;
-  /** Maximum length of ngrams. */
-  public static final int MAXIMUM_NGRAM_LENGTH = 15;
-
-  /** Pattern, that splits the user search input **/
-  public static final String SEARCH_TERM_TOKENIZER_PATTERN = "[\\s]+";
-
-  public static final String ANALYSIS = "index.analysis";
-  public static final String DELIMITER = ".";
-
-  public static final String TOKENIZER = "tokenizer";
-  public static final String FILTER = "filter";
-  public static final String CHAR_FILTER = "char_filter";
-  public static final String ANALYZER = "analyzer";
-  public static final String SEARCH_ANALYZER = "search_analyzer";
-
-  public static final String TYPE = "type";
-  public static final String INDEX = "index";
-  public static final String INDEX_SEARCHABLE = "true";
-  public static final String INDEX_NOT_SEARCHABLE = "false";
-  public static final String FIELD_TYPE_TEXT = "text";
-  public static final String FIELD_TYPE_KEYWORD = "keyword";
-  public static final String NORMS = "norms";
-  public static final String STORE = "store";
-  public static final String FIELD_FIELDDATA = "fielddata";
-  public static final String FIELDDATA_ENABLED = "true";
-  public static final String FIELD_TERM_VECTOR = "term_vector";
-  public static final String STANDARD = "standard";
-  public static final String PATTERN = "pattern";
-  public static final String CUSTOM = "custom";
-  public static final String KEYWORD = "keyword";
-  public static final String CLASSIC = "classic";
-  public static final RefreshPolicy REFRESH_IMMEDIATE = RefreshPolicy.IMMEDIATE;
-  public static final RefreshPolicy REFRESH_NONE = RefreshPolicy.NONE;
-
-  public static final String TRUNCATE = "truncate";
-
-  public static final String SUB_FIELD_DELIMITER = ".";
-  public static final String TRIM = "trim";
-  public static final String LOWERCASE = "lowercase";
-  public static final String WHITESPACE = "whitespace";
-  public static final String STOP = "stop";
-  public static final String ASCIIFOLDING = "asciifolding";
-  public static final String PORTER_STEM = "porter_stem";
-  public static final String MIN_GRAM = "min_gram";
-  public static final String MAX_GRAM = "max_gram";
-  public static final String LENGTH = "length";
-  public static final String HTML_STRIP = "html_strip";
-
-  private DefaultIndexSettings() {
-    // only static stuff
-  }
-
-  static Settings.Builder defaults() {
-    Settings.Builder builder = Settings.builder()
-      .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
-      .put("index.refresh_interval", "30s")
-      .put("index.mapper.dynamic", false);
-
-    Arrays.stream(DefaultIndexSettingsElement.values())
-      .map(DefaultIndexSettingsElement::settings)
-      .forEach(builder::put);
-
-    return builder;
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java
deleted file mode 100644 (file)
index 31ccdd0..0000000
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.es;
-
-import com.google.common.collect.ImmutableSortedMap;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.SortedMap;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.settings.Settings.Builder;
-
-import static org.sonar.server.es.DefaultIndexSettings.ANALYSIS;
-import static org.sonar.server.es.DefaultIndexSettings.ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettings.ASCIIFOLDING;
-import static org.sonar.server.es.DefaultIndexSettings.CHAR_FILTER;
-import static org.sonar.server.es.DefaultIndexSettings.DELIMITER;
-import static org.sonar.server.es.DefaultIndexSettings.FIELDDATA_ENABLED;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_FIELDDATA;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_TYPE_TEXT;
-import static org.sonar.server.es.DefaultIndexSettings.FILTER;
-import static org.sonar.server.es.DefaultIndexSettings.HTML_STRIP;
-import static org.sonar.server.es.DefaultIndexSettings.INDEX;
-import static org.sonar.server.es.DefaultIndexSettings.INDEX_SEARCHABLE;
-import static org.sonar.server.es.DefaultIndexSettings.KEYWORD;
-import static org.sonar.server.es.DefaultIndexSettings.LOWERCASE;
-import static org.sonar.server.es.DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH;
-import static org.sonar.server.es.DefaultIndexSettings.MAX_GRAM;
-import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
-import static org.sonar.server.es.DefaultIndexSettings.MIN_GRAM;
-import static org.sonar.server.es.DefaultIndexSettings.PATTERN;
-import static org.sonar.server.es.DefaultIndexSettings.PORTER_STEM;
-import static org.sonar.server.es.DefaultIndexSettings.SEARCH_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettings.STANDARD;
-import static org.sonar.server.es.DefaultIndexSettings.STOP;
-import static org.sonar.server.es.DefaultIndexSettings.SUB_FIELD_DELIMITER;
-import static org.sonar.server.es.DefaultIndexSettings.TOKENIZER;
-import static org.sonar.server.es.DefaultIndexSettings.TRIM;
-import static org.sonar.server.es.DefaultIndexSettings.TYPE;
-import static org.sonar.server.es.DefaultIndexSettings.WHITESPACE;
-
-public enum DefaultIndexSettingsElement {
-
-  // Filters
-
-  WORD_FILTER(FILTER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, "word_delimiter");
-      set("generate_word_parts", true);
-      set("catenate_words", true);
-      set("catenate_numbers", true);
-      set("catenate_all", true);
-      set("split_on_case_change", true);
-      set("preserve_original", true);
-      set("split_on_numerics", true);
-      set("stem_english_possessive", true);
-    }
-  },
-  NGRAM_FILTER(FILTER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, "nGram");
-      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
-      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
-      setArray("token_chars", "letter", "digit", "punctuation", "symbol");
-    }
-  },
-
-  // Tokenizers
-
-  GRAM_TOKENIZER(TOKENIZER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, "nGram");
-      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
-      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
-      setArray("token_chars", "letter", "digit", "punctuation", "symbol");
-    }
-  },
-  PREFIX_TOKENIZER(TOKENIZER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, "edgeNGram");
-      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
-      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
-    }
-  },
-  UUID_MODULE_TOKENIZER(TOKENIZER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, PATTERN);
-      set(PATTERN, "\\.");
-    }
-  },
-
-  // Analyzers
-
-  SORTABLE_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, KEYWORD);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, getName(),
-        FIELD_FIELDDATA, FIELDDATA_ENABLED);
-    }
-  },
-  INDEX_GRAMS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, GRAM_TOKENIZER);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-  },
-  SEARCH_GRAMS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, WHITESPACE);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, INDEX_GRAMS_ANALYZER.getName(),
-        SEARCH_ANALYZER, getName());
-    }
-  },
-  INDEX_PREFIX_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, PREFIX_TOKENIZER);
-      setArray(FILTER, TRIM);
-    }
-  },
-  SEARCH_PREFIX_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, WHITESPACE);
-      setArray(FILTER, TRIM);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, INDEX_PREFIX_ANALYZER.getName(),
-        SEARCH_ANALYZER, getName());
-    }
-  },
-  INDEX_PREFIX_CASE_INSENSITIVE_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, PREFIX_TOKENIZER);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-  },
-  SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, WHITESPACE);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, INDEX_PREFIX_CASE_INSENSITIVE_ANALYZER.getName(),
-        SEARCH_ANALYZER, getName());
-    }
-  },
-  USER_INDEX_GRAMS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, WHITESPACE);
-      setArray(FILTER, TRIM, LOWERCASE, NGRAM_FILTER.getName());
-    }
-  },
-  USER_SEARCH_GRAMS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, WHITESPACE);
-      setArray(FILTER, TRIM, LOWERCASE);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, USER_INDEX_GRAMS_ANALYZER.getName(),
-        SEARCH_ANALYZER, getName());
-    }
-  },
-  INDEX_WORDS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, STANDARD);
-      setArray(FILTER, STANDARD, "word_filter", LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
-    }
-  },
-  SEARCH_WORDS_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, STANDARD);
-      setArray(FILTER, STANDARD, LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, INDEX_WORDS_ANALYZER.getName(),
-        SEARCH_ANALYZER, getName());
-    }
-  },
-  ENGLISH_HTML_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, STANDARD);
-      setArray(FILTER, STANDARD, LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
-      setArray(CHAR_FILTER, HTML_STRIP);
-    }
-
-    @Override
-    public SortedMap<String, String> fieldMapping() {
-      return ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, INDEX_SEARCHABLE,
-        ANALYZER, getName());
-    }
-  },
-  PATH_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, "path_hierarchy");
-    }
-  },
-  UUID_MODULE_ANALYZER(ANALYZER) {
-
-    @Override
-    protected void setup() {
-      set(TOKENIZER, UUID_MODULE_TOKENIZER);
-      setArray(FILTER, TRIM);
-    }
-  },
-
-  ;
-
-  private final String type;
-  private final String name;
-
-  private Builder builder = Settings.builder();
-
-  DefaultIndexSettingsElement(String type) {
-    this.type = type;
-    this.name = name().toLowerCase(Locale.ENGLISH);
-    setup();
-  }
-
-  protected void set(String settingSuffix, int value) {
-    put(localName(settingSuffix), Integer.toString(value));
-  }
-
-  protected void set(String settingSuffix, boolean value) {
-    put(localName(settingSuffix), Boolean.toString(value));
-  }
-
-  protected void set(String settingSuffix, DefaultIndexSettingsElement otherElement) {
-    put(localName(settingSuffix), otherElement.name);
-  }
-
-  protected void set(String settingSuffix, String value) {
-    put(localName(settingSuffix), value);
-  }
-
-  protected void setArray(String settingSuffix, String... values) {
-    putArray(localName(settingSuffix), values);
-  }
-
-  protected void setArray(String settingSuffix, DefaultIndexSettingsElement... values) {
-    putArray(localName(settingSuffix), Arrays.stream(values).map(DefaultIndexSettingsElement::getName).toArray(String[]::new));
-  }
-
-  private void put(String setting, String value) {
-    builder = builder.put(setting, value);
-  }
-
-  private void putArray(String setting, String... values) {
-    builder = builder.putArray(setting, values);
-  }
-
-  private String localName(String settingSuffix) {
-    return ANALYSIS + DELIMITER + type + DELIMITER + name + DELIMITER + settingSuffix;
-  }
-
-  public Settings settings() {
-    return builder.build();
-  }
-
-  protected abstract void setup();
-
-  public SortedMap<String, String> fieldMapping() {
-    throw new UnsupportedOperationException("The elasticsearch configuration element '" + name + "' cannot be used as field mapping.");
-  }
-
-  public String subField(String fieldName) {
-    return fieldName + SUB_FIELD_DELIMITER + getSubFieldSuffix();
-  }
-
-  public String getSubFieldSuffix() {
-    return getName();
-  }
-
-  public String getName() {
-    return name;
-  }
-}
index 26326c4f8ef66d80181f3ef61ce8530a12b2b5af..c78efb4529cf88ae295c9c450566bd61153bb8ff 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.server.es;
 
 import javax.annotation.concurrent.Immutable;
 
+import static java.util.Objects.requireNonNull;
+
 @Immutable
 class DocId {
 
@@ -28,14 +30,10 @@ class DocId {
   private final String indexType;
   private final String id;
 
-  DocId(IndexType indexType, String id) {
-    this(indexType.getIndex(), indexType.getType(), id);
-  }
-
   DocId(String index, String indexType, String id) {
-    this.index = index;
-    this.indexType = indexType;
-    this.id = id;
+    this.index = requireNonNull(index, "index can't be null");
+    this.indexType = requireNonNull(indexType,"type can't be null");
+    this.id = requireNonNull(id, "id can't be null");
   }
 
   @Override
@@ -48,13 +46,7 @@ class DocId {
     }
     DocId docId = (DocId) o;
 
-    if (!index.equals(docId.index)) {
-      return false;
-    }
-    if (!indexType.equals(docId.indexType)) {
-      return false;
-    }
-    return id.equals(docId.id);
+    return index.equals(docId.index) && indexType.equals(docId.indexType) && id.equals(docId.id);
   }
 
   @Override
@@ -64,4 +56,9 @@ class DocId {
     result = 31 * result + id.hashCode();
     return result;
   }
+
+  @Override
+  public String toString() {
+    return "DocId{" + index + '/' + indexType + '/' + id + '}';
+  }
 }
index 03edf91a5005114357c1457272c6a342d77ad876..bfd37f0b76a865668b8beb048391f4d72ae6b5d7 100644 (file)
@@ -41,6 +41,7 @@ import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.Priority;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.sonar.server.es.IndexType.IndexMainType;
 import org.sonar.server.es.request.ProxyClearCacheRequestBuilder;
 import org.sonar.server.es.request.ProxyClusterHealthRequestBuilder;
 import org.sonar.server.es.request.ProxyClusterStateRequestBuilder;
@@ -77,12 +78,16 @@ public class EsClient implements Closeable {
     this.nativeClient = null;
   }
 
-  public RefreshRequestBuilder prepareRefresh(String... indices) {
-    return new ProxyRefreshRequestBuilder(nativeClient()).setIndices(indices);
+  public RefreshRequestBuilder prepareRefresh(Index index) {
+    return new ProxyRefreshRequestBuilder(nativeClient()).setIndices(index.getName());
   }
 
-  public IndicesStatsRequestBuilder prepareStats(String... indices) {
-    return new ProxyIndicesStatsRequestBuilder(nativeClient()).setIndices(indices);
+  public IndicesStatsRequestBuilder prepareStats() {
+    return new ProxyIndicesStatsRequestBuilder(nativeClient());
+  }
+
+  public IndicesStatsRequestBuilder prepareStats(Index index) {
+    return new ProxyIndicesStatsRequestBuilder(nativeClient()).setIndices(index.getName());
   }
 
   public NodesStatsRequestBuilder prepareNodesStats(String... nodesIds) {
@@ -97,34 +102,34 @@ public class EsClient implements Closeable {
     return new ProxyClusterStateRequestBuilder(nativeClient());
   }
 
-  public ClusterHealthRequestBuilder prepareHealth(String... indices) {
-    return new ProxyClusterHealthRequestBuilder(nativeClient()).setIndices(indices);
+  public ClusterHealthRequestBuilder prepareHealth() {
+    return new ProxyClusterHealthRequestBuilder(nativeClient());
   }
 
   public void waitForStatus(ClusterHealthStatus status) {
     prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForStatus(status).get();
   }
 
-  public IndicesExistsRequestBuilder prepareIndicesExist(String... indices) {
-    return new ProxyIndicesExistsRequestBuilder(nativeClient(), indices);
+  public IndicesExistsRequestBuilder prepareIndicesExist(Index index) {
+    return new ProxyIndicesExistsRequestBuilder(nativeClient(), index.getName());
   }
 
-  public CreateIndexRequestBuilder prepareCreate(String index) {
-    return new ProxyCreateIndexRequestBuilder(nativeClient(), index);
+  public CreateIndexRequestBuilder prepareCreate(Index index) {
+    return new ProxyCreateIndexRequestBuilder(nativeClient(), index.getName());
   }
 
-  public PutMappingRequestBuilder preparePutMapping(String... indices) {
-    return new ProxyPutMappingRequestBuilder(nativeClient()).setIndices(indices);
+  public PutMappingRequestBuilder preparePutMapping(Index index) {
+    return new ProxyPutMappingRequestBuilder(nativeClient()).setIndices(index.getName());
   }
 
-  public SearchRequestBuilder prepareSearch(String... indices) {
-    return new ProxySearchRequestBuilder(nativeClient()).setIndices(indices);
+  public SearchRequestBuilder prepareSearch(Index index) {
+    return new ProxySearchRequestBuilder(nativeClient()).setIndices(index.getName());
   }
 
-  public SearchRequestBuilder prepareSearch(IndexType... indexType) {
+  public SearchRequestBuilder prepareSearch(IndexMainType indexType) {
     return new ProxySearchRequestBuilder(nativeClient())
-      .setIndices(IndexType.getIndices(indexType))
-      .setTypes(IndexType.getTypes(indexType));
+      .setIndices(indexType.getIndex().getName())
+      .setTypes(indexType.getType());
   }
 
   public SearchScrollRequestBuilder prepareSearchScroll(String scrollId) {
@@ -132,19 +137,22 @@ public class EsClient implements Closeable {
   }
 
   public GetRequestBuilder prepareGet(IndexType indexType, String id) {
-    return new ProxyGetRequestBuilder(nativeClient()).setIndex(indexType.getIndex()).setType(indexType.getType()).setId(id);
+    IndexMainType mainType = indexType.getMainType();
+    return new ProxyGetRequestBuilder(nativeClient()).setIndex(mainType.getIndex().getName()).setType(mainType.getType()).setId(id);
   }
 
   public DeleteRequestBuilder prepareDelete(IndexType indexType, String id) {
-    return new ProxyDeleteRequestBuilder(nativeClient(), indexType.getIndex()).setType(indexType.getType()).setId(id);
+    IndexMainType mainType = indexType.getMainType();
+    return new ProxyDeleteRequestBuilder(nativeClient(), mainType.getIndex().getName()).setType(mainType.getType()).setId(id);
   }
 
-  public DeleteRequestBuilder prepareDelete(String index, String type, String id) {
+  DeleteRequestBuilder prepareDelete(String index, String type, String id) {
     return new ProxyDeleteRequestBuilder(nativeClient(), index).setType(type).setId(id);
   }
 
   public IndexRequestBuilder prepareIndex(IndexType indexType) {
-    return new ProxyIndexRequestBuilder(nativeClient()).setIndex(indexType.getIndex()).setType(indexType.getType());
+    IndexMainType mainType = indexType.getMainType();
+    return new ProxyIndexRequestBuilder(nativeClient()).setIndex(mainType.getIndex().getName()).setType(mainType.getType());
   }
 
   public ForceMergeRequestBuilder prepareForceMerge(String indexName) {
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/Index.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/Index.java
new file mode 100644 (file)
index 0000000..77f1981
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es;
+
+import java.util.Objects;
+import org.apache.commons.lang.StringUtils;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+public final class Index {
+  public static final Index ALL_INDICES = Index.simple("_all");
+
+  private final String name;
+  private final boolean relations;
+
+  private Index(String name, boolean acceptsRelations) {
+    checkArgument(name != null && !name.isEmpty(), "Index name can't be null nor empty");
+    checkArgument("_all".equals(name) || StringUtils.isAllLowerCase(name), "Index name must be lower-case letters or '_all': %s", name);
+    this.name = name;
+    this.relations = acceptsRelations;
+  }
+
+  public static Index simple(String name) {
+    return new Index(name, false);
+  }
+
+  public static Index withRelations(String name) {
+    return new Index(name, true);
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public boolean acceptsRelations() {
+    return relations;
+  }
+
+  /**
+   * @return the name of the join field for this index if it accepts relations
+   * @throws IllegalStateException if index does not accept relations
+   * @see #acceptsRelations()
+   */
+  public String getJoinField() {
+    checkState(relations, "Only index accepting relations has a join field");
+    return "join_" + name;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Index index = (Index) o;
+    return relations == index.relations && name.equals(index.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, relations);
+  }
+
+  @Override
+  public String toString() {
+    return "[" + name + (relations ? "|*" : "|") + ']';
+  }
+}
index 94faba81a52c72139eff29819f163c2e2f913533..09005233a0ec9584a6286c0c68d0c3b32ec6a422 100644 (file)
  */
 package org.sonar.server.es;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import org.elasticsearch.common.settings.Settings;
+import java.util.Map;
 import org.sonar.api.server.ServerSide;
+import org.sonar.server.es.newindex.NewAuthorizedIndex;
+import org.sonar.server.es.newindex.NewIndex;
+import org.sonar.server.es.newindex.NewRegularIndex;
+import org.sonar.server.es.newindex.SettingsConfiguration;
 
-import java.util.Map;
+import static com.google.common.base.Preconditions.checkArgument;
 
 @ServerSide
 public interface IndexDefinition {
@@ -33,70 +35,29 @@ public interface IndexDefinition {
   class IndexDefinitionContext {
     private final Map<String, NewIndex> byKey = Maps.newHashMap();
 
-    public NewIndex create(String key, NewIndex.SettingsConfiguration settingsConfiguration) {
-      Preconditions.checkArgument(!byKey.containsKey(key), String.format("Index already exists: %s", key));
-      NewIndex index = new NewIndex(key, settingsConfiguration);
-      byKey.put(key, index);
-      return index;
-    }
-
-    public Map<String, NewIndex> getIndices() {
-      return byKey;
-    }
-  }
-
-  void define(IndexDefinitionContext context);
-
-  /**
-   * Immutable copy of {@link NewIndex}
-   */
-  class Index {
-    private final String name;
-    private final Settings settings;
-    private final Map<String, Type> types;
-
-    Index(NewIndex newIndex) {
-      this.name = newIndex.getName();
-      this.settings = newIndex.getSettings().build();
-      ImmutableMap.Builder<String, Type> builder = ImmutableMap.builder();
-      for (NewIndex.NewIndexType newIndexType : newIndex.getTypes().values()) {
-        Type type = new Type(newIndexType);
-        builder.put(type.getName(), type);
-      }
-      this.types = builder.build();
+    public NewRegularIndex create(Index index, SettingsConfiguration settingsConfiguration) {
+      String indexName = index.getName();
+      checkArgument(!byKey.containsKey(indexName), String.format("Index already exists: %s", indexName));
+      NewRegularIndex newIndex = new NewRegularIndex(index, settingsConfiguration);
+      byKey.put(indexName, newIndex);
+      return newIndex;
     }
 
-    public String getName() {
-      return name;
-    }
+    public NewAuthorizedIndex createWithAuthorization(Index index, SettingsConfiguration settingsConfiguration) {
+      checkArgument(index.acceptsRelations(), "Index with authorization must accept relations");
+      String indexName = index.getName();
+      checkArgument(!byKey.containsKey(indexName), String.format("Index already exists: %s", indexName));
 
-    public Settings getSettings() {
-      return settings;
+      NewAuthorizedIndex newIndex = new NewAuthorizedIndex(index, settingsConfiguration);
+      byKey.put(indexName, newIndex);
+      return newIndex;
     }
 
-    public Map<String, Type> getTypes() {
-      return types;
+    public Map<String, NewIndex> getIndices() {
+      return byKey;
     }
   }
 
-  /**
-   * Immutable copy of {@link NewIndex.NewIndexType}
-   */
-  class Type {
-    private final String name;
-    private final Map<String, Object> attributes;
-
-    private Type(NewIndex.NewIndexType newType) {
-      this.name = newType.getName();
-      this.attributes = ImmutableMap.copyOf(newType.getAttributes());
-    }
-
-    public String getName() {
-      return name;
-    }
+  void define(IndexDefinitionContext context);
 
-    public Map<String, Object> getAttributes() {
-      return attributes;
-    }
-  }
 }
index d8dba45db8b9fbca7ace31e17139793bd84e2aef..8be1677ede3df1fd57393607035f8e7749ea22b7 100644 (file)
  */
 package org.sonar.server.es;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.Lists;
-import org.apache.commons.codec.digest.DigestUtils;
-
-import java.util.Collections;
-import java.util.List;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.SortedMap;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.sonar.server.es.newindex.BuiltIndex;
+
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 
 /**
  * Hash of index definition is stored in the index itself in order to detect changes of mappings
@@ -34,14 +35,18 @@ import java.util.SortedMap;
  * and re-populated from scratch. There's no attempt to migrate existing data.
  */
 class IndexDefinitionHash {
-
   private static final char DELIMITER = ',';
 
   private IndexDefinitionHash() {
   }
 
-  static String of(IndexDefinition.Index index) {
-    return of(index.getSettings().getAsMap(), index.getTypes());
+  static String of(BuiltIndex<?> index) {
+    IndexType.IndexMainType mainType = index.getMainType();
+    return of(
+      ImmutableMap.of(mainType.getIndex(), mainType),
+      index.getRelationTypes().stream().collect(uniqueIndex(IndexType.IndexRelationType::getName, t -> t)),
+      index.getSettings().getAsMap(),
+      index.getAttributes());
   }
 
   private static String of(Map... maps) {
@@ -52,22 +57,6 @@ class IndexDefinitionHash {
     return DigestUtils.sha256Hex(sb.toString());
   }
 
-  private static void appendObject(StringBuilder sb, Object value) {
-    if (value instanceof IndexDefinition.Type) {
-      appendIndexType(sb, (IndexDefinition.Type) value);
-    } else if (value instanceof Map) {
-      appendMap(sb, (Map) value);
-    } else if (value instanceof Iterable) {
-      appendIterable(sb, (Iterable) value);
-    } else {
-      sb.append(String.valueOf(value));
-    }
-  }
-
-  private static void appendIndexType(StringBuilder sb, IndexDefinition.Type type) {
-    appendMap(sb, type.getAttributes());
-  }
-
   private static void appendMap(StringBuilder sb, Map attributes) {
     for (Object entry : sort(attributes).entrySet()) {
       sb.append(((Map.Entry) entry).getKey());
@@ -77,16 +66,22 @@ class IndexDefinitionHash {
     }
   }
 
-  private static void appendIterable(StringBuilder sb, Iterable value) {
-    List sorted = Lists.newArrayList(value);
-    Collections.sort(sorted);
-    for (Object o : sorted) {
-      appendObject(sb, o);
-      sb.append(DELIMITER);
+  private static void appendObject(StringBuilder sb, Object value) {
+    if (value instanceof Object[]) {
+      sb.append(Arrays.toString((Object[]) value));
+    } else if (value instanceof Map) {
+      appendMap(sb, (Map) value);
+    } else if (value instanceof IndexType) {
+      sb.append(((IndexType) value).format());
+    } else {
+      sb.append(String.valueOf(value));
     }
   }
 
   private static SortedMap sort(Map map) {
+    if (map instanceof ImmutableSortedMap) {
+      return (ImmutableSortedMap) map;
+    }
     return ImmutableSortedMap.copyOf(map);
   }
 }
index 9aa62543b7398ac28778fead690dd9635123b870..e773f5904bdcfb678569ebf7029686bad0e8dbff 100644 (file)
 package org.sonar.server.es;
 
 import com.google.common.base.Splitter;
-import java.util.Arrays;
 import java.util.List;
-import java.util.function.Function;
-import org.sonar.core.util.stream.MoreCollectors;
+import java.util.Objects;
+import javax.annotation.concurrent.Immutable;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
-public class IndexType {
+public abstract class IndexType {
+
+  public static final String FIELD_INDEX_TYPE = "indexType";
 
   private static final String SEPARATOR = "/";
   private static final Splitter SEPARATOR_SPLITTER = Splitter.on(SEPARATOR);
 
-  private final String index;
-  private final String type;
-  private final String key;
+  public abstract IndexMainType getMainType();
 
-  public IndexType(String index, String type) {
-    this.index = requireNonNull(index);
-    this.type = requireNonNull(type);
-    this.key = index + SEPARATOR + type;
-  }
+  public abstract String format();
 
-  public String getIndex() {
-    return index;
-  }
+  /**
+   * Parse a String generated by {@link #format()} to extract the simple details of the main type.
+   * <p>
+   * Note: neither {@link IndexMainType} nor {@link IndexRelationType} can be parsed from the string generated by
+   * {@link #format()} as the generated string does not contain the {@link Index#acceptsRelations() acceptsRelations}
+   * flag).
+   */
+  public static SimpleIndexMainType parseMainType(String s) {
+    List<String> split = SEPARATOR_SPLITTER.splitToList(s);
+    checkArgument(split.size() >= 2, "Unsupported IndexType value: %s", s);
 
-  public String getType() {
-    return type;
+    return new SimpleIndexMainType(split.get(0), split.get(1));
   }
 
-  public static String[] getIndices(IndexType... indexTypes) {
-    return getDetails(IndexType::getIndex, indexTypes);
-  }
+  @Immutable
+  public static final class SimpleIndexMainType {
+    private final String index;
+    private final String type;
+
+    private SimpleIndexMainType(String index, String type) {
+      this.index = index;
+      this.type = type;
+    }
+
+    public String getIndex() {
+      return index;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SimpleIndexMainType that = (SimpleIndexMainType) o;
+      return index.equals(that.index) && type.equals(that.type);
+    }
 
-  public static String[] getTypes(IndexType... indexTypes) {
-    return getDetails(IndexType::getType, indexTypes);
+    @Override
+    public int hashCode() {
+      return Objects.hash(index, type);
+    }
+
+    @Override
+    public String toString() {
+      return "[" + index + '/' + type + ']';
+    }
   }
 
-  private static String[] getDetails(Function<? super IndexType, ? extends String> function, IndexType... indexTypes) {
-    return Arrays.stream(indexTypes).map(function).collect(MoreCollectors.toSet(indexTypes.length)).toArray(new String[0]);
+  public static IndexMainType main(Index index, String type) {
+    return new IndexMainType(index, type);
   }
 
-  public String format() {
-    return key;
+  public static IndexRelationType relation(IndexMainType mainType, String name) {
+    checkArgument(mainType.getIndex().acceptsRelations(), "Index must define a join field to have relations");
+
+    return new IndexRelationType(mainType, name);
   }
 
-  /**
-   * Parse a String generated by {@link #format()}
-   */
-  public static IndexType parse(String s) {
-    List<String> split = SEPARATOR_SPLITTER.splitToList(s);
-    if (split.size() != 2) {
-      throw new IllegalArgumentException("Unsupported IndexType value: " + s);
+  @Immutable
+  public static final class IndexMainType extends IndexType {
+    private final Index index;
+    private final String type;
+    private final String key;
+
+    private IndexMainType(Index index, String type) {
+      this.index = requireNonNull(index);
+      checkArgument(type != null && !type.isEmpty(), "type name can't be null nor empty");
+      this.type = type;
+      this.key = index.getName() + SEPARATOR + type;
     }
-    return new IndexType(split.get(0), split.get(1));
-  }
 
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
+    @Override
+    public IndexMainType getMainType() {
+      return this;
     }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
+
+    public Index getIndex() {
+      return index;
     }
 
-    IndexType indexType = (IndexType) o;
-    return key.equals(indexType.key);
-  }
+    public String getType() {
+      return type;
+    }
+
+    @Override
+    public String format() {
+      return key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+
+      IndexMainType indexType = (IndexMainType) o;
+      return index.equals(indexType.index) && type.equals(indexType.type);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(index, type);
+    }
+
+    @Override
+    public String toString() {
+      return "[" + key + "]";
+    }
 
-  @Override
-  public int hashCode() {
-    return key.hashCode();
   }
 
-  @Override
-  public String toString() {
-    return "[" + index + "/" + type + "]";
+  @Immutable
+  public static final class IndexRelationType extends IndexType {
+    private final IndexMainType mainType;
+    private final String name;
+    private final String key;
+
+    private IndexRelationType(IndexMainType mainType, String name) {
+      this.mainType = mainType;
+      checkArgument(name != null && !name.isEmpty(), "type name can't be null nor empty");
+      this.name = name;
+      this.key = mainType.index.getName() + "/" + mainType.type + "/" + name;
+    }
+
+    @Override
+    public IndexMainType getMainType() {
+      return mainType;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public String format() {
+      return key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+
+      IndexRelationType indexType = (IndexRelationType) o;
+      return mainType.equals(indexType.mainType) && name.equals(indexType.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(mainType, name);
+    }
+
+    @Override
+    public String toString() {
+      return "[" + key + "]";
+    }
   }
 }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/NewIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/NewIndex.java
deleted file mode 100644 (file)
index 88276a2..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.es;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.Maps;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import javax.annotation.CheckForNull;
-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 static com.google.common.base.Preconditions.checkArgument;
-import static java.lang.String.format;
-import static java.lang.String.valueOf;
-import static java.util.Objects.requireNonNull;
-import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.Property.SEARCH_REPLICAS;
-import static org.sonar.server.es.DefaultIndexSettings.ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettings.FIELDDATA_ENABLED;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_FIELDDATA;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_TERM_VECTOR;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_TYPE_KEYWORD;
-import static org.sonar.server.es.DefaultIndexSettings.FIELD_TYPE_TEXT;
-import static org.sonar.server.es.DefaultIndexSettings.INDEX;
-import static org.sonar.server.es.DefaultIndexSettings.INDEX_NOT_SEARCHABLE;
-import static org.sonar.server.es.DefaultIndexSettings.INDEX_SEARCHABLE;
-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 {
-
-  private final String indexName;
-  private final Settings.Builder settings = DefaultIndexSettings.defaults();
-  private final Map<String, NewIndexType> types = new LinkedHashMap<>();
-
-  NewIndex(String indexName, SettingsConfiguration settingsConfiguration) {
-    checkArgument(StringUtils.isAllLowerCase(indexName), "Index name must be lower-case: " + indexName);
-    this.indexName = indexName;
-    applySettingsConfiguration(settingsConfiguration);
-  }
-
-  private void applySettingsConfiguration(SettingsConfiguration settingsConfiguration) {
-    settings.put("index.mapper.dynamic", valueOf(false));
-    settings.put("index.refresh_interval", refreshInterval(settingsConfiguration));
-
-    Configuration config = settingsConfiguration.getConfiguration();
-    boolean clusterMode = config.getBoolean(CLUSTER_ENABLED.getKey()).orElse(false);
-    int shards = config.getInt(format("sonar.search.%s.shards", indexName))
-      .orElse(settingsConfiguration.getDefaultNbOfShards());
-    int replicas = clusterMode ? config.getInt(SEARCH_REPLICAS.getKey()).orElse(1) : 0;
-
-    settings.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, shards);
-    settings.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicas);
-  }
-
-  private static String refreshInterval(SettingsConfiguration settingsConfiguration) {
-    int refreshInterval = settingsConfiguration.getRefreshInterval();
-    if (refreshInterval == -1) {
-      return "-1";
-    }
-    return refreshInterval + "s";
-  }
-
-  public static class SettingsConfiguration {
-    public static final int MANUAL_REFRESH_INTERVAL = -1;
-
-    private final Configuration configuration;
-    private final int defaultNbOfShards;
-    private final int refreshInterval;
-
-    private SettingsConfiguration(Builder builder) {
-      this.configuration = builder.configuration;
-      this.defaultNbOfShards = builder.defaultNbOfShards;
-      this.refreshInterval = builder.refreshInterval;
-    }
-
-    public static Builder newBuilder(Configuration configuration) {
-      return new Builder(configuration);
-    }
-
-    public Configuration getConfiguration() {
-      return configuration;
-    }
-
-    public int getDefaultNbOfShards() {
-      return defaultNbOfShards;
-    }
-
-    public int getRefreshInterval() {
-      return refreshInterval;
-    }
-
-    public static class Builder {
-      private final Configuration configuration;
-      private int defaultNbOfShards = 1;
-      private int refreshInterval = 30;
-
-      public Builder(Configuration configuration) {
-        this.configuration = requireNonNull(configuration, "configuration can't be null");
-      }
-
-      public Builder setDefaultNbOfShards(int defaultNbOfShards) {
-        checkArgument(defaultNbOfShards >= 1, "defaultNbOfShards must be >= 1");
-        this.defaultNbOfShards = defaultNbOfShards;
-        return this;
-      }
-
-      public Builder setRefreshInterval(int refreshInterval) {
-        checkArgument(refreshInterval == -1 || refreshInterval > 0,
-          "refreshInterval must be either -1 or strictly positive");
-        this.refreshInterval = refreshInterval;
-        return this;
-      }
-
-      public SettingsConfiguration build() {
-        return new SettingsConfiguration(this);
-      }
-    }
-
-  }
-
-  public String getName() {
-    return indexName;
-  }
-
-  public Settings.Builder getSettings() {
-    return settings;
-  }
-
-  public NewIndexType createType(String typeName) {
-    NewIndexType type = new NewIndexType(this, typeName);
-    types.put(typeName, type);
-    return type;
-  }
-
-  public Map<String, NewIndexType> getTypes() {
-    return types;
-  }
-
-  public static class NewIndexType {
-    private final NewIndex index;
-    private final String name;
-    private final Map<String, Object> attributes = new TreeMap<>();
-    private final Map<String, Object> properties = new TreeMap<>();
-
-    private NewIndexType(NewIndex index, String typeName) {
-      this.index = index;
-      this.name = typeName;
-      // defaults
-      attributes.put("dynamic", false);
-      attributes.put("_all", ImmutableSortedMap.of("enabled", false));
-      attributes.put("_source", ImmutableSortedMap.of("enabled", true));
-      attributes.put("properties", properties);
-    }
-
-    public NewIndexType requireProjectAuthorization() {
-      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;
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    /**
-     * Complete the root json hash of mapping type, for example to set the attribute "_id"
-     */
-    public NewIndexType setAttribute(String key, Object value) {
-      attributes.put(key, value);
-      return this;
-    }
-
-    /**
-     * Complete the json hash named "properties" in mapping type, usually to declare fields
-     */
-    public NewIndexType setProperty(String key, Object value) {
-      properties.put(key, value);
-      return this;
-    }
-
-    public NewIndexType setEnableSource(boolean enableSource) {
-      attributes.put("_source", ImmutableSortedMap.of("enabled", enableSource));
-      return this;
-    }
-
-    public KeywordFieldBuilder keywordFieldBuilder(String fieldName) {
-      return new KeywordFieldBuilder(this, fieldName);
-    }
-
-    public TextFieldBuilder textFieldBuilder(String fieldName) {
-      return new TextFieldBuilder(this, fieldName);
-    }
-
-    public NestedFieldBuilder nestedFieldBuilder(String fieldName) {
-      return new NestedFieldBuilder(this, fieldName);
-    }
-
-    public NewIndexType createBooleanField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "boolean"));
-    }
-
-    public NewIndexType createByteField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "byte"));
-    }
-
-    public NewIndexType createDateTimeField(String fieldName) {
-      Map<String, String> hash = new TreeMap<>();
-      hash.put("type", "date");
-      hash.put("format", "date_time||epoch_second");
-      return setProperty(fieldName, hash);
-    }
-
-    public NewIndexType createDoubleField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "double"));
-    }
-
-    public NewIndexType createIntegerField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "integer"));
-    }
-
-    public NewIndexType createLongField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "long"));
-    }
-
-    public NewIndexType createShortField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "short"));
-    }
-
-    public NewIndexType createUuidPathField(String fieldName) {
-      return setProperty(fieldName, ImmutableSortedMap.of(
-        TYPE, FIELD_TYPE_TEXT,
-        INDEX, DefaultIndexSettings.INDEX_SEARCHABLE,
-        ANALYZER, UUID_MODULE_ANALYZER.getName()));
-    }
-
-    public Map<String, Object> getAttributes() {
-      return attributes;
-    }
-
-    @CheckForNull
-    public Object getProperty(String key) {
-      return properties.get(key);
-    }
-  }
-
-  /**
-   * Helper to define a string field in mapping of index type
-   */
-  public abstract static class StringFieldBuilder<T extends StringFieldBuilder<T>> {
-    private final NewIndexType indexType;
-    private final String fieldName;
-    private boolean disableSearch = false;
-    private boolean disableNorms = false;
-    private boolean termVectorWithPositionOffsets = false;
-    private SortedMap<String, Object> subFields = Maps.newTreeMap();
-    private boolean store = false;
-    protected boolean disabledDocValues = false;
-
-    private StringFieldBuilder(NewIndexType indexType, String fieldName) {
-      this.indexType = indexType;
-      this.fieldName = fieldName;
-    }
-
-    /**
-     * Add a sub-field. A {@code SortedMap} is required for consistency of the index settings hash.
-     * @see IndexDefinitionHash
-     */
-    private T addSubField(String fieldName, SortedMap<String, String> fieldDefinition) {
-      subFields.put(fieldName, fieldDefinition);
-      return castThis();
-    }
-
-    /**
-     * Add subfields, one for each analyzer.
-     */
-    public T addSubFields(DefaultIndexSettingsElement... analyzers) {
-      Arrays.stream(analyzers)
-        .forEach(analyzer -> addSubField(analyzer.getSubFieldSuffix(), analyzer.fieldMapping()));
-      return castThis();
-    }
-
-    /**
-     * Norms consume useless memory if string field is used for filtering or aggregations.
-     *
-     * https://www.elastic.co/guide/en/elasticsearch/reference/2.3/norms.html
-     * https://www.elastic.co/guide/en/elasticsearch/guide/current/scoring-theory.html#field-norm
-     */
-    public T disableNorms() {
-      this.disableNorms = true;
-      return castThis();
-    }
-
-    /**
-     * Position offset term vectors are required for the fast_vector_highlighter (fvh).
-     */
-    public T termVectorWithPositionOffsets() {
-      this.termVectorWithPositionOffsets = true;
-      return castThis();
-    }
-
-    /**
-     * "index: false" -> Make this field not searchable.
-     * By default field is "true": it is searchable, but index the value exactly
-     * as specified.
-     */
-    public T disableSearch() {
-      this.disableSearch = true;
-      return castThis();
-    }
-
-    public T store() {
-      this.store = true;
-      return castThis();
-    }
-
-    @SuppressWarnings("unchecked")
-    private T castThis() {
-      return (T) this;
-    }
-
-    public NewIndexType build() {
-      if (subFields.isEmpty()) {
-        return buildWithoutSubfields();
-      }
-      return buildWithSubfields();
-    }
-
-    private NewIndexType buildWithoutSubfields() {
-      Map<String, Object> hash = new TreeMap<>();
-      hash.put("type", getFieldType());
-      hash.put(INDEX, disableSearch ? INDEX_NOT_SEARCHABLE : INDEX_SEARCHABLE);
-      hash.put(NORMS, valueOf(!disableNorms));
-      hash.put(STORE, valueOf(store));
-      if (FIELD_TYPE_KEYWORD.equals(getFieldType())) {
-        hash.put("doc_values", valueOf(!disabledDocValues));
-      }
-      if (getFieldData()) {
-        hash.put(FIELD_FIELDDATA, FIELDDATA_ENABLED);
-      }
-      return indexType.setProperty(fieldName, hash);
-    }
-
-    private NewIndexType buildWithSubfields() {
-      Map<String, Object> hash = new TreeMap<>();
-      hash.put("type", getFieldType());
-      hash.put(INDEX, disableSearch ? INDEX_NOT_SEARCHABLE : INDEX_SEARCHABLE);
-      hash.put(NORMS, "false");
-      hash.put(STORE, valueOf(store));
-      if (FIELD_TYPE_KEYWORD.equals(getFieldType())) {
-        hash.put("doc_values", valueOf(!disabledDocValues));
-      }
-      if (getFieldData()) {
-        hash.put(FIELD_FIELDDATA, FIELDDATA_ENABLED);
-      }
-      if (termVectorWithPositionOffsets) {
-        hash.put(FIELD_TERM_VECTOR, "with_positions_offsets");
-      }
-      hash.put("fields", configureSubFields());
-      return indexType.setProperty(fieldName, hash);
-    }
-
-    private Map<String, Object> configureSubFields() {
-      Map<String, Object> multiFields = new TreeMap<>(subFields);
-
-      // apply this fields configuration to all subfields
-      multiFields.entrySet().forEach(entry -> {
-        Object subFieldMapping = entry.getValue();
-        if (subFieldMapping instanceof Map) {
-          entry.setValue(configureSubField((Map<String, String>) subFieldMapping));
-        }
-      });
-      return multiFields;
-    }
-
-    private Map<String, String> configureSubField(Map<String, String> subFieldMapping) {
-      Map<String, String> subHash = new TreeMap<>(subFieldMapping);
-      subHash.put(INDEX, INDEX_SEARCHABLE);
-      subHash.put(NORMS, "false");
-      subHash.put(STORE, valueOf(store));
-      if (termVectorWithPositionOffsets) {
-        subHash.put(FIELD_TERM_VECTOR, "with_positions_offsets");
-      }
-      return subHash;
-    }
-
-    protected abstract boolean getFieldData();
-
-    protected abstract String getFieldType();
-  }
-
-  public static class KeywordFieldBuilder extends StringFieldBuilder<KeywordFieldBuilder> {
-
-    private KeywordFieldBuilder(NewIndexType indexType, String fieldName) {
-      super(indexType, fieldName);
-    }
-
-    @Override
-    protected boolean getFieldData() {
-      return false;
-    }
-
-    protected String getFieldType() {
-      return FIELD_TYPE_KEYWORD;
-    }
-
-    /**
-     * By default, field is stored on disk in a column-stride fashion, so that it can later be used for sorting,
-     * aggregations, or scripting.
-     * Disabling this reduces the size of the index and drop the constraint of single term max size of
-     * 32766 bytes (which, if there is no tokenizing enabled on the field, equals the size of the whole data).
-     */
-    public KeywordFieldBuilder disableSortingAndAggregating() {
-      this.disabledDocValues = true;
-      return this;
-    }
-  }
-
-  public static class TextFieldBuilder extends StringFieldBuilder<TextFieldBuilder> {
-
-    private boolean fieldData = false;
-
-    private TextFieldBuilder(NewIndexType indexType, String fieldName) {
-      super(indexType, fieldName);
-    }
-
-    protected String getFieldType() {
-      return FIELD_TYPE_TEXT;
-    }
-
-    /**
-     * Required to enable sorting, aggregation and access to field data on fields of type "text".
-     * <p>Disabled by default as this can have significant memory cost</p>
-     */
-    public StringFieldBuilder withFieldData() {
-      this.fieldData = true;
-      return this;
-    }
-
-    @Override
-    protected boolean getFieldData() {
-      return fieldData;
-    }
-  }
-
-  public static class NestedFieldBuilder {
-    private final NewIndexType indexType;
-    private final String fieldName;
-    private final Map<String, Object> properties = new TreeMap<>();
-
-    private NestedFieldBuilder(NewIndexType indexType, String fieldName) {
-      this.indexType = indexType;
-      this.fieldName = fieldName;
-    }
-
-    private NestedFieldBuilder setProperty(String fieldName, Object value) {
-      properties.put(fieldName, value);
-
-      return this;
-    }
-
-    public NestedFieldBuilder addKeywordField(String fieldName) {
-      return setProperty(fieldName, ImmutableSortedMap.of(
-        "type", FIELD_TYPE_KEYWORD,
-        INDEX, INDEX_SEARCHABLE));
-    }
-
-    public NestedFieldBuilder addDoubleField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "double"));
-    }
-
-    public NestedFieldBuilder addIntegerField(String fieldName) {
-      return setProperty(fieldName, ImmutableMap.of("type", "integer"));
-    }
-
-    public NewIndexType build() {
-      checkArgument(!properties.isEmpty(), "At least one sub-field must be declared in nested property '%s'", fieldName);
-
-      return indexType.setProperty(fieldName, ImmutableSortedMap.of(
-        "type", "nested",
-        "properties", properties));
-    }
-  }
-
-}
index 732551a30b50e585cb4e785365874fd7dfdf83dc..c4dfdbb5bcc5869c2101f62509d40429e4379386 100644 (file)
@@ -48,7 +48,10 @@ public class OneToOneResilientIndexingListener implements IndexingListener {
     this.dbClient = dbClient;
     this.dbSession = dbSession;
     this.itemsById = items.stream()
-      .collect(MoreCollectors.index(i -> new DocId(IndexType.parse(i.getDocType()), i.getDocId()), Function.identity()));
+      .collect(MoreCollectors.index(i -> {
+        IndexType.SimpleIndexMainType mainType = IndexType.parseMainType(i.getDocType());
+        return new DocId(mainType.getIndex(), mainType.getType(), i.getDocId());
+      }, Function.identity()));
   }
 
   @Override
index 3d49090fc30858fcb0fd5a9cd945de45916893bb..1aaccbc132e8434bbabf272d6ff9cca4198d05f7 100644 (file)
@@ -24,9 +24,13 @@ import org.elasticsearch.action.get.GetRequestBuilder;
 import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.index.get.GetField;
 import org.sonar.server.es.EsClient;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
 
-import static org.sonar.server.es.DefaultIndexSettings.REFRESH_IMMEDIATE;
+import static org.sonar.server.es.metadata.MetadataIndexDefinition.TYPE_METADATA;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.REFRESH_IMMEDIATE;
 
 public class MetadataIndex {
 
@@ -38,16 +42,16 @@ public class MetadataIndex {
     this.esClient = esClient;
   }
 
-  public Optional<String> getHash(String index) {
+  public Optional<String> getHash(Index index) {
     return getMetadata(hashId(index));
   }
 
-  public void setHash(String index, String hash) {
+  public void setHash(Index index, String hash) {
     setMetadata(hashId(index), hash);
   }
 
-  private static String hashId(String index) {
-    return index + ".indexStructure";
+  private static String hashId(Index index) {
+    return index.getName() + ".indexStructure";
   }
 
   public boolean getInitialized(IndexType indexType) {
@@ -59,7 +63,16 @@ public class MetadataIndex {
   }
 
   private static String initializedId(IndexType indexType) {
-    return indexType.getIndex() + "." + indexType.getType() + ".initialized";
+    if (indexType instanceof IndexMainType) {
+      IndexMainType mainType = (IndexMainType) indexType;
+      return mainType.getIndex().getName() + "." + mainType.getType() + ".initialized";
+    }
+    if (indexType instanceof IndexRelationType) {
+      IndexRelationType relationType = (IndexRelationType) indexType;
+      IndexMainType mainType = relationType.getMainType();
+      return mainType.getIndex().getName() + "." + mainType.getType() + "." + relationType.getName() + ".initialized";
+    }
+    throw new IllegalArgumentException("Unsupported IndexType " + indexType.getClass());
   }
 
   public Optional<String> getDbVendor() {
@@ -71,7 +84,7 @@ public class MetadataIndex {
   }
 
   private Optional<String> getMetadata(String id) {
-    GetRequestBuilder request = esClient.prepareGet(MetadataIndexDefinition.INDEX_TYPE_METADATA, id)
+    GetRequestBuilder request = esClient.prepareGet(TYPE_METADATA, id)
       .setStoredFields(MetadataIndexDefinition.FIELD_VALUE);
     GetResponse response = request.get();
     if (response.isExists()) {
@@ -83,7 +96,7 @@ public class MetadataIndex {
   }
 
   private void setMetadata(String id, String value) {
-    esClient.prepareIndex(MetadataIndexDefinition.INDEX_TYPE_METADATA)
+    esClient.prepareIndex(TYPE_METADATA)
       .setId(id)
       .setSource(MetadataIndexDefinition.FIELD_VALUE, value)
       .setRefreshPolicy(REFRESH_IMMEDIATE)
index 57c00c12491f13583ea0bce98bc4803098b04ec5..391e03254a0cfd748ad0aa0ab9ec62d50600e3fc 100644 (file)
 package org.sonar.server.es.metadata;
 
 import org.sonar.api.config.Configuration;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition.IndexDefinitionContext;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.NewRegularIndex;
 
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 public class MetadataIndexDefinition {
 
-  public static final IndexType INDEX_TYPE_METADATA = new IndexType("metadatas", "metadata");
+  public static final Index DESCRIPTOR = Index.simple("metadatas");
+  public static final IndexMainType TYPE_METADATA = IndexType.main(DESCRIPTOR, "metadata");
   public static final String FIELD_VALUE = "value";
 
   private static final int DEFAULT_NUMBER_OF_SHARDS = 1;
@@ -41,15 +44,14 @@ public class MetadataIndexDefinition {
   }
 
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_METADATA.getIndex(),
+    NewRegularIndex index = context.create(
+      DESCRIPTOR,
       newBuilder(configuration)
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         .setDefaultNbOfShards(DEFAULT_NUMBER_OF_SHARDS)
         .build());
 
-    NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_METADATA.getType());
-
-    mapping.keywordFieldBuilder(FIELD_VALUE).disableSearch().store().build();
+    index.createTypeMapping(TYPE_METADATA)
+      .keywordFieldBuilder(FIELD_VALUE).disableSearch().store().build();
   }
 }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/BuiltIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/BuiltIndex.java
new file mode 100644 (file)
index 0000000..bae180d
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import org.elasticsearch.common.settings.Settings;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.NORMS;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.STORE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TYPE;
+
+/**
+ * Immutable copy of {@link NewIndex}
+ */
+public final class BuiltIndex<T extends NewIndex<T>> {
+  private final IndexType.IndexMainType mainType;
+  private final Set<IndexRelationType> relationTypes;
+  private final Settings settings;
+  private final Map<String, Object> attributes;
+
+  BuiltIndex(T newIndex) {
+    this.mainType = newIndex.getMainType();
+    this.settings = newIndex.getSettings().build();
+    this.relationTypes = newIndex.getRelationsStream().collect(toSet());
+    this.attributes = buildAttributes(newIndex);
+  }
+
+  private static Map<String, Object> buildAttributes(NewIndex<?> newIndex) {
+    Map<String, Object> indexAttributes = new TreeMap<>(newIndex.getAttributes());
+    setRouting(indexAttributes, newIndex);
+    indexAttributes.put("properties", buildProperties(newIndex));
+    return ImmutableSortedMap.copyOf(indexAttributes);
+  }
+
+  private static void setRouting(Map<String, Object> indexAttributes, NewIndex newIndex) {
+    if (!newIndex.getRelations().isEmpty()) {
+      indexAttributes.put("_routing", ImmutableMap.of("required", true));
+    }
+  }
+
+  private static TreeMap<String, Object> buildProperties(NewIndex<?> newIndex) {
+    TreeMap<String, Object> indexProperties = new TreeMap<>(newIndex.getProperties());
+    setTypeField(indexProperties, newIndex);
+    setJoinField(indexProperties, newIndex);
+    return indexProperties;
+  }
+
+  private static void setTypeField(TreeMap<String, Object> indexProperties, NewIndex newIndex) {
+    Collection<IndexRelationType> relations = newIndex.getRelations();
+    if (!relations.isEmpty()) {
+      indexProperties.put(
+        FIELD_INDEX_TYPE,
+        ImmutableMap.of(
+          TYPE, "keyword",
+          NORMS, false,
+          STORE, false,
+          "doc_values", false));
+    }
+  }
+
+  private static void setJoinField(TreeMap<String, Object> indexProperties, NewIndex newIndex) {
+    Collection<IndexRelationType> relations = newIndex.getRelations();
+    IndexType.IndexMainType mainType = newIndex.getMainType();
+    if (!relations.isEmpty()) {
+      indexProperties.put(mainType.getIndex().getJoinField(), ImmutableMap.of(
+        TYPE, "join",
+        "relations", ImmutableMap.of(mainType.getType(), namesToStringOrStringArray(relations))));
+    }
+  }
+
+  private static Serializable namesToStringOrStringArray(Collection<IndexRelationType> relations) {
+    if (relations.size() == 1) {
+      return relations.iterator().next().getName();
+    }
+
+    return relations.stream()
+      .map(IndexRelationType::getName)
+      .sorted()
+      .toArray(String[]::new);
+  }
+
+  public IndexType.IndexMainType getMainType() {
+    return mainType;
+  }
+
+  public Set<IndexRelationType> getRelationTypes() {
+    return relationTypes;
+  }
+
+  public Settings getSettings() {
+    return settings;
+  }
+
+  public Map<String, Object> getAttributes() {
+    return attributes;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettings.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettings.java
new file mode 100644 (file)
index 0000000..aaf2b2e
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import java.util.Arrays;
+import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
+import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.common.settings.Settings;
+
+public class DefaultIndexSettings {
+
+  /** Minimum length of ngrams. */
+  public static final int MINIMUM_NGRAM_LENGTH = 2;
+  /** Maximum length of ngrams. */
+  public static final int MAXIMUM_NGRAM_LENGTH = 15;
+
+  /** Pattern, that splits the user search input **/
+  public static final String SEARCH_TERM_TOKENIZER_PATTERN = "[\\s]+";
+
+  public static final String ANALYSIS = "index.analysis";
+  public static final String DELIMITER = ".";
+
+  public static final String TOKENIZER = "tokenizer";
+  public static final String FILTER = "filter";
+  public static final String CHAR_FILTER = "char_filter";
+  public static final String ANALYZER = "analyzer";
+  public static final String SEARCH_ANALYZER = "search_analyzer";
+
+  public static final String TYPE = "type";
+  public static final String INDEX = "index";
+  public static final String INDEX_SEARCHABLE = "true";
+  public static final String INDEX_NOT_SEARCHABLE = "false";
+  public static final String FIELD_TYPE_TEXT = "text";
+  public static final String FIELD_TYPE_KEYWORD = "keyword";
+  public static final String NORMS = "norms";
+  public static final String STORE = "store";
+  public static final String FIELD_FIELDDATA = "fielddata";
+  public static final String FIELDDATA_ENABLED = "true";
+  public static final String FIELD_TERM_VECTOR = "term_vector";
+  public static final String STANDARD = "standard";
+  public static final String PATTERN = "pattern";
+  public static final String CUSTOM = "custom";
+  public static final String KEYWORD = "keyword";
+  public static final String CLASSIC = "classic";
+  public static final RefreshPolicy REFRESH_IMMEDIATE = RefreshPolicy.IMMEDIATE;
+  public static final RefreshPolicy REFRESH_NONE = RefreshPolicy.NONE;
+
+  public static final String TRUNCATE = "truncate";
+
+  public static final String SUB_FIELD_DELIMITER = ".";
+  public static final String TRIM = "trim";
+  public static final String LOWERCASE = "lowercase";
+  public static final String WHITESPACE = "whitespace";
+  public static final String STOP = "stop";
+  public static final String ASCIIFOLDING = "asciifolding";
+  public static final String PORTER_STEM = "porter_stem";
+  public static final String MIN_GRAM = "min_gram";
+  public static final String MAX_GRAM = "max_gram";
+  public static final String LENGTH = "length";
+  public static final String HTML_STRIP = "html_strip";
+
+  private DefaultIndexSettings() {
+    // only static stuff
+  }
+
+  public static Settings.Builder defaults() {
+    Settings.Builder builder = Settings.builder()
+      .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
+      .put("index.refresh_interval", "30s")
+      .put("index.mapper.dynamic", false);
+
+    Arrays.stream(DefaultIndexSettingsElement.values())
+      .map(DefaultIndexSettingsElement::settings)
+      .forEach(builder::put);
+
+    return builder;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettingsElement.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/DefaultIndexSettingsElement.java
new file mode 100644 (file)
index 0000000..c051731
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.SortedMap;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.settings.Settings.Builder;
+
+import static org.sonar.server.es.newindex.DefaultIndexSettings.ANALYSIS;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.ASCIIFOLDING;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.CHAR_FILTER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.DELIMITER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELDDATA_ENABLED;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_FIELDDATA;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_TEXT;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FILTER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.HTML_STRIP;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX_SEARCHABLE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.KEYWORD;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.LOWERCASE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MAX_GRAM;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MIN_GRAM;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.PATTERN;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.PORTER_STEM;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.SEARCH_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.STANDARD;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.STOP;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.SUB_FIELD_DELIMITER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TOKENIZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TRIM;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.WHITESPACE;
+
+public enum DefaultIndexSettingsElement {
+
+  // Filters
+
+  WORD_FILTER(FILTER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, "word_delimiter");
+      set("generate_word_parts", true);
+      set("catenate_words", true);
+      set("catenate_numbers", true);
+      set("catenate_all", true);
+      set("split_on_case_change", true);
+      set("preserve_original", true);
+      set("split_on_numerics", true);
+      set("stem_english_possessive", true);
+    }
+  },
+  NGRAM_FILTER(FILTER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, "nGram");
+      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
+      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
+      setArray("token_chars", "letter", "digit", "punctuation", "symbol");
+    }
+  },
+
+  // Tokenizers
+
+  GRAM_TOKENIZER(TOKENIZER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, "nGram");
+      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
+      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
+      setArray("token_chars", "letter", "digit", "punctuation", "symbol");
+    }
+  },
+  PREFIX_TOKENIZER(TOKENIZER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, "edgeNGram");
+      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
+      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
+    }
+  },
+  UUID_MODULE_TOKENIZER(TOKENIZER) {
+
+    @Override
+    protected void setup() {
+      set(TYPE, PATTERN);
+      set(PATTERN, "\\.");
+    }
+  },
+
+  // Analyzers
+
+  SORTABLE_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, KEYWORD);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, getName(),
+        FIELD_FIELDDATA, FIELDDATA_ENABLED);
+    }
+  },
+  INDEX_GRAMS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, GRAM_TOKENIZER);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+  },
+  SEARCH_GRAMS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, WHITESPACE);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, INDEX_GRAMS_ANALYZER.getName(),
+        SEARCH_ANALYZER, getName());
+    }
+  },
+  INDEX_PREFIX_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, PREFIX_TOKENIZER);
+      setArray(FILTER, TRIM);
+    }
+  },
+  SEARCH_PREFIX_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, WHITESPACE);
+      setArray(FILTER, TRIM);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, INDEX_PREFIX_ANALYZER.getName(),
+        SEARCH_ANALYZER, getName());
+    }
+  },
+  INDEX_PREFIX_CASE_INSENSITIVE_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, PREFIX_TOKENIZER);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+  },
+  SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, WHITESPACE);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, INDEX_PREFIX_CASE_INSENSITIVE_ANALYZER.getName(),
+        SEARCH_ANALYZER, getName());
+    }
+  },
+  USER_INDEX_GRAMS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, WHITESPACE);
+      setArray(FILTER, TRIM, LOWERCASE, NGRAM_FILTER.getName());
+    }
+  },
+  USER_SEARCH_GRAMS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, WHITESPACE);
+      setArray(FILTER, TRIM, LOWERCASE);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, USER_INDEX_GRAMS_ANALYZER.getName(),
+        SEARCH_ANALYZER, getName());
+    }
+  },
+  INDEX_WORDS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, STANDARD);
+      setArray(FILTER, STANDARD, "word_filter", LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
+    }
+  },
+  SEARCH_WORDS_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, STANDARD);
+      setArray(FILTER, STANDARD, LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, INDEX_WORDS_ANALYZER.getName(),
+        SEARCH_ANALYZER, getName());
+    }
+  },
+  ENGLISH_HTML_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, STANDARD);
+      setArray(FILTER, STANDARD, LOWERCASE, STOP, ASCIIFOLDING, PORTER_STEM);
+      setArray(CHAR_FILTER, HTML_STRIP);
+    }
+
+    @Override
+    public SortedMap<String, String> fieldMapping() {
+      return ImmutableSortedMap.of(
+        TYPE, FIELD_TYPE_TEXT,
+        INDEX, INDEX_SEARCHABLE,
+        ANALYZER, getName());
+    }
+  },
+  PATH_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, "path_hierarchy");
+    }
+  },
+  UUID_MODULE_ANALYZER(ANALYZER) {
+
+    @Override
+    protected void setup() {
+      set(TOKENIZER, UUID_MODULE_TOKENIZER);
+      setArray(FILTER, TRIM);
+    }
+  },
+
+  ;
+
+  private final String type;
+  private final String name;
+
+  private Builder builder = Settings.builder();
+
+  DefaultIndexSettingsElement(String type) {
+    this.type = type;
+    this.name = name().toLowerCase(Locale.ENGLISH);
+    setup();
+  }
+
+  protected void set(String settingSuffix, int value) {
+    put(localName(settingSuffix), Integer.toString(value));
+  }
+
+  protected void set(String settingSuffix, boolean value) {
+    put(localName(settingSuffix), Boolean.toString(value));
+  }
+
+  protected void set(String settingSuffix, DefaultIndexSettingsElement otherElement) {
+    put(localName(settingSuffix), otherElement.name);
+  }
+
+  protected void set(String settingSuffix, String value) {
+    put(localName(settingSuffix), value);
+  }
+
+  protected void setArray(String settingSuffix, String... values) {
+    putArray(localName(settingSuffix), values);
+  }
+
+  protected void setArray(String settingSuffix, DefaultIndexSettingsElement... values) {
+    putArray(localName(settingSuffix), Arrays.stream(values).map(DefaultIndexSettingsElement::getName).toArray(String[]::new));
+  }
+
+  private void put(String setting, String value) {
+    builder = builder.put(setting, value);
+  }
+
+  private void putArray(String setting, String... values) {
+    builder = builder.putArray(setting, values);
+  }
+
+  private String localName(String settingSuffix) {
+    return ANALYSIS + DELIMITER + type + DELIMITER + name + DELIMITER + settingSuffix;
+  }
+
+  public Settings settings() {
+    return builder.build();
+  }
+
+  protected abstract void setup();
+
+  public SortedMap<String, String> fieldMapping() {
+    throw new UnsupportedOperationException("The elasticsearch configuration element '" + name + "' cannot be used as field mapping.");
+  }
+
+  public String subField(String fieldName) {
+    return fieldName + SUB_FIELD_DELIMITER + getSubFieldSuffix();
+  }
+
+  public String getSubFieldSuffix() {
+    return getName();
+  }
+
+  public String getName() {
+    return name;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/FieldAware.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/FieldAware.java
new file mode 100644 (file)
index 0000000..a1cdd5a
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_TEXT;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.UUID_MODULE_ANALYZER;
+
+public abstract class FieldAware<U extends FieldAware<U>> {
+
+  abstract U setFieldImpl(String fieldName, Object attributes);
+
+  protected final U setField(String fieldName, Object attributes) {
+    checkArgument(!FIELD_INDEX_TYPE.equalsIgnoreCase(fieldName), "%s is a reserved field name", FIELD_INDEX_TYPE);
+    return setFieldImpl(fieldName, attributes);
+  }
+
+  @SuppressWarnings("unchecked")
+  public KeywordFieldBuilder<U> keywordFieldBuilder(String fieldName) {
+    return (KeywordFieldBuilder<U>) new KeywordFieldBuilder(this, fieldName);
+  }
+
+  @SuppressWarnings("unchecked")
+  public TextFieldBuilder<U> textFieldBuilder(String fieldName) {
+    return (TextFieldBuilder<U>) new TextFieldBuilder(this, fieldName);
+  }
+
+  @SuppressWarnings("unchecked")
+  public NestedFieldBuilder<U> nestedFieldBuilder(String fieldName) {
+    return (NestedFieldBuilder<U>) new NestedFieldBuilder(this, fieldName);
+  }
+
+  public U createBooleanField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "boolean"));
+  }
+
+  public U createByteField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "byte"));
+  }
+
+  public U createDateTimeField(String fieldName) {
+    Map<String, String> hash = new TreeMap<>();
+    hash.put("type", "date");
+    hash.put("format", "date_time||epoch_second");
+    return setField(fieldName, hash);
+  }
+
+  public U createDoubleField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "double"));
+  }
+
+  public U createIntegerField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "integer"));
+  }
+
+  public U createLongField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "long"));
+  }
+
+  public U createShortField(String fieldName) {
+    return setField(fieldName, ImmutableMap.of("type", "short"));
+  }
+
+  public U createUuidPathField(String fieldName) {
+    return setField(fieldName, ImmutableSortedMap.of(
+      TYPE, FIELD_TYPE_TEXT,
+      INDEX, DefaultIndexSettings.INDEX_SEARCHABLE,
+      ANALYZER, UUID_MODULE_ANALYZER.getName()));
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/KeywordFieldBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/KeywordFieldBuilder.java
new file mode 100644 (file)
index 0000000..751952c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_KEYWORD;
+
+public class KeywordFieldBuilder<U extends FieldAware<U>> extends StringFieldBuilder<U, KeywordFieldBuilder<U>> {
+
+  protected KeywordFieldBuilder(U indexType, String fieldName) {
+    super(indexType, fieldName);
+  }
+
+  @Override
+  protected boolean getFieldData() {
+    return false;
+  }
+
+  protected String getFieldType() {
+    return FIELD_TYPE_KEYWORD;
+  }
+
+  /**
+   * By default, field is stored on disk in a column-stride fashion, so that it can later be used for sorting,
+   * aggregations, or scripting.
+   * Disabling this reduces the size of the index and drop the constraint of single term max size of
+   * 32766 bytes (which, if there is no tokenizing enabled on the field, equals the size of the whole data).
+   */
+  public KeywordFieldBuilder disableSortingAndAggregating() {
+    this.disabledDocValues = true;
+    return this;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NestedFieldBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NestedFieldBuilder.java
new file mode 100644 (file)
index 0000000..0116713
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_KEYWORD;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX_SEARCHABLE;
+
+public class NestedFieldBuilder<U extends FieldAware<U>> {
+  private final U parent;
+  private final String fieldName;
+  private final Map<String, Object> properties = new TreeMap<>();
+
+  protected NestedFieldBuilder(U parent, String fieldName) {
+    this.parent = parent;
+    this.fieldName = fieldName;
+  }
+
+  private NestedFieldBuilder setProperty(String fieldName, Object value) {
+    properties.put(fieldName, value);
+
+    return this;
+  }
+
+  public NestedFieldBuilder addKeywordField(String fieldName) {
+    return setProperty(fieldName, ImmutableSortedMap.of(
+      "type", FIELD_TYPE_KEYWORD,
+      INDEX, INDEX_SEARCHABLE));
+  }
+
+  public NestedFieldBuilder addDoubleField(String fieldName) {
+    return setProperty(fieldName, ImmutableMap.of("type", "double"));
+  }
+
+  public NestedFieldBuilder addIntegerField(String fieldName) {
+    return setProperty(fieldName, ImmutableMap.of("type", "integer"));
+  }
+
+  public U build() {
+    checkArgument(!properties.isEmpty(), "At least one sub-field must be declared in nested property '%s'", fieldName);
+
+    return parent.setField(fieldName, ImmutableSortedMap.of(
+      "type", "nested",
+      "properties", properties));
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewAuthorizedIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewAuthorizedIndex.java
new file mode 100644 (file)
index 0000000..33202b6
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+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 NewAuthorizedIndex extends NewIndex<NewAuthorizedIndex> {
+  private final IndexType.IndexMainType mainType;
+
+  public NewAuthorizedIndex(Index index, SettingsConfiguration settingsConfiguration) {
+    super(index, settingsConfiguration);
+    checkArgument(index.acceptsRelations(), "Index must accept relations");
+
+    this.mainType = IndexType.main(index, TYPE_AUTHORIZATION);
+    super.createTypeMapping(mainType)
+      .createLongField(FIELD_GROUP_IDS)
+      .createLongField(FIELD_USER_IDS)
+      .createBooleanField(FIELD_ALLOW_ANYONE);
+  }
+
+  @Override
+  public IndexType.IndexMainType getMainType() {
+    return mainType;
+  }
+
+  @Override
+  public TypeMapping createTypeMapping(IndexRelationType relationType) {
+    checkArgument(relationType.getMainType().equals(mainType), "mainType of relation must be %s", mainType);
+    return super.createTypeMapping(relationType);
+  }
+
+  @Override
+  public BuiltIndex<NewAuthorizedIndex> build() {
+    checkState(!getRelations().isEmpty(), "At least one relation mapping must be defined");
+    return new BuiltIndex<>(this);
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewIndex.java
new file mode 100644 (file)
index 0000000..6246e8a
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.common.settings.Settings;
+import org.sonar.api.config.Configuration;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.valueOf;
+import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.Property.SEARCH_REPLICAS;
+
+public abstract class NewIndex<T extends NewIndex<T>> {
+
+  private static final String ENABLED = "enabled";
+  private final Index index;
+  private final Map<String, IndexRelationType> relations = new LinkedHashMap<>();
+  private final Settings.Builder settings = DefaultIndexSettings.defaults();
+  private final Map<String, Object> attributes = new TreeMap<>();
+  private final Map<String, Object> properties = new TreeMap<>();
+
+  public NewIndex(Index index, SettingsConfiguration settingsConfiguration) {
+    this.index = index;
+    applySettingsConfiguration(settingsConfiguration);
+    configureDefaultAttributes();
+  }
+
+  private void applySettingsConfiguration(SettingsConfiguration settingsConfiguration) {
+    settings.put("index.mapper.dynamic", valueOf(false));
+    settings.put("index.refresh_interval", refreshInterval(settingsConfiguration));
+    settings.put("mapping.single_type", valueOf(true));
+
+    Configuration config = settingsConfiguration.getConfiguration();
+    boolean clusterMode = config.getBoolean(CLUSTER_ENABLED.getKey()).orElse(false);
+    int shards = config.getInt(String.format("sonar.search.%s.shards", index.getName()))
+      .orElse(settingsConfiguration.getDefaultNbOfShards());
+    int replicas = clusterMode ? config.getInt(SEARCH_REPLICAS.getKey()).orElse(1) : 0;
+
+    settings.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, shards);
+    settings.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicas);
+  }
+
+  private void configureDefaultAttributes() {
+    attributes.put("dynamic", valueOf(false));
+    attributes.put("_all", ImmutableSortedMap.of(ENABLED, false));
+    attributes.put("_source", ImmutableSortedMap.of(ENABLED, true));
+  }
+
+  private static String refreshInterval(SettingsConfiguration settingsConfiguration) {
+    int refreshInterval = settingsConfiguration.getRefreshInterval();
+    if (refreshInterval == -1) {
+      return "-1";
+    }
+    return refreshInterval + "s";
+  }
+
+  protected Index getIndex() {
+    return index;
+  }
+
+  public abstract IndexMainType getMainType();
+
+  Collection<IndexRelationType> getRelations() {
+    return relations.values();
+  }
+
+  /**
+   * Public, read-only version of {@link #getRelations()}
+   */
+  public Stream<IndexRelationType> getRelationsStream() {
+    return relations.values().stream();
+  }
+
+  Settings.Builder getSettings() {
+    return settings;
+  }
+
+  @CheckForNull
+  public String getSetting(String key) {
+    return settings.get(key);
+  }
+
+  protected TypeMapping createTypeMapping(IndexMainType mainType) {
+    checkArgument(mainType.getIndex().equals(index), "Main type must belong to index %s", index);
+    return new TypeMapping(this);
+  }
+
+  protected TypeMapping createTypeMapping(IndexRelationType relationType) {
+    checkAcceptsRelations();
+    IndexMainType mainType = getMainType();
+    checkArgument(relationType.getMainType().equals(mainType), "mainType of relation must be %s", mainType);
+    String relationName = relationType.getName();
+    checkArgument(!relations.containsKey(relationName), "relation %s already exists", relationName);
+    relations.put(relationName, relationType);
+    return new TypeMapping(this);
+  }
+
+  private void checkAcceptsRelations() {
+    checkState(getMainType().getIndex().acceptsRelations(), "Index is not configured to accept relations. Update IndexDefinition.Descriptor instance for this index");
+  }
+
+  /**
+   * Complete the json hash named "properties" in mapping type, usually to declare fields
+   */
+  void setFieldImpl(String fieldName, Object attributes) {
+    properties.put(fieldName, attributes);
+  }
+
+  public T setEnableSource(boolean enableSource) {
+    attributes.put("_source", ImmutableSortedMap.of(ENABLED, enableSource));
+    return castThis();
+  }
+
+  @SuppressWarnings("unchecked")
+  private T castThis() {
+    return (T) this;
+  }
+
+  public Map<String, Object> getAttributes() {
+    return attributes;
+  }
+
+  Map<String, Object> getProperties() {
+    return properties;
+  }
+
+  @CheckForNull
+  public Object getProperty(String key) {
+    return properties.get(key);
+  }
+
+  public abstract BuiltIndex<T> build();
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewRegularIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewRegularIndex.java
new file mode 100644 (file)
index 0000000..57b1852
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+public class NewRegularIndex extends NewIndex<NewRegularIndex> {
+  private IndexMainType mainType;
+
+  public NewRegularIndex(Index index, SettingsConfiguration settingsConfiguration) {
+    super(index, settingsConfiguration);
+  }
+
+  @Override
+  public IndexMainType getMainType() {
+    checkState(mainType != null, "Main type has not been defined");
+    return mainType;
+  }
+
+  @Override
+  public TypeMapping createTypeMapping(IndexMainType mainType) {
+    checkState(this.mainType == null, "Main type can only be defined once");
+    this.mainType = mainType;
+    return super.createTypeMapping(mainType);
+  }
+
+  @Override
+  public TypeMapping createTypeMapping(IndexType.IndexRelationType relationType) {
+    checkState(mainType != null, "Mapping for main type must be created first");
+    checkArgument(relationType.getMainType().equals(mainType), "main type of relation must be %s", mainType);
+    return super.createTypeMapping(relationType);
+  }
+
+  @Override
+  public BuiltIndex<NewRegularIndex> build() {
+    checkState(mainType != null, "Mapping for main type must be defined");
+    checkState(!mainType.getIndex().acceptsRelations() || !getRelations().isEmpty(), "At least one relation must be defined when index accepts relations");
+    return new BuiltIndex<>(this);
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/SettingsConfiguration.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/SettingsConfiguration.java
new file mode 100644 (file)
index 0000000..260703a
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import org.sonar.api.config.Configuration;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class SettingsConfiguration {
+  public static final int MANUAL_REFRESH_INTERVAL = -1;
+
+  private final Configuration configuration;
+  private final int defaultNbOfShards;
+  private final int refreshInterval;
+
+  private SettingsConfiguration(Builder builder) {
+    this.configuration = builder.configuration;
+    this.defaultNbOfShards = builder.defaultNbOfShards;
+    this.refreshInterval = builder.refreshInterval;
+  }
+
+  public static Builder newBuilder(Configuration configuration) {
+    return new Builder(configuration);
+  }
+
+  public Configuration getConfiguration() {
+    return configuration;
+  }
+
+  public int getDefaultNbOfShards() {
+    return defaultNbOfShards;
+  }
+
+  public int getRefreshInterval() {
+    return refreshInterval;
+  }
+
+  public static class Builder {
+    private final Configuration configuration;
+    private int defaultNbOfShards = 1;
+    private int refreshInterval = 30;
+
+    public Builder(Configuration configuration) {
+      this.configuration = requireNonNull(configuration, "configuration can't be null");
+    }
+
+    public Builder setDefaultNbOfShards(int defaultNbOfShards) {
+      checkArgument(defaultNbOfShards >= 1, "defaultNbOfShards must be >= 1");
+      this.defaultNbOfShards = defaultNbOfShards;
+      return this;
+    }
+
+    public Builder setRefreshInterval(int refreshInterval) {
+      checkArgument(refreshInterval == -1 || refreshInterval > 0,
+        "refreshInterval must be either -1 or strictly positive");
+      this.refreshInterval = refreshInterval;
+      return this;
+    }
+
+    public SettingsConfiguration build() {
+      return new SettingsConfiguration(this);
+    }
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/StringFieldBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/StringFieldBuilder.java
new file mode 100644 (file)
index 0000000..24dd3ab
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.Maps;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import static java.lang.String.valueOf;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELDDATA_ENABLED;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_FIELDDATA;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TERM_VECTOR;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_KEYWORD;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX_NOT_SEARCHABLE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.INDEX_SEARCHABLE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.NORMS;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.STORE;
+
+/**
+ * Helper to define a string field in mapping of index type
+ */
+public abstract class StringFieldBuilder<U extends FieldAware<U>, T extends StringFieldBuilder<U, T>> {
+  private final U parent;
+  private final String fieldName;
+  private boolean disableSearch = false;
+  private boolean disableNorms = false;
+  private boolean termVectorWithPositionOffsets = false;
+  private SortedMap<String, Object> subFields = Maps.newTreeMap();
+  private boolean store = false;
+  protected boolean disabledDocValues = false;
+
+  protected StringFieldBuilder(U parent, String fieldName) {
+    this.parent = parent;
+    this.fieldName = fieldName;
+  }
+
+  /**
+   * Add a sub-field. A {@code SortedMap} is required for consistency of the index settings hash.
+   */
+  private T addSubField(String fieldName, SortedMap<String, String> fieldDefinition) {
+    subFields.put(fieldName, fieldDefinition);
+    return castThis();
+  }
+
+  /**
+   * Add subfields, one for each analyzer.
+   */
+  public T addSubFields(DefaultIndexSettingsElement... analyzers) {
+    Arrays.stream(analyzers)
+      .forEach(analyzer -> addSubField(analyzer.getSubFieldSuffix(), analyzer.fieldMapping()));
+    return castThis();
+  }
+
+  /**
+   * Norms consume useless memory if string field is used for filtering or aggregations.
+   *
+   * https://www.elastic.co/guide/en/elasticsearch/reference/2.3/norms.html
+   * https://www.elastic.co/guide/en/elasticsearch/guide/current/scoring-theory.html#field-norm
+   */
+  public T disableNorms() {
+    this.disableNorms = true;
+    return castThis();
+  }
+
+  /**
+   * Position offset term vectors are required for the fast_vector_highlighter (fvh).
+   */
+  public T termVectorWithPositionOffsets() {
+    this.termVectorWithPositionOffsets = true;
+    return castThis();
+  }
+
+  /**
+   * "index: false" -> Make this field not searchable.
+   * By default field is "true": it is searchable, but index the value exactly
+   * as specified.
+   */
+  public T disableSearch() {
+    this.disableSearch = true;
+    return castThis();
+  }
+
+  public T store() {
+    this.store = true;
+    return castThis();
+  }
+
+  @SuppressWarnings("unchecked")
+  private T castThis() {
+    return (T) this;
+  }
+
+  public U build() {
+    if (subFields.isEmpty()) {
+      return buildWithoutSubfields();
+    }
+    return buildWithSubfields();
+  }
+
+  private U buildWithoutSubfields() {
+    Map<String, Object> hash = new TreeMap<>();
+    hash.put("type", getFieldType());
+    hash.put(INDEX, disableSearch ? INDEX_NOT_SEARCHABLE : INDEX_SEARCHABLE);
+    hash.put(NORMS, valueOf(!disableNorms));
+    hash.put(STORE, valueOf(store));
+    if (FIELD_TYPE_KEYWORD.equals(getFieldType())) {
+      hash.put("doc_values", valueOf(!disabledDocValues));
+    }
+    if (getFieldData()) {
+      hash.put(FIELD_FIELDDATA, FIELDDATA_ENABLED);
+    }
+    return parent.setField(fieldName, hash);
+  }
+
+  private U buildWithSubfields() {
+    Map<String, Object> hash = new TreeMap<>();
+    hash.put("type", getFieldType());
+    hash.put(INDEX, disableSearch ? INDEX_NOT_SEARCHABLE : INDEX_SEARCHABLE);
+    hash.put(NORMS, "false");
+    hash.put(STORE, valueOf(store));
+    if (FIELD_TYPE_KEYWORD.equals(getFieldType())) {
+      hash.put("doc_values", valueOf(!disabledDocValues));
+    }
+    if (getFieldData()) {
+      hash.put(FIELD_FIELDDATA, FIELDDATA_ENABLED);
+    }
+    if (termVectorWithPositionOffsets) {
+      hash.put(FIELD_TERM_VECTOR, "with_positions_offsets");
+    }
+    hash.put("fields", configureSubFields());
+    return parent.setField(fieldName, hash);
+  }
+
+  private Map<String, Object> configureSubFields() {
+    Map<String, Object> multiFields = new TreeMap<>(subFields);
+
+    // apply this fields configuration to all subfields
+    multiFields.entrySet().forEach(entry -> {
+      Object subFieldMapping = entry.getValue();
+      if (subFieldMapping instanceof Map) {
+        entry.setValue(configureSubField((Map<String, String>) subFieldMapping));
+      }
+    });
+    return multiFields;
+  }
+
+  private Map<String, String> configureSubField(Map<String, String> subFieldMapping) {
+    Map<String, String> subHash = new TreeMap<>(subFieldMapping);
+    subHash.put(INDEX, INDEX_SEARCHABLE);
+    subHash.put(NORMS, "false");
+    subHash.put(STORE, valueOf(store));
+    if (termVectorWithPositionOffsets) {
+      subHash.put(FIELD_TERM_VECTOR, "with_positions_offsets");
+    }
+    return subHash;
+  }
+
+  protected abstract boolean getFieldData();
+
+  protected abstract String getFieldType();
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TextFieldBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TextFieldBuilder.java
new file mode 100644 (file)
index 0000000..7817b50
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import static org.sonar.server.es.newindex.DefaultIndexSettings.FIELD_TYPE_TEXT;
+
+public class TextFieldBuilder<U extends FieldAware<U>> extends StringFieldBuilder<U, TextFieldBuilder<U>> {
+
+  private boolean fieldData = false;
+
+  protected TextFieldBuilder(U indexType, String fieldName) {
+    super(indexType, fieldName);
+  }
+
+  protected String getFieldType() {
+    return FIELD_TYPE_TEXT;
+  }
+
+  /**
+   * Required to enable sorting, aggregation and access to field data on fields of type "text".
+   * <p>Disabled by default as this can have significant memory cost</p>
+   */
+  public StringFieldBuilder withFieldData() {
+    this.fieldData = true;
+    return this;
+  }
+
+  @Override
+  protected boolean getFieldData() {
+    return fieldData;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TypeMapping.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/TypeMapping.java
new file mode 100644 (file)
index 0000000..40a42d8
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+public class TypeMapping extends FieldAware<TypeMapping> {
+
+  private final NewIndex<?> newIndex;
+
+  TypeMapping(NewIndex<?> newIndex) {
+    this.newIndex = newIndex;
+  }
+
+  @Override
+  TypeMapping setFieldImpl(String fieldName, Object attributes) {
+    newIndex.setFieldImpl(fieldName, attributes);
+    return this;
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/package-info.java
new file mode 100644 (file)
index 0000000..f3a4f39
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index a52ca3bb90c3d2d86bf5b8c21c1a09900ef9cec2..c4d9428f69390f3bf8237741460dcef2930e985b 100644 (file)
@@ -68,8 +68,9 @@ public class ProxyClusterHealthRequestBuilder extends ClusterHealthRequestBuilde
   public String toString() {
     StringBuilder message = new StringBuilder();
     message.append("ES cluster health request");
-    if (request.indices().length > 0) {
-      message.append(String.format(" on indices '%s'", StringUtils.join(request.indices(), ",")));
+    String[] indices = request.indices();
+    if (indices != null && indices.length > 0) {
+      message.append(String.format(" on indices '%s'", StringUtils.join(indices, ",")));
     }
     return message.toString();
   }
index d97cd6741d36d4110f578af991b2bc8989368487..51ee8240080aff38a5ca2d9fb31aa74e6e6e835f 100644 (file)
  */
 package org.sonar.server.es.request;
 
+import java.io.IOException;
 import org.elasticsearch.action.ListenableActionFuture;
 import org.elasticsearch.action.admin.indices.create.CreateIndexAction;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.server.es.EsClient;
 
@@ -66,6 +70,21 @@ public class ProxyCreateIndexRequestBuilder extends CreateIndexRequestBuilder {
     throw new UnsupportedOperationException("execute() should not be called as it's used for asynchronous");
   }
 
+  public String toJson() {
+    try {
+      XContentBuilder builder = XContentFactory.jsonBuilder();
+      builder.startObject()
+        .field("settings")
+        .startObject();
+      request.settings().toXContent(builder, ToXContent.EMPTY_PARAMS);
+      builder.endObject().endObject();
+      builder.prettyPrint();
+      return builder.string();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @Override
   public String toString() {
     return String.format("ES create index '%s'", index);
index dcba39e44b3e1d50279464c1d8a8a8ea387edcb7..a7bae09c894ec9552f9a9b3964d9af19bfa8be24 100644 (file)
@@ -68,8 +68,9 @@ public class ProxyIndicesStatsRequestBuilder extends IndicesStatsRequestBuilder
   public String toString() {
     StringBuilder message = new StringBuilder();
     message.append("ES indices stats request");
-    if (request.indices().length > 0) {
-      message.append(String.format(" on indices '%s'", StringUtils.join(request.indices(), ",")));
+    String[] indices = request.indices();
+    if (indices != null && indices.length > 0) {
+      message.append(String.format(" on indices '%s'", StringUtils.join(indices, ",")));
     }
     return message.toString();
   }
index 623a8b0ee45fa1241c0cda4c2830ddbbed58964a..6e4cdd2452d7323cabb5f170d859f8414ffa76ed 100644 (file)
@@ -29,17 +29,17 @@ import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.MatchQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.server.es.DefaultIndexSettings;
-import org.sonar.server.es.DefaultIndexSettingsElement;
+import org.sonar.server.es.newindex.DefaultIndexSettings;
+import org.sonar.server.es.newindex.DefaultIndexSettingsElement;
 import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory.ComponentTextSearchQuery;
 
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_PREFIX_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_PREFIX_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_PREFIX_CASE_INSENSITIVE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.es.textsearch.ComponentTextSearchFeature.UseCase.CHANGE_ORDER_OF_RESULTS;
 import static org.sonar.server.es.textsearch.ComponentTextSearchFeature.UseCase.GENERATE_RESULTS;
 
index 658f6fff7e6a2b01ea11ef8511906cdc0c3cfb82..6decac89ccd0c9868108f2cb7851a93860517901 100644 (file)
@@ -23,9 +23,9 @@ import java.util.Arrays;
 import java.util.List;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.server.es.DefaultIndexSettings;
+import org.sonar.server.es.newindex.DefaultIndexSettings;
 
-import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
 
 /**
  * Splits text queries into their tokens, for to use them in n_gram match queries later.
index 82f3e279ffb76b225d9a0a3d1809d36335671104..406133e965732ef5af8a9131a72430d809a7497b 100644 (file)
@@ -29,15 +29,18 @@ import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.Duration;
 import org.sonar.server.es.BaseDoc;
+import org.sonar.server.permission.index.AuthorizationDoc;
+
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 
 public class IssueDoc extends BaseDoc {
 
   public IssueDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_ISSUE, fields);
   }
 
   public IssueDoc() {
-    super(Maps.newHashMapWithExpectedSize(32));
+    super(TYPE_ISSUE, Maps.newHashMapWithExpectedSize(32));
   }
 
   @Override
@@ -45,16 +48,6 @@ public class IssueDoc extends BaseDoc {
     return key();
   }
 
-  @Override
-  public String getRouting() {
-    return projectUuid();
-  }
-
-  @Override
-  public String getParent() {
-    return projectUuid();
-  }
-
   public String key() {
     return getField(IssueIndexDefinition.FIELD_ISSUE_KEY);
   }
@@ -181,6 +174,7 @@ public class IssueDoc extends BaseDoc {
 
   public IssueDoc setProjectUuid(String s) {
     setField(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, s);
+    setParent(AuthorizationDoc.idOf(s));
     return this;
   }
 
index 0e3db147dfca1ec084841a36ee59307bd22545fa..04b24d58e793e20aa10c3cdce266f2ad4b3afec9 100644 (file)
@@ -21,20 +21,24 @@ package org.sonar.server.issue.index;
 
 import org.sonar.api.config.Configuration;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.newindex.NewAuthorizedIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
-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;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 /**
  * Definition of ES index "issues", including settings and fields.
  */
 public class IssueIndexDefinition implements IndexDefinition {
 
-  public static final IndexType INDEX_TYPE_ISSUE = new IndexType("issues", "issue");
+  public static final Index DESCRIPTOR = Index.withRelations("issues");
+  public static final IndexType.IndexRelationType TYPE_ISSUE = IndexType.relation(IndexType.main(DESCRIPTOR, TYPE_AUTHORIZATION), "issue");
   public static final String FIELD_ISSUE_ASSIGNEE_UUID = "assignee";
   public static final String FIELD_ISSUE_AUTHOR_LOGIN = "authorLogin";
   public static final String FIELD_ISSUE_COMPONENT_UUID = "component";
@@ -118,44 +122,42 @@ public class IssueIndexDefinition implements IndexDefinition {
 
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_ISSUE.getIndex(),
+    NewAuthorizedIndex index = context.createWithAuthorization(
+      DESCRIPTOR,
       newBuilder(config)
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         .setDefaultNbOfShards(5)
-        .build());
+        .build())
+      .setEnableSource(enableSource);
 
-    NewIndex.NewIndexType type = index.createType(INDEX_TYPE_ISSUE.getType());
-    type.requireProjectAuthorization();
-    type.setEnableSource(enableSource);
-
-    type.keywordFieldBuilder(FIELD_ISSUE_ASSIGNEE_UUID).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
-    type.keywordFieldBuilder(FIELD_ISSUE_AUTHOR_LOGIN).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_COMPONENT_UUID).disableNorms().build();
-    type.createLongField(FIELD_ISSUE_EFFORT);
-    type.keywordFieldBuilder(FIELD_ISSUE_FILE_PATH).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
-    type.createDateTimeField(FIELD_ISSUE_FUNC_CREATED_AT);
-    type.createDateTimeField(FIELD_ISSUE_FUNC_UPDATED_AT);
-    type.createDateTimeField(FIELD_ISSUE_FUNC_CLOSED_AT);
-    type.keywordFieldBuilder(FIELD_ISSUE_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
-    type.keywordFieldBuilder(FIELD_ISSUE_LANGUAGE).disableNorms().build();
-    type.createIntegerField(FIELD_ISSUE_LINE);
-    type.keywordFieldBuilder(FIELD_ISSUE_MODULE_UUID).disableNorms().build();
-    type.createUuidPathField(FIELD_ISSUE_MODULE_PATH);
-    type.keywordFieldBuilder(FIELD_ISSUE_ORGANIZATION_UUID).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_PROJECT_UUID).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
-    type.keywordFieldBuilder(FIELD_ISSUE_BRANCH_UUID).disableNorms().build();
-    type.createBooleanField(FIELD_ISSUE_IS_MAIN_BRANCH);
-    type.keywordFieldBuilder(FIELD_ISSUE_DIRECTORY_PATH).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_RESOLUTION).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_RULE_ID).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_SEVERITY).disableNorms().build();
-    type.createByteField(FIELD_ISSUE_SEVERITY_VALUE);
-    type.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
-    type.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_SANS_TOP_25).disableNorms().build();
-    type.keywordFieldBuilder(FIELD_ISSUE_CWE).disableNorms().build();
+    TypeMapping mapping = index.createTypeMapping(TYPE_ISSUE);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_ASSIGNEE_UUID).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_AUTHOR_LOGIN).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_COMPONENT_UUID).disableNorms().build();
+    mapping.createLongField(FIELD_ISSUE_EFFORT);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_FILE_PATH).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.createDateTimeField(FIELD_ISSUE_FUNC_CREATED_AT);
+    mapping.createDateTimeField(FIELD_ISSUE_FUNC_UPDATED_AT);
+    mapping.createDateTimeField(FIELD_ISSUE_FUNC_CLOSED_AT);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_LANGUAGE).disableNorms().build();
+    mapping.createIntegerField(FIELD_ISSUE_LINE);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_MODULE_UUID).disableNorms().build();
+    mapping.createUuidPathField(FIELD_ISSUE_MODULE_PATH);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_ORGANIZATION_UUID).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_PROJECT_UUID).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_BRANCH_UUID).disableNorms().build();
+    mapping.createBooleanField(FIELD_ISSUE_IS_MAIN_BRANCH);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_DIRECTORY_PATH).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_RESOLUTION).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_RULE_ID).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_SEVERITY).disableNorms().build();
+    mapping.createByteField(FIELD_ISSUE_SEVERITY_VALUE);
+    mapping.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_SANS_TOP_25).disableNorms().build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_CWE).disableNorms().build();
   }
 }
index 472498b8de3bbdba8a867301f8ce29c4983cf52e..37396061bd3217b635302734f131e2e1cd031b8b 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.server.es.IndexingResult;
 import org.sonar.server.es.OneToManyResilientIndexingListener;
 import org.sonar.server.es.OneToOneResilientIndexingListener;
 import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.permission.index.AuthorizationDoc;
 import org.sonar.server.permission.index.AuthorizationScope;
 import org.sonar.server.permission.index.NeedAuthorizationIndexer;
 
@@ -53,7 +54,7 @@ import static java.util.Collections.emptyList;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID;
-import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 
 public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
@@ -66,8 +67,8 @@ public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
    */
   private static final String ID_TYPE_PROJECT_UUID = "projectUuid";
   private static final Logger LOGGER = Loggers.get(IssueIndexer.class);
-  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_ISSUE, project -> Qualifiers.PROJECT.equals(project.getQualifier()));
-  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(INDEX_TYPE_ISSUE);
+  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(TYPE_ISSUE, project -> Qualifiers.PROJECT.equals(project.getQualifier()));
+  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(TYPE_ISSUE);
 
   private final EsClient esClient;
   private final DbClient dbClient;
@@ -187,7 +188,7 @@ public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
     // the remaining uuids reference issues that don't exist in db. They must
     // be deleted from index.
     itemsByIssueKey.values().forEach(
-      item -> bulkIndexer.addDeletion(INDEX_TYPE_ISSUE, item.getDocId(), item.getDocRouting()));
+      item -> bulkIndexer.addDeletion(TYPE_ISSUE.getMainType(), item.getDocId(), item.getDocRouting()));
 
     return bulkIndexer.stop();
   }
@@ -229,7 +230,7 @@ public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
     BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, IndexingListener.FAIL_ON_ERROR);
     bulkIndexer.start();
-    issueKeys.forEach(issueKey -> bulkIndexer.addDeletion(INDEX_TYPE_ISSUE, issueKey, projectUuid));
+    issueKeys.forEach(issueKey -> bulkIndexer.addDeletion(TYPE_ISSUE.getMainType(), issueKey, AuthorizationDoc.idOf(projectUuid)));
     bulkIndexer.stop();
   }
 
@@ -249,27 +250,25 @@ public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
   }
 
   private IndexRequest newIndexRequest(IssueDoc issue) {
-    String projectUuid = issue.projectUuid();
-    return esClient.prepareIndex(INDEX_TYPE_ISSUE)
-      .setId(issue.key())
-      .setRouting(projectUuid)
-      .setParent(projectUuid)
+    return esClient.prepareIndex(TYPE_ISSUE.getMainType())
+      .setId(issue.getId())
+      .setRouting(issue.getRouting().orElseThrow(() -> new IllegalStateException("IssueDoc should define a routing")))
       .setSource(issue.getFields())
       .request();
   }
 
   private void addProjectDeletionToBulkIndexer(BulkIndexer bulkIndexer, String projectUuid) {
-    SearchRequestBuilder search = esClient.prepareSearch(INDEX_TYPE_ISSUE)
-      .setRouting(projectUuid)
+    SearchRequestBuilder search = esClient.prepareSearch(TYPE_ISSUE.getMainType())
+      .setRouting(AuthorizationDoc.idOf(projectUuid))
       .setQuery(boolQuery().must(termQuery(FIELD_ISSUE_PROJECT_UUID, projectUuid)));
     bulkIndexer.addDeletion(search);
   }
 
   private static EsQueueDto createQueueDto(String docId, String docIdType, String projectUuid) {
-    return EsQueueDto.create(INDEX_TYPE_ISSUE.format(), docId, docIdType, projectUuid);
+    return EsQueueDto.create(TYPE_ISSUE.format(), docId, docIdType, projectUuid);
   }
 
   private BulkIndexer createBulkIndexer(Size size, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_ISSUE, size, listener);
+    return new BulkIndexer(esClient, TYPE_ISSUE, size, listener);
   }
 }
index de4ded099825cfbf9e0401571ec7c1b5cd6246ee..4d2f68acd0d3aa6bb0140c674a43cfab70c88a18 100644 (file)
@@ -29,6 +29,7 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.server.es.BaseDoc;
+import org.sonar.server.permission.index.AuthorizationDoc;
 
 import static org.sonar.api.measures.Metric.Level.ERROR;
 import static org.sonar.api.measures.Metric.Level.OK;
@@ -47,13 +48,14 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIEL
 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.FIELD_UUID;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ProjectMeasuresDoc extends BaseDoc {
 
   public static final Map<String, Integer> QUALITY_GATE_STATUS = ImmutableMap.of(OK.name(), 1, WARN.name(), 2, ERROR.name(), 3);
 
   public ProjectMeasuresDoc() {
-    super(new HashMap<>(8));
+    super(TYPE_PROJECT_MEASURES, new HashMap<>(8));
   }
 
   @Override
@@ -61,18 +63,9 @@ public class ProjectMeasuresDoc extends BaseDoc {
     return getField(FIELD_UUID);
   }
 
-  @Override
-  public String getRouting() {
-    return getId();
-  }
-
-  @Override
-  public String getParent() {
-    return getId();
-  }
-
   public ProjectMeasuresDoc setId(String s) {
     setField(FIELD_UUID, s);
+    setParent(AuthorizationDoc.idOf(s));
     return this;
   }
 
index 6905b9c2f842be199899aab019c9d5e37b6591f9..ed1c40ea8b878db8de753a038f0362ba4b90e1dc 100644 (file)
 package org.sonar.server.measure.index;
 
 import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType.IndexRelationType;
+import org.sonar.server.es.newindex.NewAuthorizedIndex;
+import org.sonar.server.es.newindex.TypeMapping;
+import org.sonar.server.permission.index.IndexAuthorizationConstants;
 
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-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;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 public class ProjectMeasuresIndexDefinition implements IndexDefinition {
 
-  public static final IndexType INDEX_TYPE_PROJECT_MEASURES = new IndexType("projectmeasures", "projectmeasure");
+  public static final Index DESCRIPTOR = Index.withRelations("projectmeasures");
+  public static final IndexType.IndexMainType TYPE_AUTHORIZATION = IndexType.main(DESCRIPTOR, IndexAuthorizationConstants.TYPE_AUTHORIZATION);
+  public static final IndexRelationType TYPE_PROJECT_MEASURES = IndexType.relation(TYPE_AUTHORIZATION, "projectmeasure");
+
   public static final String FIELD_UUID = "uuid";
   public static final String FIELD_ORGANIZATION_UUID = "organizationUuid";
 
@@ -52,23 +60,36 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
   public static final String FIELD_DISTRIB_NCLOC = "ncloc";
 
   private final Configuration config;
+  private final boolean enableSource;
+
+  private ProjectMeasuresIndexDefinition(Configuration config, boolean enableSource) {
+    this.config = config;
+    this.enableSource = enableSource;
+  }
+
+  public ProjectMeasuresIndexDefinition(Configuration config) {
+    this(config, false);
+  }
 
-  public ProjectMeasuresIndexDefinition(Configuration settings) {
-    this.config = settings;
+  /**
+   * Keep the document sources in index so that indexer tests can verify content
+   * of indexed documents.
+   */
+  public static ProjectMeasuresIndexDefinition createForTest() {
+    return new ProjectMeasuresIndexDefinition(new MapSettings().asConfig(), true);
   }
 
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_PROJECT_MEASURES.getIndex(),
+    NewAuthorizedIndex index = context.createWithAuthorization(
+      DESCRIPTOR,
       newBuilder(config)
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         .setDefaultNbOfShards(5)
-        .build());
-
-    NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_PROJECT_MEASURES.getType())
-      .requireProjectAuthorization();
+        .build())
+      .setEnableSource(enableSource);
 
+    TypeMapping mapping = index.createTypeMapping(TYPE_PROJECT_MEASURES);
     mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_ORGANIZATION_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
@@ -85,6 +106,5 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
       .addIntegerField(FIELD_DISTRIB_NCLOC)
       .build();
     mapping.createDateTimeField(FIELD_ANALYSED_AT);
-    mapping.setEnableSource(false);
   }
 }
index 44f24c64a77d9012973a2b3e86c135d34abfd9af..851da90388c107745b8acd3c726199dddd36bfa5 100644 (file)
@@ -28,7 +28,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import javax.annotation.Nullable;
-import org.elasticsearch.action.index.IndexRequest;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
@@ -44,15 +43,16 @@ import org.sonar.server.es.IndexingListener;
 import org.sonar.server.es.IndexingResult;
 import org.sonar.server.es.OneToOneResilientIndexingListener;
 import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.permission.index.AuthorizationDoc;
 import org.sonar.server.permission.index.AuthorizationScope;
 import org.sonar.server.permission.index.NeedAuthorizationIndexer;
 
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
-  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_PROJECT_MEASURES, project -> Qualifiers.PROJECT.equals(project.getQualifier()));
-  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(INDEX_TYPE_PROJECT_MEASURES);
+  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(TYPE_PROJECT_MEASURES, project -> Qualifiers.PROJECT.equals(project.getQualifier()));
+  private static final ImmutableSet<IndexType> INDEX_TYPES = ImmutableSet.of(TYPE_PROJECT_MEASURES);
 
   private final DbClient dbClient;
   private final EsClient esClient;
@@ -96,7 +96,7 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
       case PROJECT_TAGS_UPDATE:
       case PROJECT_DELETION:
         List<EsQueueDto> items = projectUuids.stream()
-          .map(projectUuid -> EsQueueDto.create(INDEX_TYPE_PROJECT_MEASURES.format(), projectUuid, null, projectUuid))
+          .map(projectUuid -> EsQueueDto.create(TYPE_PROJECT_MEASURES.format(), projectUuid, null, projectUuid))
           .collect(MoreCollectors.toArrayList(projectUuids.size()));
         return dbClient.esQueueDao().insert(dbSession, items);
 
@@ -108,7 +108,7 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
 
   public IndexingResult commitAndIndex(DbSession dbSession, Collection<String> projectUuids) {
     List<EsQueueDto> items = projectUuids.stream()
-      .map(projectUuid -> EsQueueDto.create(INDEX_TYPE_PROJECT_MEASURES.format(), projectUuid, null, projectUuid))
+      .map(projectUuid -> EsQueueDto.create(TYPE_PROJECT_MEASURES.format(), projectUuid, null, projectUuid))
       .collect(MoreCollectors.toArrayList(projectUuids.size()));
     dbClient.esQueueDao().insert(dbSession, items);
 
@@ -132,7 +132,7 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
       String projectUuid = it.next();
       try (ProjectMeasuresIndexerIterator rowIt = ProjectMeasuresIndexerIterator.create(dbSession, projectUuid)) {
         while (rowIt.hasNext()) {
-          bulkIndexer.add(newIndexRequest(toProjectMeasuresDoc(rowIt.next())));
+          bulkIndexer.add(toProjectMeasuresDoc(rowIt.next()).toIndexRequest());
           it.remove();
         }
       }
@@ -140,7 +140,7 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
 
     // the remaining uuids reference projects that don't exist in db. They must
     // be deleted from index.
-    projectUuids.forEach(projectUuid -> bulkIndexer.addDeletion(INDEX_TYPE_PROJECT_MEASURES, projectUuid, projectUuid));
+    projectUuids.forEach(projectUuid -> bulkIndexer.addDeletion(TYPE_PROJECT_MEASURES, projectUuid, AuthorizationDoc.idOf(projectUuid)));
 
     return bulkIndexer.stop();
   }
@@ -153,22 +153,14 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
       bulkIndexer.start();
       while (rowIt.hasNext()) {
         ProjectMeasures doc = rowIt.next();
-        bulkIndexer.add(newIndexRequest(toProjectMeasuresDoc(doc)));
+        bulkIndexer.add(toProjectMeasuresDoc(doc).toIndexRequest());
       }
       bulkIndexer.stop();
     }
   }
 
   private BulkIndexer createBulkIndexer(Size bulkSize, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_PROJECT_MEASURES, bulkSize, listener);
-  }
-
-  private static IndexRequest newIndexRequest(ProjectMeasuresDoc doc) {
-    String projectUuid = doc.getId();
-    return new IndexRequest(INDEX_TYPE_PROJECT_MEASURES.getIndex(), INDEX_TYPE_PROJECT_MEASURES.getType(), projectUuid)
-      .routing(projectUuid)
-      .parent(projectUuid)
-      .source(doc.getFields());
+    return new BulkIndexer(esClient, TYPE_PROJECT_MEASURES, bulkSize, listener);
   }
 
   private static ProjectMeasuresDoc toProjectMeasuresDoc(ProjectMeasures projectMeasures) {
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/permission/index/AuthorizationDoc.java
new file mode 100644 (file)
index 0000000..c9e782b
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.List;
+import java.util.Optional;
+import org.sonar.server.es.BaseDoc;
+import org.sonar.server.es.IndexType;
+
+import static java.util.Objects.requireNonNull;
+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;
+
+public class AuthorizationDoc extends BaseDoc {
+  private static final String ID_PREFIX = "auth_";
+  private final String projectUuid;
+
+  private AuthorizationDoc(IndexType indexType, String projectUuid) {
+    super(indexType);
+    this.projectUuid = projectUuid;
+  }
+
+  public static AuthorizationDoc fromDto(IndexType indexType, IndexPermissions dto) {
+    AuthorizationDoc res = new AuthorizationDoc(indexType, dto.getProjectUuid());
+    if (dto.isAllowAnyone()) {
+      return res.setAllowAnyone();
+    }
+    return res.setRestricted(dto.getGroupIds(), dto.getUserIds());
+  }
+
+  @Override
+  public String getId() {
+    return idOf(projectUuid);
+  }
+
+  public static String idOf(String projectUuid) {
+    requireNonNull(projectUuid, "projectUuid can't be null");
+    return ID_PREFIX + projectUuid;
+  }
+
+  public static String projectUuidOf(String id) {
+    if (id.startsWith(ID_PREFIX)) {
+      return id.substring(ID_PREFIX.length());
+    }
+    return id;
+  }
+
+  @Override
+  protected Optional<String> getSimpleMainTypeRouting() {
+    return Optional.of(projectUuid);
+  }
+
+  private AuthorizationDoc setAllowAnyone() {
+    setField(FIELD_ALLOW_ANYONE, true);
+    return this;
+  }
+
+  private AuthorizationDoc setRestricted(List<Integer> groupIds, List<Integer> userIds) {
+    setField(FIELD_ALLOW_ANYONE, false);
+    setField(FIELD_GROUP_IDS, groupIds);
+    setField(FIELD_USER_IDS, userIds);
+    return this;
+  }
+}
index c5ca1107870e450fd49e590f574753b12cae1224..e7337040f3f2f08807b5ba3cd92fdcc922654663 100644 (file)
@@ -21,7 +21,8 @@ package org.sonar.server.permission.index;
 
 import java.util.function.Predicate;
 import javax.annotation.concurrent.Immutable;
-import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
@@ -29,28 +30,31 @@ import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE
 
 @Immutable
 public final class AuthorizationScope {
-  private final IndexType indexType;
+  private final IndexMainType indexType;
   private final Predicate<IndexPermissions> projectPredicate;
 
-  public AuthorizationScope(IndexType indexType, Predicate<IndexPermissions> projectPredicate) {
-    this.indexType = getAuthorizationIndexType(indexType);
+  public AuthorizationScope(IndexRelationType functionalType, Predicate<IndexPermissions> projectPredicate) {
+    this.indexType = getAuthorizationIndexType(functionalType);
     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);
+  private static IndexMainType getAuthorizationIndexType(IndexRelationType functionalType) {
+    requireNonNull(functionalType);
+    IndexMainType mainType = functionalType.getMainType();
+    checkArgument(
+      TYPE_AUTHORIZATION.equals(mainType.getType()),
+      "Index %s doesn't seem to be an authorized index as main type is not %s (got %s)",
+      mainType.getIndex(), TYPE_AUTHORIZATION, mainType.getType());
+    return mainType;
   }
 
   /**
    * Identifier of the authorization type (in the same index than the original IndexType, passed into the constructor).
    */
-  public IndexType getIndexType() {
+  public IndexMainType getIndexType() {
     return indexType;
   }
 
index 2a0c222f6e88d901bd051eaf7d0c8e502db603bb..ff4567b89d6ad4a653aa375c04b31036882d7474 100644 (file)
 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";
+  public static final String TYPE_AUTHORIZATION = "auth";
+  public static final String FIELD_GROUP_IDS = TYPE_AUTHORIZATION + "_groupIds";
+  public static final String FIELD_USER_IDS = TYPE_AUTHORIZATION + "_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";
+  public static final String FIELD_ALLOW_ANYONE = TYPE_AUTHORIZATION + "_allowAnyone";
 
   private IndexAuthorizationConstants() {
     // prevents instantiation
index 90257da8ffff3122096c03af02ff53369f916553..24ca4ddbd7f93938a8398af830547f227d1a0409 100644 (file)
@@ -29,41 +29,48 @@ import static org.apache.commons.lang.StringUtils.containsIgnoreCase;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_ID;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
-import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_RULE_ID;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_ID;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
 
 public class ActiveRuleDoc extends BaseDoc {
 
+  public static final String DOC_ID_PREFIX = "ar_";
+
   public ActiveRuleDoc(long id) {
-    super(Maps.newHashMapWithExpectedSize(10));
+    super(TYPE_ACTIVE_RULE, Maps.newHashMapWithExpectedSize(10));
     setField(FIELD_ACTIVE_RULE_ID, String.valueOf(id));
   }
 
   public ActiveRuleDoc(Map<String, Object> source) {
-    super(source);
+    super(TYPE_ACTIVE_RULE, source);
   }
 
-  @Override
-  public String getId() {
-    return getField(FIELD_ACTIVE_RULE_ID);
+  public static String docIdOf(long activeRuleId) {
+    return docIdOf(String.valueOf(activeRuleId));
   }
 
-  @Override
-  public String getRouting() {
-    return getRuleIdAsString();
+  public static String docIdOf(String activeRuleId) {
+    return DOC_ID_PREFIX + activeRuleId;
   }
 
-  @Override
-  public String getParent() {
-    return getRuleIdAsString();
+  public static long activeRuleIdOf(String docId) {
+    if (docId.startsWith(DOC_ID_PREFIX)) {
+      return Long.valueOf(docId.substring(DOC_ID_PREFIX.length()));
+    }
+    // support for old active rule docId
+    return Long.valueOf(docId);
   }
 
-  private String getRuleIdAsString() {
-    return getField(FIELD_ACTIVE_RULE_RULE_ID);
+  @Override
+  public String getId() {
+    return docIdOf(getField(FIELD_ACTIVE_RULE_ID));
   }
 
   ActiveRuleDoc setRuleId(int ruleId) {
-    setField(FIELD_ACTIVE_RULE_RULE_ID, String.valueOf(ruleId));
+    String parent = String.valueOf(ruleId);
+    setParent(parent);
+    setField(FIELD_RULE_ID, parent);
     return this;
   }
 
index 5f7f3948462a2f9f63623d053b38f7bc86eb831f..8ca2cefe8675c5f50a39f2a535e76e9fe6ec5e0f 100644 (file)
@@ -48,12 +48,13 @@ import org.sonar.server.es.OneToOneResilientIndexingListener;
 import org.sonar.server.es.ResilientIndexer;
 import org.sonar.server.qualityprofile.ActiveRuleChange;
 import org.sonar.server.qualityprofile.ActiveRuleInheritance;
-import org.sonar.server.rule.index.RuleIndexDefinition;
 
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.server.qualityprofile.index.ActiveRuleDoc.docIdOf;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_ACTIVE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
 
 public class ActiveRuleIndexer implements ResilientIndexer {
 
@@ -81,13 +82,13 @@ public class ActiveRuleIndexer implements ResilientIndexer {
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_ACTIVE_RULE);
+    return ImmutableSet.of(TYPE_ACTIVE_RULE);
   }
 
   public void commitAndIndex(DbSession dbSession, Collection<ActiveRuleChange> changes) {
     List<EsQueueDto> items = changes.stream()
       .map(ActiveRuleChange::getActiveRule)
-      .map(ar -> newQueueDto(String.valueOf(ar.getId()), ID_TYPE_ACTIVE_RULE_ID, String.valueOf(ar.getRuleId())))
+      .map(ar -> newQueueDto(docIdOf(ar.getId()), ID_TYPE_ACTIVE_RULE_ID, String.valueOf(ar.getRuleId())))
       .collect(toArrayList());
 
     dbClient.esQueueDao().insert(dbSession, items);
@@ -125,13 +126,13 @@ public class ActiveRuleIndexer implements ResilientIndexer {
       return result;
     }
 
-    Map<Long, EsQueueDto> activeRuleItems = new HashMap<>();
+    Map<String, EsQueueDto> activeRuleItems = new HashMap<>();
     Map<String, EsQueueDto> ruleProfileItems = new HashMap<>();
     items.forEach(i -> {
       if (ID_TYPE_RULE_PROFILE_UUID.equals(i.getDocIdType())) {
         ruleProfileItems.put(i.getDocId(), i);
       } else if (ID_TYPE_ACTIVE_RULE_ID.equals(i.getDocIdType())) {
-        activeRuleItems.put(Long.parseLong(i.getDocId()), i);
+        activeRuleItems.put(i.getDocId(), i);
       } else {
         LOGGER.error("Unsupported es_queue.doc_id_type. Removing row from queue: " + i);
         deleteQueueDto(dbSession, i);
@@ -147,24 +148,31 @@ public class ActiveRuleIndexer implements ResilientIndexer {
     return result;
   }
 
-  private IndexingResult doIndexActiveRules(DbSession dbSession, Map<Long, EsQueueDto> activeRuleItems) {
+  private IndexingResult doIndexActiveRules(DbSession dbSession, Map<String, EsQueueDto> activeRuleItems) {
     OneToOneResilientIndexingListener listener = new OneToOneResilientIndexingListener(dbClient, dbSession, activeRuleItems.values());
     BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, listener);
     bulkIndexer.start();
-    Map<Long, EsQueueDto> remaining = new HashMap<>(activeRuleItems);
-    dbClient.activeRuleDao().scrollByIdsForIndexing(dbSession, activeRuleItems.keySet(),
+    Map<String, EsQueueDto> remaining = new HashMap<>(activeRuleItems);
+    dbClient.activeRuleDao().scrollByIdsForIndexing(dbSession, toActiveRuleIds(activeRuleItems),
       i -> {
-        remaining.remove(i.getId());
+        remaining.remove(docIdOf(i.getId()));
         bulkIndexer.add(newIndexRequest(i));
       });
 
     // the remaining ids reference rows that don't exist in db. They must
     // be deleted from index.
-    remaining.values().forEach(item -> bulkIndexer.addDeletion(RuleIndexDefinition.INDEX_TYPE_ACTIVE_RULE,
+    remaining.values().forEach(item -> bulkIndexer.addDeletion(TYPE_ACTIVE_RULE,
       item.getDocId(), item.getDocRouting()));
     return bulkIndexer.stop();
   }
 
+  private static Collection<Long> toActiveRuleIds(Map<String, EsQueueDto> activeRuleItems) {
+    Set<String> docIds = activeRuleItems.keySet();
+    return docIds.stream()
+      .map(ActiveRuleDoc::activeRuleIdOf)
+      .collect(toSet(docIds.size()));
+  }
+
   private IndexingResult doIndexRuleProfiles(DbSession dbSession, Map<String, EsQueueDto> ruleProfileItems) {
     IndexingResult result = new IndexingResult();
 
@@ -176,9 +184,9 @@ public class ActiveRuleIndexer implements ResilientIndexer {
       RulesProfileDto profile = dbClient.qualityProfileDao().selectRuleProfile(dbSession, ruleProfileUUid);
       if (profile == null) {
         // profile does not exist anymore in db --> related documents must be deleted from index rules/activeRule
-        SearchRequestBuilder search = esClient.prepareSearch(INDEX_TYPE_ACTIVE_RULE)
+        SearchRequestBuilder search = esClient.prepareSearch(TYPE_ACTIVE_RULE.getMainType())
           .setQuery(QueryBuilders.boolQuery().must(termQuery(FIELD_ACTIVE_RULE_PROFILE_UUID, ruleProfileUUid)));
-        profileResult = BulkIndexer.delete(esClient, INDEX_TYPE_ACTIVE_RULE, search);
+        profileResult = BulkIndexer.delete(esClient, TYPE_ACTIVE_RULE, search);
 
       } else {
         BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, IndexingListener.FAIL_ON_ERROR);
@@ -202,7 +210,7 @@ public class ActiveRuleIndexer implements ResilientIndexer {
   }
 
   private BulkIndexer createBulkIndexer(Size size, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_ACTIVE_RULE, size, listener);
+    return new BulkIndexer(esClient, TYPE_ACTIVE_RULE, size, listener);
   }
 
   private static IndexRequest newIndexRequest(IndexedActiveRuleDto dto) {
@@ -213,14 +221,10 @@ public class ActiveRuleIndexer implements ResilientIndexer {
     // all the fields must be present, even if value is null
     String inheritance = dto.getInheritance();
     doc.setInheritance(inheritance == null ? ActiveRuleInheritance.NONE.name() : inheritance);
-    return new IndexRequest(INDEX_TYPE_ACTIVE_RULE.getIndex(), INDEX_TYPE_ACTIVE_RULE.getType())
-      .id(doc.getId())
-      .parent(doc.getParent())
-      .routing(doc.getRouting())
-      .source(doc.getFields());
+    return doc.toIndexRequest();
   }
 
   private static EsQueueDto newQueueDto(String docId, String docIdType, @Nullable String routing) {
-    return EsQueueDto.create(INDEX_TYPE_ACTIVE_RULE.format(), docId, docIdType, routing);
+    return EsQueueDto.create(TYPE_ACTIVE_RULE.format(), docId, docIdType, routing);
   }
 }
index 973575f00df6e2612ae7b0a3361e00a363c62307..0ab4ce887475ffe4c9cb1d8dd6ec0a04d5d06c6c 100644 (file)
  */
 package org.sonar.server.rule.index;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Maps;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
@@ -32,17 +35,21 @@ import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.markdown.Markdown;
 import org.sonar.server.es.BaseDoc;
 
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+
+
 /**
  * Implementation of Rule based on an Elasticsearch document
  */
 public class RuleDoc extends BaseDoc {
 
+  @VisibleForTesting
   public RuleDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_RULE, new HashMap<>(fields));
   }
 
   public RuleDoc() {
-    super(Maps.newHashMapWithExpectedSize(15));
+    super(TYPE_RULE, Maps.newHashMapWithExpectedSize(16));
   }
 
   @Override
@@ -60,13 +67,8 @@ public class RuleDoc extends BaseDoc {
   }
 
   @Override
-  public String getRouting() {
-    return idAsString();
-  }
-
-  @Override
-  public String getParent() {
-    return null;
+  protected Optional<String> getSimpleMainTypeRouting() {
+    return Optional.of(idAsString());
   }
 
   public RuleKey key() {
index 950fa811bf29a1045af07e0ac2c2e5e769d2840a..d75627e32910ee93f6446bf743b327731de39ab7 100644 (file)
@@ -27,14 +27,16 @@ import org.sonar.db.rule.RuleExtensionForIndexingDto;
 import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.server.es.BaseDoc;
 
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
+
 public class RuleExtensionDoc extends BaseDoc {
 
   public RuleExtensionDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_RULE_EXTENSION, fields);
   }
 
   public RuleExtensionDoc() {
-    super(Maps.newHashMapWithExpectedSize(4));
+    super(TYPE_RULE_EXTENSION, Maps.newHashMapWithExpectedSize(4));
   }
 
   @Override
@@ -42,26 +44,18 @@ public class RuleExtensionDoc extends BaseDoc {
     return idOf(getRuleId(), getScope());
   }
 
-  @Override
-  public String getRouting() {
-    return ruleIdAsString();
-  }
-
-  @Override
-  public String getParent() {
-    return ruleIdAsString();
-  }
-
   public int getRuleId() {
     return Integer.valueOf(ruleIdAsString());
   }
 
   private String ruleIdAsString() {
-    return getField(RuleIndexDefinition.FIELD_RULE_EXTENSION_RULE_ID);
+    return getField(RuleIndexDefinition.FIELD_RULE_ID);
   }
 
   public RuleExtensionDoc setRuleId(int ruleId) {
-    setField(RuleIndexDefinition.FIELD_RULE_EXTENSION_RULE_ID, String.valueOf(ruleId));
+    String parent = String.valueOf(ruleId);
+    setField(RuleIndexDefinition.FIELD_RULE_ID, parent);
+    setParent(parent);
     return this;
   }
 
index 385f270bc129331140dac79b596d42263c5b15de..b3a121d62007c439dca8c9322fde9f78c8a0daa3 100644 (file)
@@ -59,12 +59,12 @@ import org.sonar.api.utils.System2;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.server.es.DefaultIndexSettings;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.EsUtils;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.es.newindex.DefaultIndexSettings;
 import org.sonar.server.es.textsearch.JavaTokenizer;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -77,14 +77,14 @@ import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.sonar.server.es.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES;
-import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
 import static org.sonar.server.es.EsUtils.optimizeScrollRequest;
 import static org.sonar.server.es.EsUtils.scrollIds;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY;
@@ -105,9 +105,9 @@ import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_STATUS;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TYPE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_UPDATED_AT;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_ACTIVE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
 
 /**
  * The unique entry-point to interact with Elasticsearch index "rules".
@@ -131,7 +131,6 @@ public class RuleIndex {
     .map(RuleStatus::toString)
     .collect(MoreCollectors.toList());
 
-  private static final String AGGREGATION_NAME = "_ref";
   private static final String AGGREGATION_NAME_FOR_TAGS = "tagsAggregation";
 
   private final EsClient client;
@@ -144,7 +143,7 @@ public class RuleIndex {
 
   public SearchIdResult<Integer> search(RuleQuery query, SearchOptions options) {
     SearchRequestBuilder esSearch = client
-      .prepareSearch(INDEX_TYPE_RULE);
+      .prepareSearch(TYPE_RULE);
 
     QueryBuilder qb = buildQuery(query);
     Map<String, QueryBuilder> filters = buildFilters(query);
@@ -172,7 +171,7 @@ public class RuleIndex {
    */
   public Iterator<Integer> searchAll(RuleQuery query) {
     SearchRequestBuilder esSearch = client
-      .prepareSearch(INDEX_TYPE_RULE)
+      .prepareSearch(TYPE_RULE)
       .setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES));
 
     optimizeScrollRequest(esSearch);
@@ -236,6 +235,11 @@ public class RuleIndex {
   private static Map<String, QueryBuilder> buildFilters(RuleQuery query) {
     Map<String, QueryBuilder> filters = new HashMap<>();
 
+    /* Add enforced filter on main type Rule */
+    filters.put(
+      FIELD_INDEX_TYPE,
+      boolQuery().must(QueryBuilders.termsQuery(FIELD_INDEX_TYPE, TYPE_RULE.getType())));
+
     /* Add enforced filter on rules that are REMOVED */
     filters.put(FIELD_RULE_STATUS,
       boolQuery().mustNot(
@@ -323,19 +327,19 @@ public class RuleIndex {
 
       if (TRUE.equals(query.getActivation())) {
         filters.put("activation",
-          JoinQueryBuilders.hasChildQuery(INDEX_TYPE_ACTIVE_RULE.getType(),
+          JoinQueryBuilders.hasChildQuery(TYPE_ACTIVE_RULE.getName(),
             childQuery, ScoreMode.None));
       } else if (FALSE.equals(query.getActivation())) {
         filters.put("activation",
           boolQuery().mustNot(
-            JoinQueryBuilders.hasChildQuery(INDEX_TYPE_ACTIVE_RULE.getType(),
+            JoinQueryBuilders.hasChildQuery(TYPE_ACTIVE_RULE.getName(),
               childQuery, ScoreMode.None)));
       }
       QProfileDto compareToQProfile = query.getCompareToQProfile();
       if (compareToQProfile != null) {
         filters.put("comparison",
           JoinQueryBuilders.hasChildQuery(
-            INDEX_TYPE_ACTIVE_RULE.getType(),
+            TYPE_ACTIVE_RULE.getName(),
             boolQuery().must(QueryBuilders.termQuery(FIELD_ACTIVE_RULE_PROFILE_UUID, compareToQProfile.getRulesProfileUuid())),
             ScoreMode.None));
       }
@@ -350,7 +354,7 @@ public class RuleIndex {
       .map(tag -> boolQuery()
         .filter(QueryBuilders.termQuery(FIELD_RULE_EXTENSION_TAGS, tag))
         .filter(termsQuery(FIELD_RULE_EXTENSION_SCOPE, RuleExtensionScope.system().getScope(), RuleExtensionScope.organization(organization).getScope())))
-      .map(childQuery -> JoinQueryBuilders.hasChildQuery(INDEX_TYPE_RULE_EXTENSION.getType(), childQuery, ScoreMode.None))
+      .map(childQuery -> JoinQueryBuilders.hasChildQuery(TYPE_RULE_EXTENSION.getName(), childQuery, ScoreMode.None))
       .forEach(q::should);
     return q;
   }
@@ -428,7 +432,7 @@ public class RuleIndex {
             RuleExtensionScope.organization(query.getOrganization()).getScope()))
           .subAggregation(termsAggregation);
 
-        return JoinAggregationBuilders.children("children_for_" + termsAggregation.getName(), INDEX_TYPE_RULE_EXTENSION.getType())
+        return JoinAggregationBuilders.children("children_for_" + termsAggregation.getName(), TYPE_RULE_EXTENSION.getName())
           .subAggregation(scopeAggregation);
       };
 
@@ -473,7 +477,7 @@ public class RuleIndex {
       // so the rule filter has to be used as parent filter for active rules
       // from which we remove filters that concern active rules ("activation")
       HasParentQueryBuilder ruleFilter = JoinQueryBuilders.hasParentQuery(
-        INDEX_TYPE_RULE.getType(),
+        TYPE_RULE.getType(),
         stickyFacetBuilder.getStickyFacetFilter("activation"),
         false);
 
@@ -483,7 +487,7 @@ public class RuleIndex {
       RuleIndex.addTermFilter(childrenFilter, FIELD_ACTIVE_RULE_INHERITANCE, query.getInheritance());
       QueryBuilder activeRuleFilter = childrenFilter.must(ruleFilter);
 
-      AggregationBuilder activeSeverities = JoinAggregationBuilders.children(FACET_ACTIVE_SEVERITIES + "_children", INDEX_TYPE_ACTIVE_RULE.getType())
+      AggregationBuilder activeSeverities = JoinAggregationBuilders.children(FACET_ACTIVE_SEVERITIES + "_children", TYPE_ACTIVE_RULE.getName())
         .subAggregation(
           AggregationBuilders.filter(FACET_ACTIVE_SEVERITIES + "_filter", activeRuleFilter)
             .subAggregation(
@@ -533,28 +537,6 @@ public class RuleIndex {
     esSearch.setSize(options.getLimit());
   }
 
-  public List<String> terms(String fields) {
-    return terms(fields, null, Integer.MAX_VALUE);
-  }
-
-  public List<String> terms(String fields, @Nullable String query, int size) {
-    TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME)
-      .field(fields)
-      .size(size)
-      .minDocCount(1);
-    if (query != null) {
-      termsAggregation.includeExclude(new IncludeExclude(".*" + escapeSpecialRegexChars(query) + ".*", null));
-    }
-    SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_RULE, INDEX_TYPE_ACTIVE_RULE)
-      .setQuery(matchAllQuery())
-      .setSize(0)
-      .addAggregation(termsAggregation);
-
-    SearchResponse esResponse = request.get();
-    return EsUtils.termsKeys(esResponse.getAggregations().get(AGGREGATION_NAME));
-  }
-
   public List<String> listTags(@Nullable OrganizationDto organization, @Nullable String query, int size) {
     int maxPageSize = 500;
     checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize);
@@ -583,7 +565,7 @@ public class RuleIndex {
       .ifPresent(termsAggregation::includeExclude);
 
     SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_RULE_EXTENSION)
+      .prepareSearch(TYPE_RULE_EXTENSION.getMainType())
       .setQuery(boolQuery().filter(scopeFilter))
       .setSize(0)
       .addAggregation(termsAggregation);
index 07075b2c589e185bf4ac2eae33f745a86f275b62..4d0226a02299bce15e0355aba572371371fe9b4a 100644 (file)
  */
 package org.sonar.server.rule.index;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+import org.sonar.server.es.newindex.NewRegularIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
-import static org.sonar.server.es.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
-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;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 /**
  * Definition of ES index "rules", including settings and fields.
  */
 public class RuleIndexDefinition implements IndexDefinition {
 
-  private static final String INDEX = "rules";
-
-  public static final IndexType INDEX_TYPE_RULE = new IndexType(INDEX, "rule");
-  public static final String FIELD_RULE_ID = "id";
+  public static final Index DESCRIPTOR = Index.withRelations("rules");
+  public static final IndexMainType TYPE_RULE = IndexType.main(DESCRIPTOR, "rule");
+  public static final String FIELD_RULE_ID = "ruleId";
   public static final String FIELD_RULE_KEY = "key";
   public static final String FIELD_RULE_REPOSITORY = "repo";
   public static final String FIELD_RULE_RULE_KEY = "ruleKey";
@@ -66,21 +68,19 @@ public class RuleIndexDefinition implements IndexDefinition {
     FIELD_RULE_KEY);
 
   // Rule extension fields
-  public static final IndexType INDEX_TYPE_RULE_EXTENSION = new IndexType(INDEX, "ruleExtension");
+  public static final IndexRelationType TYPE_RULE_EXTENSION = IndexType.relation(TYPE_RULE, "ruleExtension");
   /**
    * The uuid of a {@link RuleExtensionScope}
    */
-  public static final String FIELD_RULE_EXTENSION_SCOPE = "scope";
-  public static final String FIELD_RULE_EXTENSION_RULE_ID = "ruleId";
-  public static final String FIELD_RULE_EXTENSION_TAGS = "tags";
+  public static final String FIELD_RULE_EXTENSION_SCOPE = "ruleExt_scope";
+  public static final String FIELD_RULE_EXTENSION_TAGS = "ruleExt_tags";
 
   // Active rule fields
-  public static final IndexType INDEX_TYPE_ACTIVE_RULE = new IndexType(INDEX, "activeRule");
-  public static final String FIELD_ACTIVE_RULE_ID = "id";
-  public static final String FIELD_ACTIVE_RULE_RULE_ID = "ruleId";
-  public static final String FIELD_ACTIVE_RULE_INHERITANCE = "inheritance";
-  public static final String FIELD_ACTIVE_RULE_PROFILE_UUID = "ruleProfile";
-  public static final String FIELD_ACTIVE_RULE_SEVERITY = "severity";
+  public static final IndexRelationType TYPE_ACTIVE_RULE = IndexType.relation(TYPE_RULE, "activeRule");
+  public static final String FIELD_ACTIVE_RULE_ID = "activeRule_id";
+  public static final String FIELD_ACTIVE_RULE_INHERITANCE = "activeRule_inheritance";
+  public static final String FIELD_ACTIVE_RULE_PROFILE_UUID = "activeRule_ruleProfile";
+  public static final String FIELD_ACTIVE_RULE_SEVERITY = "activeRule_severity";
 
   private final Configuration config;
   private final boolean enableSource;
@@ -104,39 +104,19 @@ public class RuleIndexDefinition implements IndexDefinition {
 
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_RULE.getIndex(),
+    NewRegularIndex index = context.create(
+      DESCRIPTOR,
       newBuilder(config)
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         // Default nb of shards should be greater than 1 in order to
         // easily detect routing misconfiguration.
         // See https://jira.sonarsource.com/browse/SONAR-9489
         .setDefaultNbOfShards(2)
-        .build());
-
-    // Active rule type
-    NewIndex.NewIndexType activeRuleMapping = index.createType(INDEX_TYPE_ACTIVE_RULE.getType());
-    activeRuleMapping.setEnableSource(enableSource);
-    activeRuleMapping.setAttribute("_parent", ImmutableMap.of("type", INDEX_TYPE_RULE.getType()));
-
-    activeRuleMapping.keywordFieldBuilder(FIELD_ACTIVE_RULE_ID).disableNorms().build();
-    activeRuleMapping.keywordFieldBuilder(FIELD_ACTIVE_RULE_RULE_ID).disableNorms().build();
-    activeRuleMapping.keywordFieldBuilder(FIELD_ACTIVE_RULE_PROFILE_UUID).disableNorms().build();
-    activeRuleMapping.keywordFieldBuilder(FIELD_ACTIVE_RULE_INHERITANCE).disableNorms().build();
-    activeRuleMapping.keywordFieldBuilder(FIELD_ACTIVE_RULE_SEVERITY).disableNorms().build();
-
-    // Rule extension type
-    NewIndex.NewIndexType ruleExtensionType = index.createType(INDEX_TYPE_RULE_EXTENSION.getType());
-    ruleExtensionType.setEnableSource(enableSource);
-    ruleExtensionType.setAttribute("_parent", ImmutableMap.of("type", INDEX_TYPE_RULE.getType()));
-
-    ruleExtensionType.keywordFieldBuilder(FIELD_RULE_EXTENSION_SCOPE).disableNorms().build();
-    ruleExtensionType.keywordFieldBuilder(FIELD_RULE_EXTENSION_TAGS).build();
+        .build())
+      .setEnableSource(enableSource);
 
     // Rule type
-    NewIndex.NewIndexType ruleMapping = index.createType(INDEX_TYPE_RULE.getType());
-    ruleMapping.setEnableSource(enableSource);
-
+    TypeMapping ruleMapping = index.createTypeMapping(TYPE_RULE);
     ruleMapping.keywordFieldBuilder(FIELD_RULE_ID).disableNorms().build();
     ruleMapping.keywordFieldBuilder(FIELD_RULE_KEY).addSubFields(SORTABLE_ANALYZER).build();
     ruleMapping.keywordFieldBuilder(FIELD_RULE_RULE_KEY).addSubFields(SORTABLE_ANALYZER).build();
@@ -162,5 +142,17 @@ public class RuleIndexDefinition implements IndexDefinition {
 
     ruleMapping.createLongField(FIELD_RULE_CREATED_AT);
     ruleMapping.createLongField(FIELD_RULE_UPDATED_AT);
+
+    // Active rule
+    index.createTypeMapping(TYPE_ACTIVE_RULE)
+      .keywordFieldBuilder(FIELD_ACTIVE_RULE_ID).disableNorms().build()
+      .keywordFieldBuilder(FIELD_ACTIVE_RULE_PROFILE_UUID).disableNorms().build()
+      .keywordFieldBuilder(FIELD_ACTIVE_RULE_INHERITANCE).disableNorms().build()
+      .keywordFieldBuilder(FIELD_ACTIVE_RULE_SEVERITY).disableNorms().build();
+
+    // Rule extension
+    index.createTypeMapping(TYPE_RULE_EXTENSION)
+      .keywordFieldBuilder(FIELD_RULE_EXTENSION_SCOPE).disableNorms().build()
+      .keywordFieldBuilder(FIELD_RULE_EXTENSION_TAGS).build();
   }
 }
index 7ed25632d257038ce3e557dd370e35fdc5e7c8ec..b91a5fff10d496e7091aa3cc2f5deee566bf191e 100644 (file)
@@ -23,17 +23,14 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import java.util.Collection;
 import java.util.List;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
-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.db.es.RuleExtensionId;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleExtensionForIndexingDto;
-import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.BulkIndexer.Size;
 import org.sonar.server.es.EsClient;
@@ -47,8 +44,8 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
 
 public class RuleIndexer implements ResilientIndexer {
 
@@ -62,7 +59,7 @@ public class RuleIndexer implements ResilientIndexer {
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_RULE, INDEX_TYPE_RULE_EXTENSION);
+    return ImmutableSet.of(TYPE_RULE, TYPE_RULE_EXTENSION);
   }
 
   @Override
@@ -72,16 +69,16 @@ public class RuleIndexer implements ResilientIndexer {
       bulk.start();
 
       // index all definitions and system extensions
-      if (uninitializedIndexTypes.contains(INDEX_TYPE_RULE)) {
+      if (uninitializedIndexTypes.contains(TYPE_RULE)) {
         dbClient.ruleDao().scrollIndexingRules(dbSession, dto -> {
-          bulk.add(newRuleDocIndexRequest(dto));
-          bulk.add(newRuleExtensionDocIndexRequest(dto));
+          bulk.add(RuleDoc.of(dto).toIndexRequest());
+          bulk.add(RuleExtensionDoc.of(dto).toIndexRequest());
         });
       }
 
       // index all organization extensions
-      if (uninitializedIndexTypes.contains(INDEX_TYPE_RULE_EXTENSION)) {
-        dbClient.ruleDao().scrollIndexingRuleExtensions(dbSession, dto -> bulk.add(newRuleExtensionDocIndexRequest(dto)));
+      if (uninitializedIndexTypes.contains(TYPE_RULE_EXTENSION)) {
+        dbClient.ruleDao().scrollIndexingRuleExtensions(dbSession, dto -> bulk.add(RuleExtensionDoc.of(dto).toIndexRequest()));
       }
 
       bulk.stop();
@@ -123,14 +120,18 @@ public class RuleIndexer implements ResilientIndexer {
   public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
     IndexingResult result = new IndexingResult();
     if (!items.isEmpty()) {
-      ListMultimap<IndexType, EsQueueDto> itemsByType = groupItemsByType(items);
-      result.add(doIndexRules(dbSession, itemsByType.get(INDEX_TYPE_RULE)));
-      result.add(doIndexRuleExtensions(dbSession, itemsByType.get(INDEX_TYPE_RULE_EXTENSION)));
+      ListMultimap<String, EsQueueDto> itemsByType = groupItemsByIndexTypeFormat(items);
+      doIndexRules(dbSession, itemsByType.get(TYPE_RULE.format())).ifPresent(result::add);
+      doIndexRuleExtensions(dbSession, itemsByType.get(TYPE_RULE_EXTENSION.format())).ifPresent(result::add);
     }
     return result;
   }
 
-  private IndexingResult doIndexRules(DbSession dbSession, List<EsQueueDto> items) {
+  private Optional<IndexingResult> doIndexRules(DbSession dbSession, List<EsQueueDto> items) {
+    if (items.isEmpty()) {
+      return Optional.empty();
+    }
+
     BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items));
     bulkIndexer.start();
 
@@ -141,22 +142,26 @@ public class RuleIndexer implements ResilientIndexer {
 
     dbClient.ruleDao().scrollIndexingRulesByKeys(dbSession, ruleIds,
       r -> {
-        bulkIndexer.add(newRuleDocIndexRequest(r));
-        bulkIndexer.add(newRuleExtensionDocIndexRequest(r));
+        bulkIndexer.add(RuleDoc.of(r).toIndexRequest());
+        bulkIndexer.add(RuleExtensionDoc.of(r).toIndexRequest());
         ruleIds.remove(r.getId());
       });
 
     // the remaining items reference rows that don't exist in db. They must
     // be deleted from index.
     ruleIds.forEach(ruleId -> {
-      bulkIndexer.addDeletion(INDEX_TYPE_RULE, ruleId.toString(), ruleId.toString());
-      bulkIndexer.addDeletion(INDEX_TYPE_RULE_EXTENSION, RuleExtensionDoc.idOf(ruleId, RuleExtensionScope.system()), ruleId.toString());
+      bulkIndexer.addDeletion(TYPE_RULE, ruleId.toString(), ruleId.toString());
+      bulkIndexer.addDeletion(TYPE_RULE_EXTENSION, RuleExtensionDoc.idOf(ruleId, RuleExtensionScope.system()), ruleId.toString());
     });
 
-    return bulkIndexer.stop();
+    return Optional.of(bulkIndexer.stop());
   }
 
-  private IndexingResult doIndexRuleExtensions(DbSession dbSession, List<EsQueueDto> items) {
+  private Optional<IndexingResult> doIndexRuleExtensions(DbSession dbSession, List<EsQueueDto> items) {
+    if (items.isEmpty()) {
+      return Optional.empty();
+    }
+
     BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items));
     bulkIndexer.start();
 
@@ -171,65 +176,37 @@ public class RuleIndexer implements ResilientIndexer {
       r -> {
         RuleExtensionId docId = new RuleExtensionId(r.getOrganizationUuid(), r.getRuleId());
         docIds.remove(docId);
-        bulkIndexer.add(newRuleExtensionDocIndexRequest(r));
+        bulkIndexer.add(RuleExtensionDoc.of(r).toIndexRequest());
       });
 
     // the remaining items reference rows that don't exist in db. They must
     // be deleted from index.
-    docIds.forEach(docId -> bulkIndexer.addDeletion(INDEX_TYPE_RULE_EXTENSION, docId.getId(), String.valueOf(docId.getRuleId())));
-
-    return bulkIndexer.stop();
-  }
-
-  private static IndexRequest newRuleDocIndexRequest(RuleForIndexingDto ruleForIndexingDto) {
-    RuleDoc doc = RuleDoc.of(ruleForIndexingDto);
-
-    return new IndexRequest(INDEX_TYPE_RULE.getIndex(), INDEX_TYPE_RULE.getType())
-      .id(doc.getId())
-      .routing(doc.getRouting())
-      .source(doc.getFields());
-  }
-
-  private static IndexRequest newRuleExtensionDocIndexRequest(RuleForIndexingDto ruleForIndexingDto) {
-    RuleExtensionDoc ruleExtensionDoc = RuleExtensionDoc.of(ruleForIndexingDto);
-
-    return new IndexRequest(INDEX_TYPE_RULE_EXTENSION.getIndex(), INDEX_TYPE_RULE_EXTENSION.getType())
-      .id(ruleExtensionDoc.getId())
-      .routing(ruleExtensionDoc.getRouting())
-      .parent(ruleExtensionDoc.getParent())
-      .source(ruleExtensionDoc.getFields());
-  }
+    docIds.forEach(docId -> bulkIndexer.addDeletion(TYPE_RULE_EXTENSION, docId.getId(), String.valueOf(docId.getRuleId())));
 
-  private static IndexRequest newRuleExtensionDocIndexRequest(RuleExtensionForIndexingDto ruleExtensionForIndexingDto) {
-    RuleExtensionDoc doc = RuleExtensionDoc.of(ruleExtensionForIndexingDto);
-    return new IndexRequest(INDEX_TYPE_RULE_EXTENSION.getIndex(), INDEX_TYPE_RULE_EXTENSION.getType())
-      .id(doc.getId())
-      .routing(doc.getRouting())
-      .parent(doc.getParent())
-      .source(doc.getFields());
+    return Optional.of(bulkIndexer.stop());
   }
 
   private BulkIndexer createBulkIndexer(Size bulkSize, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_RULE, bulkSize, listener);
+    return new BulkIndexer(esClient, TYPE_RULE, bulkSize, listener);
   }
 
-  private static ListMultimap<IndexType, EsQueueDto> groupItemsByType(Collection<EsQueueDto> items) {
-    return items.stream().collect(MoreCollectors.index(i -> IndexType.parse(i.getDocType())));
+  private static ListMultimap<String, EsQueueDto> groupItemsByIndexTypeFormat(Collection<EsQueueDto> items) {
+    return items.stream().collect(MoreCollectors.index(EsQueueDto::getDocType));
   }
 
   private static RuleExtensionId explodeRuleExtensionDocId(EsQueueDto esQueueDto) {
-    checkArgument(Objects.equals(esQueueDto.getDocType(), "rules/ruleExtension"));
+    checkArgument(TYPE_RULE_EXTENSION.format().equals(esQueueDto.getDocType()));
     return new RuleExtensionId(esQueueDto.getDocId());
   }
 
   private static EsQueueDto createQueueDtoForRule(int ruleId) {
     String docId = String.valueOf(ruleId);
-    return EsQueueDto.create("rules/rule", docId, null, docId);
+    return EsQueueDto.create(TYPE_RULE.format(), docId, null, docId);
   }
 
   private static EsQueueDto createQueueDtoForRuleExtension(int ruleId, OrganizationDto organization) {
     String docId = RuleExtensionDoc.idOf(ruleId, RuleExtensionScope.organization(organization));
-    return EsQueueDto.create("rules/ruleExtension", docId, null, String.valueOf(ruleId));
+    return EsQueueDto.create(TYPE_RULE_EXTENSION.format(), docId, null, String.valueOf(ruleId));
   }
 
 }
index ab48d5e1e065458ea76051c3727fd910352147e6..ed435fadfa73149d8405e5f66cc9ffd5fc06457e 100644 (file)
@@ -23,15 +23,15 @@ import com.google.common.collect.Maps;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
-import org.sonar.api.user.User;
 import org.sonar.server.es.BaseDoc;
 
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ORGANIZATION_UUIDS;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
 
-public class UserDoc extends BaseDoc implements User {
+public class UserDoc extends BaseDoc {
 
   public UserDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_USER, fields);
   }
 
   public UserDoc() {
@@ -43,37 +43,23 @@ public class UserDoc extends BaseDoc implements User {
     return uuid();
   }
 
-  @Override
-  public String getRouting() {
-    return null;
-  }
-
-  @Override
-  public String getParent() {
-    return null;
-  }
-
   public String uuid() {
     return getField(UserIndexDefinition.FIELD_UUID);
   }
 
-  @Override
   public String login() {
     return getField(UserIndexDefinition.FIELD_LOGIN);
   }
 
-  @Override
   public String name() {
     return getField(UserIndexDefinition.FIELD_NAME);
   }
 
-  @Override
   @Nullable
   public String email() {
     return getNullableField(UserIndexDefinition.FIELD_EMAIL);
   }
 
-  @Override
   public boolean active() {
     return (Boolean) getField(UserIndexDefinition.FIELD_ACTIVE);
   }
index 69526173cb8f3961b7fe6746b4bd75eef5b8d0e4..a654d3fe7f2967e6539f1049b8f661b4cc6fbe3c 100644 (file)
@@ -41,8 +41,8 @@ import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.USER_SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.USER_SEARCH_GRAMS_ANALYZER;
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ACTIVE;
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_EMAIL;
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_LOGIN;
@@ -69,7 +69,7 @@ public class UserIndex {
   public List<UserDoc> getAtMostThreeActiveUsersForScmAccount(String scmAccount, String organizationUuid) {
     List<UserDoc> result = new ArrayList<>();
     if (!StringUtils.isEmpty(scmAccount)) {
-      SearchRequestBuilder request = esClient.prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+      SearchRequestBuilder request = esClient.prepareSearch(UserIndexDefinition.TYPE_USER)
         .setQuery(boolQuery().must(matchAllQuery()).filter(
           boolQuery()
             .must(termQuery(FIELD_ACTIVE, true))
@@ -86,7 +86,7 @@ public class UserIndex {
   }
 
   public SearchResult<UserDoc> search(UserQuery userQuery, SearchOptions options) {
-    SearchRequestBuilder request = esClient.prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+    SearchRequestBuilder request = esClient.prepareSearch(UserIndexDefinition.TYPE_USER)
       .setSize(options.getLimit())
       .setFrom(options.getOffset())
       .addSort(FIELD_NAME, SortOrder.ASC);
index 852f22663e5ac411b3de95f2b0815cb4b9d19683..a887b55ffdc7286db08577f2818acfe47349a11e 100644 (file)
 package org.sonar.server.user.index;
 
 import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.NewRegularIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
-import static org.sonar.server.es.DefaultIndexSettingsElement.USER_SEARCH_GRAMS_ANALYZER;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.USER_SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 /**
  * Definition of ES index "users", including settings and fields.
  */
 public class UserIndexDefinition implements IndexDefinition {
 
-  public static final IndexType INDEX_TYPE_USER = new IndexType("users", "user");
+  public static final Index DESCRIPTOR = Index.simple("users");
+  public static final IndexMainType TYPE_USER = IndexType.main(DESCRIPTOR, "user");
   public static final String FIELD_UUID = "uuid";
   public static final String FIELD_LOGIN = "login";
   public static final String FIELD_NAME = "name";
@@ -48,15 +53,21 @@ public class UserIndexDefinition implements IndexDefinition {
     this.config = config;
   }
 
+  public static UserIndexDefinition createForTest() {
+    return new UserIndexDefinition(new MapSettings().asConfig());
+  }
+
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(INDEX_TYPE_USER.getIndex(),
+    NewRegularIndex index = context.create(
+      DESCRIPTOR,
       newBuilder(config)
         .setDefaultNbOfShards(1)
-        .build());
+        .build())
+      // all information is retrieved from the index, not only IDs
+      .setEnableSource(true);
 
-    // type "user"
-    NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_USER.getType());
+    TypeMapping mapping = index.createTypeMapping(TYPE_USER);
     mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_LOGIN).addSubFields(USER_SEARCH_GRAMS_ANALYZER).build();
     mapping.keywordFieldBuilder(FIELD_NAME).addSubFields(USER_SEARCH_GRAMS_ANALYZER).build();
index 2f94cd0de27afff409e11939cbcb40ccdd55db4d..0dab27deed720513fb12c8a4abcafe578c938eb2 100644 (file)
@@ -44,7 +44,7 @@ import org.sonar.server.es.ResilientIndexer;
 import static java.util.Collections.singletonList;
 import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.server.user.index.UserIndexDefinition.INDEX_TYPE_USER;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
 
 public class UserIndexer implements ResilientIndexer {
 
@@ -58,7 +58,7 @@ public class UserIndexer implements ResilientIndexer {
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_USER);
+    return ImmutableSet.of(TYPE_USER);
   }
 
   @Override
@@ -84,7 +84,7 @@ public class UserIndexer implements ResilientIndexer {
   public void commitAndIndex(DbSession dbSession, Collection<UserDto> users) {
     List<String> uuids = users.stream().map(UserDto::getUuid).collect(toList());
     List<EsQueueDto> items = uuids.stream()
-      .map(uuid -> EsQueueDto.create(INDEX_TYPE_USER.format(), uuid))
+      .map(uuid -> EsQueueDto.create(TYPE_USER.format(), uuid))
       .collect(MoreCollectors.toArrayList());
 
     dbClient.esQueueDao().insert(dbSession, items);
@@ -128,12 +128,12 @@ public class UserIndexer implements ResilientIndexer {
 
     // the remaining uuids reference rows that don't exist in db. They must
     // be deleted from index.
-    uuids.forEach(uuid -> bulkIndexer.addDeletion(INDEX_TYPE_USER, uuid));
+    uuids.forEach(uuid -> bulkIndexer.addDeletion(TYPE_USER, uuid));
     return bulkIndexer.stop();
   }
 
   private BulkIndexer newBulkIndexer(Size bulkSize, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_USER, bulkSize, listener);
+    return new BulkIndexer(esClient, TYPE_USER, bulkSize, listener);
   }
 
   private static IndexRequest newIndexRequest(UserDto user, ListMultimap<String, String> organizationUuidsByUserUuid) {
@@ -147,9 +147,6 @@ public class UserIndexer implements ResilientIndexer {
     doc.setScmAccounts(UserDto.decodeScmAccounts(user.getScmAccounts()));
     doc.setOrganizationUuids(organizationUuidsByUserUuid.get(user.getUuid()));
 
-    return new IndexRequest(INDEX_TYPE_USER.getIndex(), INDEX_TYPE_USER.getType())
-      .id(doc.getId())
-      .routing(doc.getRouting())
-      .source(doc.getFields());
+    return doc.toIndexRequest();
   }
 }
index 60ba429e0d48fc58190ad3a1b22442e9f6797e77..bff2c49ac2b073d60dddec20339e02202fe7b4a9 100644 (file)
@@ -24,14 +24,16 @@ import java.util.List;
 import java.util.Map;
 import org.sonar.server.es.BaseDoc;
 
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
+
 public class ViewDoc extends BaseDoc {
 
   public ViewDoc(Map<String, Object> fields) {
-    super(fields);
+    super(TYPE_VIEW, fields);
   }
 
   public ViewDoc() {
-    super(Maps.newHashMap());
+    super(TYPE_VIEW, Maps.newHashMap());
   }
 
   @Override
@@ -39,16 +41,6 @@ public class ViewDoc extends BaseDoc {
     return uuid();
   }
 
-  @Override
-  public String getRouting() {
-    return null;
-  }
-
-  @Override
-  public String getParent() {
-    return null;
-  }
-
   public String uuid() {
     return getField(ViewIndexDefinition.FIELD_UUID);
   }
index e226f9473face8483adf6db00e7fc088ff33fae1..652e8fcbe9765770bacc154ea0322fabfaebe614 100644 (file)
@@ -45,7 +45,7 @@ public class ViewIndex {
   }
 
   public List<String> findAllViewUuids() {
-    SearchRequestBuilder esSearch = esClient.prepareSearch(ViewIndexDefinition.INDEX_TYPE_VIEW)
+    SearchRequestBuilder esSearch = esClient.prepareSearch(ViewIndexDefinition.TYPE_VIEW)
       .addSort("_doc", SortOrder.ASC)
       .setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES))
       .setFetchSource(false)
index 21ea4ba23f2cbcdb62d81587b16dd7e0b430f050..2abc9030b7b662a97561b1373c82433cc844a410 100644 (file)
 package org.sonar.server.view.index;
 
 import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.NewRegularIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 /**
  * Definition of ES index "views", including settings and fields.
  */
 public class ViewIndexDefinition implements IndexDefinition {
 
-  public static final IndexType INDEX_TYPE_VIEW = new IndexType("views", "view");
+  public static final Index DESCRIPTOR = Index.simple("views");
+  public static final IndexMainType TYPE_VIEW = IndexType.main(DESCRIPTOR, "view");
   public static final String FIELD_UUID = "uuid";
   public static final String FIELD_PROJECTS = "projects";
 
@@ -41,16 +46,27 @@ public class ViewIndexDefinition implements IndexDefinition {
     this.config = config;
   }
 
+  /**
+   * Keep the document sources in index so that indexer tests can verify content
+   * of indexed documents.
+   */
+  public static ViewIndexDefinition createForTest() {
+    return new ViewIndexDefinition(new MapSettings().asConfig());
+  }
+
   @Override
   public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(
-      INDEX_TYPE_VIEW.getIndex(),
+    NewRegularIndex index = context.create(
+      DESCRIPTOR,
       newBuilder(config)
         .setDefaultNbOfShards(5)
-        .build());
+        .build())
+      // storing source is required because some search queries on issue index use terms lookup query onto the view index
+      // and this requires source to be stored (https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html#query-dsl-terms-lookup)
+      .setEnableSource(true);
 
     // type "view"
-    NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_VIEW.getType());
+    TypeMapping mapping = index.createTypeMapping(TYPE_VIEW);
     mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_PROJECTS).disableNorms().build();
   }
index 0d5086ce2fa9338eaf2f01759397bbb7ece76fdb..5a7c581ac849f59a76de7870ead58d585d3c3433 100644 (file)
@@ -42,7 +42,7 @@ import org.sonar.server.es.ResilientIndexer;
 
 import static com.google.common.collect.Maps.newHashMap;
 import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
-import static org.sonar.server.view.index.ViewIndexDefinition.INDEX_TYPE_VIEW;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
 
 public class ViewIndexer implements ResilientIndexer {
 
@@ -56,7 +56,7 @@ public class ViewIndexer implements ResilientIndexer {
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_VIEW);
+    return ImmutableSet.of(TYPE_VIEW);
   }
 
   @Override
@@ -92,14 +92,14 @@ public class ViewIndexer implements ResilientIndexer {
    * The views lookup cache will be cleared
    */
   public void index(ViewDoc viewDoc) {
-    BulkIndexer bulk = new BulkIndexer(esClient, ViewIndexDefinition.INDEX_TYPE_VIEW, Size.REGULAR);
+    BulkIndexer bulk = new BulkIndexer(esClient, TYPE_VIEW, Size.REGULAR);
     bulk.start();
     doIndex(bulk, viewDoc, true);
     bulk.stop();
   }
 
   private void index(DbSession dbSession, Map<String, String> viewAndProjectViewUuidMap, boolean needClearCache, Size bulkSize) {
-    BulkIndexer bulk = new BulkIndexer(esClient, ViewIndexDefinition.INDEX_TYPE_VIEW, bulkSize);
+    BulkIndexer bulk = new BulkIndexer(esClient, TYPE_VIEW, bulkSize);
     bulk.start();
     for (Map.Entry<String, String> entry : viewAndProjectViewUuidMap.entrySet()) {
       String viewUuid = entry.getKey();
@@ -119,7 +119,10 @@ public class ViewIndexer implements ResilientIndexer {
   }
 
   private static IndexRequest newIndexRequest(ViewDoc doc) {
-    return new IndexRequest(ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(), ViewIndexDefinition.INDEX_TYPE_VIEW.getType(), doc.uuid())
+    IndexType.IndexMainType mainType = TYPE_VIEW;
+    return new IndexRequest(mainType.getIndex().getName(), mainType.getType())
+      .id(doc.getId())
+      .routing(doc.getRouting().orElse(null))
       .source(doc.getFields());
   }
 
@@ -156,13 +159,13 @@ public class ViewIndexer implements ResilientIndexer {
 
     // Safety check to remove all views that may not have been deleted
     views.removeAll(dbClient.componentDao().selectExistingUuids(dbSession, views));
-    views.forEach(v -> bulkIndexer.addDeletion(INDEX_TYPE_VIEW, v));
+    views.forEach(v -> bulkIndexer.addDeletion(TYPE_VIEW, v));
     return bulkIndexer.stop();
   }
 
   public void delete(DbSession dbSession, Collection<String> viewUuids) {
     List<EsQueueDto> items = viewUuids.stream()
-      .map(l -> EsQueueDto.create(INDEX_TYPE_VIEW.format(), l))
+      .map(l -> EsQueueDto.create(TYPE_VIEW.format(), l))
       .collect(MoreCollectors.toArrayList());
 
     dbClient.esQueueDao().insert(dbSession, items);
@@ -171,6 +174,6 @@ public class ViewIndexer implements ResilientIndexer {
   }
 
   private BulkIndexer newBulkIndexer(Size bulkSize, IndexingListener listener) {
-    return new BulkIndexer(esClient, INDEX_TYPE_VIEW, bulkSize, listener);
+    return new BulkIndexer(esClient, TYPE_VIEW, bulkSize, listener);
   }
 }
index 5a22fd76d9a96d16334dc3370555ea4364701c0c..69a27cb0da267255dc13fe946de65bc8099fc409 100644 (file)
@@ -42,8 +42,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
-import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_TYPE_COMPONENT;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
 
@@ -62,7 +62,7 @@ public class ComponentIndexerTest {
 
   @Test
   public void test_getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(INDEX_TYPE_COMPONENT);
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_COMPONENT);
   }
 
   @Test
@@ -90,7 +90,7 @@ public class ComponentIndexerTest {
     underTest.indexOnStartup(emptySet());
 
     assertThatIndexContainsOnly(project);
-    ComponentDoc doc = es.getDocuments(INDEX_TYPE_COMPONENT, ComponentDoc.class).get(0);
+    ComponentDoc doc = es.getDocuments(TYPE_COMPONENT, ComponentDoc.class).get(0);
     assertThat(doc.getId()).isEqualTo(project.uuid());
     assertThat(doc.getKey()).isEqualTo(project.getDbKey());
     assertThat(doc.getProjectUuid()).isEqualTo(project.projectUuid());
@@ -234,7 +234,7 @@ public class ComponentIndexerTest {
   public void errors_during_indexing_are_recovered() {
     ComponentDto project = db.components().insertPrivateProject();
     ComponentDto file = db.components().insertComponent(newFileDto(project));
-    es.lockWrites(INDEX_TYPE_COMPONENT);
+    es.lockWrites(TYPE_COMPONENT);
 
     IndexingResult result = indexProject(project, PROJECT_CREATION);
     assertThat(result.getTotal()).isEqualTo(2L);
@@ -244,9 +244,9 @@ public class ComponentIndexerTest {
     result = recover();
     assertThat(result.getTotal()).isEqualTo(2L);
     assertThat(result.getFailures()).isEqualTo(2L);
-    assertThat(es.countDocuments(INDEX_TYPE_COMPONENT)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_COMPONENT)).isEqualTo(0);
 
-    es.unlockWrites(INDEX_TYPE_COMPONENT);
+    es.unlockWrites(TYPE_COMPONENT);
 
     result = recover();
     assertThat(result.getTotal()).isEqualTo(2L);
@@ -275,17 +275,17 @@ public class ComponentIndexerTest {
   }
 
   private void assertThatIndexHasSize(int expectedSize) {
-    assertThat(es.countDocuments(INDEX_TYPE_COMPONENT)).isEqualTo(expectedSize);
+    assertThat(es.countDocuments(TYPE_COMPONENT)).isEqualTo(expectedSize);
   }
 
   private void assertThatIndexContainsOnly(ComponentDto... expectedComponents) {
-    assertThat(es.getIds(INDEX_TYPE_COMPONENT)).containsExactlyInAnyOrder(
+    assertThat(es.getIds(TYPE_COMPONENT)).containsExactlyInAnyOrder(
       Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new));
   }
 
   private void assertThatComponentHasName(ComponentDto component, String expectedName) {
     SearchHit[] hits = es.client()
-      .prepareSearch(INDEX_TYPE_COMPONENT)
+      .prepareSearch(TYPE_COMPONENT.getMainType())
       .setQuery(matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), expectedName))
       .get()
       .getHits()
index 7890492b7512bb430a96e52a5d39bd70492a25c4..3a49dfdaf8a0913f2d1f0bf5a0b61de1f668c818 100644 (file)
@@ -34,11 +34,12 @@ import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.db.DbTester;
 import org.sonar.server.es.BulkIndexer.Size;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static java.util.Collections.emptyMap;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.es.FakeIndexDefinition.INDEX;
-import static org.sonar.server.es.FakeIndexDefinition.INDEX_TYPE_FAKE;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.INDEX;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
 
 public class BulkIndexerTest {
 
@@ -53,7 +54,7 @@ public class BulkIndexerTest {
 
   @Test
   public void index_nothing() {
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
     indexer.start();
     indexer.stop();
 
@@ -62,7 +63,7 @@ public class BulkIndexerTest {
 
   @Test
   public void index_documents() {
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
     indexer.start();
     indexer.add(newIndexRequest(42));
     indexer.add(newIndexRequest(78));
@@ -80,7 +81,7 @@ public class BulkIndexerTest {
     // index has one replica
     assertThat(replicas()).isEqualTo(1);
 
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.LARGE);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.LARGE);
     indexer.start();
 
     // replicas are temporarily disabled
@@ -108,12 +109,12 @@ public class BulkIndexerTest {
     for (int i = 0; i < max; i++) {
       docs[i] = FakeIndexDefinition.newDoc(i);
     }
-    es.putDocuments(INDEX_TYPE_FAKE, docs);
+    es.putDocuments(TYPE_FAKE, docs);
     assertThat(count()).isEqualTo(max);
 
-    SearchRequestBuilder req = es.client().prepareSearch(INDEX_TYPE_FAKE)
+    SearchRequestBuilder req = es.client().prepareSearch(TYPE_FAKE)
       .setQuery(QueryBuilders.rangeQuery(FakeIndexDefinition.INT_FIELD).gte(removeFrom));
-    BulkIndexer.delete(es.client(), INDEX_TYPE_FAKE, req);
+    BulkIndexer.delete(es.client(), TYPE_FAKE, req);
 
     assertThat(count()).isEqualTo(removeFrom);
   }
@@ -121,12 +122,12 @@ public class BulkIndexerTest {
   @Test
   public void listener_is_called_on_successful_requests() {
     FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR, listener);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
     indexer.start();
-    indexer.addDeletion(INDEX_TYPE_FAKE, "foo");
+    indexer.addDeletion(TYPE_FAKE, "foo");
     indexer.stop();
     assertThat(listener.calledDocIds)
-      .containsExactlyInAnyOrder(new DocId(INDEX_TYPE_FAKE, "foo"));
+      .containsExactlyInAnyOrder(newDocId(TYPE_FAKE, "foo"));
     assertThat(listener.calledResult.getSuccess()).isEqualTo(1);
     assertThat(listener.calledResult.getTotal()).isEqualTo(1);
   }
@@ -134,13 +135,13 @@ public class BulkIndexerTest {
   @Test
   public void listener_is_called_even_if_deleting_a_doc_that_does_not_exist() {
     FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR, listener);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
     indexer.start();
     indexer.add(newIndexRequestWithDocId("foo"));
     indexer.add(newIndexRequestWithDocId("bar"));
     indexer.stop();
     assertThat(listener.calledDocIds)
-      .containsExactlyInAnyOrder(new DocId(INDEX_TYPE_FAKE, "foo"), new DocId(INDEX_TYPE_FAKE, "bar"));
+      .containsExactlyInAnyOrder(newDocId(TYPE_FAKE, "foo"), newDocId(TYPE_FAKE, "bar"));
     assertThat(listener.calledResult.getSuccess()).isEqualTo(2);
     assertThat(listener.calledResult.getTotal()).isEqualTo(2);
   }
@@ -148,12 +149,12 @@ public class BulkIndexerTest {
   @Test
   public void listener_is_not_called_with_errors() {
     FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR, listener);
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
     indexer.start();
     indexer.add(newIndexRequestWithDocId("foo"));
     indexer.add(new IndexRequest("index_does_not_exist", "index_does_not_exist", "bar").source(emptyMap()));
     indexer.stop();
-    assertThat(listener.calledDocIds).containsExactly(new DocId(INDEX_TYPE_FAKE, "foo"));
+    assertThat(listener.calledDocIds).containsExactly(newDocId(TYPE_FAKE, "foo"));
     assertThat(listener.calledResult.getSuccess()).isEqualTo(1);
     assertThat(listener.calledResult.getTotal()).isEqualTo(2);
   }
@@ -162,10 +163,10 @@ public class BulkIndexerTest {
   public void log_requests_when_TRACE_level_is_enabled() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    BulkIndexer indexer = new BulkIndexer(es.client(), INDEX_TYPE_FAKE, Size.REGULAR, new FakeListener());
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, new FakeListener());
     indexer.start();
     indexer.add(newIndexRequestWithDocId("foo"));
-    indexer.addDeletion(INDEX_TYPE_FAKE, "foo");
+    indexer.addDeletion(TYPE_FAKE, "foo");
     indexer.add(newIndexRequestWithDocId("bar"));
     indexer.stop();
 
@@ -192,7 +193,7 @@ public class BulkIndexerTest {
   }
 
   private long count() {
-    return es.countDocuments("fakes", "fake");
+    return es.countDocuments(IndexType.main(Index.simple("fakes"), "fake"));
   }
 
   private int replicas() {
@@ -202,13 +203,17 @@ public class BulkIndexerTest {
   }
 
   private IndexRequest newIndexRequest(int intField) {
-    return new IndexRequest(INDEX, INDEX_TYPE_FAKE.getType())
+    return new IndexRequest(INDEX, TYPE_FAKE.getType())
       .source(ImmutableMap.of(FakeIndexDefinition.INT_FIELD, intField));
   }
 
   private IndexRequest newIndexRequestWithDocId(String id) {
-    return new IndexRequest(INDEX, INDEX_TYPE_FAKE.getType())
+    return new IndexRequest(INDEX, TYPE_FAKE.getType())
       .id(id)
       .source(ImmutableMap.of(FakeIndexDefinition.INT_FIELD, 42));
   }
+
+  private static DocId newDocId(IndexType.IndexMainType mainType, String id) {
+    return new DocId(TYPE_FAKE.getIndex().getName(), mainType.getType(), id);
+  }
 }
index bb82604b44f770f28a7ab80270a809d7e24298e1..3d3e91b6ae206e8859af1408cb964e3471f61d31 100644 (file)
@@ -21,10 +21,11 @@ package org.sonar.server.es;
 
 import java.util.Map;
 import org.junit.Test;
+import org.sonar.server.es.newindex.DefaultIndexSettings;
 import org.sonar.test.TestUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 
 public class DefaultIndexSettingsTest {
 
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/DocIdTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/DocIdTest.java
new file mode 100644 (file)
index 0000000..eefae0c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es;
+
+import org.junit.Test;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DocIdTest {
+  @Test
+  public void equals_is_based_on_index_type_and_id() {
+    String index = randomAlphabetic(5);
+    String type = randomAlphabetic(6);
+    String id = randomAlphabetic(7);
+    DocId underTest = new DocId(index, type, id);
+
+    assertThat(underTest)
+      .isEqualTo(new DocId(index, type, id))
+      .isNotEqualTo(new DocId(randomAlphabetic(7), type, id))
+      .isNotEqualTo(new DocId(index, type, randomAlphabetic(7)))
+      .isNotEqualTo(new DocId(index, randomAlphabetic(7), id))
+      .isNotEqualTo(new DocId(randomAlphabetic(7), randomAlphabetic(8), id))
+      .isNotEqualTo(new DocId(randomAlphabetic(7), type, randomAlphabetic(8)))
+      .isNotEqualTo(new DocId(index, randomAlphabetic(7), randomAlphabetic(8)))
+      .isNotEqualTo(new DocId(randomAlphabetic(7), randomAlphabetic(8), randomAlphabetic(9)));
+  }
+
+  @Test
+  public void hashcode_is_based_on_index_type_and_id() {
+    String index = randomAlphabetic(5);
+    String type = randomAlphabetic(6);
+    String id = randomAlphabetic(7);
+    DocId underTest = new DocId(index, type, id);
+
+    assertThat(underTest.hashCode())
+      .isEqualTo(new DocId(index, type, id).hashCode())
+      .isNotEqualTo(new DocId(randomAlphabetic(7), type, id).hashCode())
+      .isNotEqualTo(new DocId(index, type, randomAlphabetic(7)).hashCode())
+      .isNotEqualTo(new DocId(index, randomAlphabetic(7), id).hashCode())
+      .isNotEqualTo(new DocId(randomAlphabetic(7), randomAlphabetic(8), id).hashCode())
+      .isNotEqualTo(new DocId(randomAlphabetic(7), type, randomAlphabetic(8)).hashCode())
+      .isNotEqualTo(new DocId(index, randomAlphabetic(7), randomAlphabetic(8)).hashCode())
+      .isNotEqualTo(new DocId(randomAlphabetic(7), randomAlphabetic(8), randomAlphabetic(9)).hashCode());
+  }
+}
index cb78b45a587956a41ed603eeba87f3f5f61c0e98..82938da09bde4c18bfd5bc8f70c182d17a1e62d1 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.es;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 import org.sonar.server.es.request.ProxyClusterHealthRequestBuilder;
 import org.sonar.server.es.request.ProxyClusterStateRequestBuilder;
 import org.sonar.server.es.request.ProxyClusterStatsRequestBuilder;
@@ -44,21 +45,24 @@ public class EsClientTest {
 
   @Test
   public void proxify_requests() {
+    Index fakesIndex = Index.simple("fakes");
+    IndexType.IndexMainType fakeMainType = IndexType.main(fakesIndex, "fake");
+
     EsClient underTest = es.client();
     assertThat(underTest.nativeClient()).isNotNull();
     assertThat(underTest.prepareClusterStats()).isInstanceOf(ProxyClusterStatsRequestBuilder.class);
-    assertThat(underTest.prepareCreate("fakes")).isInstanceOf(ProxyCreateIndexRequestBuilder.class);
-    assertThat(underTest.prepareDelete("fakes", "fake", "my_id")).isInstanceOf(ProxyDeleteRequestBuilder.class);
-    assertThat(underTest.prepareIndicesExist()).isInstanceOf(ProxyIndicesExistsRequestBuilder.class);
-    assertThat(underTest.prepareGet(new IndexType("fakes", "fake"), "1")).isInstanceOf(ProxyGetRequestBuilder.class);
+    assertThat(underTest.prepareCreate(fakesIndex)).isInstanceOf(ProxyCreateIndexRequestBuilder.class);
+    assertThat(underTest.prepareDelete(fakeMainType, "my_id")).isInstanceOf(ProxyDeleteRequestBuilder.class);
+    assertThat(underTest.prepareIndicesExist(fakesIndex)).isInstanceOf(ProxyIndicesExistsRequestBuilder.class);
+    assertThat(underTest.prepareGet(fakeMainType, "1")).isInstanceOf(ProxyGetRequestBuilder.class);
     assertThat(underTest.prepareHealth()).isInstanceOf(ProxyClusterHealthRequestBuilder.class);
     assertThat(underTest.prepareNodesStats()).isInstanceOf(ProxyNodesStatsRequestBuilder.class);
-    assertThat(underTest.preparePutMapping()).isInstanceOf(ProxyPutMappingRequestBuilder.class);
-    assertThat(underTest.prepareRefresh()).isInstanceOf(ProxyRefreshRequestBuilder.class);
-    assertThat(underTest.prepareSearch(new IndexType[0])).isInstanceOf(ProxySearchRequestBuilder.class);
+    assertThat(underTest.preparePutMapping(fakesIndex)).isInstanceOf(ProxyPutMappingRequestBuilder.class);
+    assertThat(underTest.prepareRefresh(fakesIndex)).isInstanceOf(ProxyRefreshRequestBuilder.class);
+    assertThat(underTest.prepareSearch(fakesIndex)).isInstanceOf(ProxySearchRequestBuilder.class);
     assertThat(underTest.prepareSearchScroll("1234")).isInstanceOf(ProxySearchScrollRequestBuilder.class);
     assertThat(underTest.prepareState()).isInstanceOf(ProxyClusterStateRequestBuilder.class);
-    assertThat(underTest.prepareStats()).isInstanceOf(ProxyIndicesStatsRequestBuilder.class);
+    assertThat(underTest.prepareStats(fakesIndex)).isInstanceOf(ProxyIndicesStatsRequestBuilder.class);
 
     underTest.close();
   }
index 0b3b266053de28b25c78ddbadd2a4fa334210c84..a8253881b6da34c1e6cc8ba8a5700a22e60f987c 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.es;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import java.nio.file.Files;
@@ -53,13 +54,19 @@ import org.elasticsearch.discovery.DiscoveryModule;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.env.NodeEnvironment;
 import org.elasticsearch.index.IndexNotFoundException;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.TermQueryBuilder;
 import org.elasticsearch.indices.recovery.RecoverySettings;
+import org.elasticsearch.join.ParentJoinPlugin;
+import org.elasticsearch.node.InternalSettingsPreparer;
 import org.elasticsearch.node.Node;
 import org.elasticsearch.search.SearchHit;
 import org.junit.rules.ExternalResource;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.config.internal.MapSettings;
 import org.sonar.server.component.index.ComponentIndexDefinition;
+import org.sonar.server.es.IndexDefinition.IndexDefinitionContext;
+import org.sonar.server.es.IndexType.IndexRelationType;
+import org.sonar.server.es.newindex.BuiltIndex;
+import org.sonar.server.es.newindex.NewIndex;
 import org.sonar.server.issue.index.IssueIndexDefinition;
 import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 import org.sonar.server.rule.index.RuleIndexDefinition;
@@ -69,7 +76,9 @@ import org.sonar.server.view.index.ViewIndexDefinition;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Lists.newArrayList;
 import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.sonar.server.es.DefaultIndexSettings.REFRESH_IMMEDIATE;
+import static org.sonar.server.es.Index.ALL_INDICES;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.REFRESH_IMMEDIATE;
 
 public class EsTester extends ExternalResource {
 
@@ -102,17 +111,16 @@ public class EsTester extends ExternalResource {
    */
   public static EsTester create() {
     if (!CORE_INDICES_CREATED.get()) {
-      Configuration config = new MapSettings().asConfig();
-      List<IndexDefinition.Index> createdIndices = createIndices(
-        new ComponentIndexDefinition(config),
+      List<BuiltIndex> createdIndices = createIndices(
+        ComponentIndexDefinition.createForTest(),
         IssueIndexDefinition.createForTest(),
-        new ProjectMeasuresIndexDefinition(config),
+        ProjectMeasuresIndexDefinition.createForTest(),
         RuleIndexDefinition.createForTest(),
-        new UserIndexDefinition(config),
-        new ViewIndexDefinition(config));
+        UserIndexDefinition.createForTest(),
+        ViewIndexDefinition.createForTest());
 
       CORE_INDICES_CREATED.set(true);
-      createdIndices.stream().map(IndexDefinition.Index::getName).forEach(CORE_INDICES_NAMES::add);
+      createdIndices.stream().map(t -> t.getMainType().getIndex().getName()).forEach(CORE_INDICES_NAMES::add);
     }
     return new EsTester(false);
   }
@@ -135,26 +143,19 @@ public class EsTester extends ExternalResource {
         .filter(i -> !CORE_INDICES_NAMES.contains(i))
         .forEach(EsTester::deleteIndexIfExists);
     }
-    BulkIndexer.delete(client(), new IndexType("_all", ""), client().prepareSearch("_all").setQuery(matchAllQuery()));
+    BulkIndexer.delete(client(), IndexType.main(ALL_INDICES, "dummy"), client().prepareSearch(ALL_INDICES).setQuery(matchAllQuery()));
   }
 
   public EsClient client() {
     return new EsClient(SHARED_NODE.client());
   }
 
-  public void putDocuments(String index, String type, BaseDoc... docs) {
-    putDocuments(new IndexType(index, type), docs);
-  }
-
   public void putDocuments(IndexType indexType, BaseDoc... docs) {
     try {
       BulkRequestBuilder bulk = SHARED_NODE.client().prepareBulk()
         .setRefreshPolicy(REFRESH_IMMEDIATE);
       for (BaseDoc doc : docs) {
-        bulk.add(new IndexRequest(indexType.getIndex(), indexType.getType(), doc.getId())
-          .parent(doc.getParent())
-          .routing(doc.getRouting())
-          .source(doc.getFields()));
+        bulk.add(doc.toIndexRequest());
       }
       BulkResponse bulkResponse = bulk.get();
       if (bulkResponse.hasFailures()) {
@@ -170,7 +171,8 @@ public class EsTester extends ExternalResource {
       BulkRequestBuilder bulk = SHARED_NODE.client().prepareBulk()
         .setRefreshPolicy(REFRESH_IMMEDIATE);
       for (Map<String, Object> doc : docs) {
-        bulk.add(new IndexRequest(indexType.getIndex(), indexType.getType())
+        IndexType.IndexMainType mainType = indexType.getMainType();
+        bulk.add(new IndexRequest(mainType.getIndex().getName(), mainType.getType())
           .source(doc));
       }
       BulkResponse bulkResponse = bulk.get();
@@ -182,12 +184,16 @@ public class EsTester extends ExternalResource {
     }
   }
 
-  public long countDocuments(String index, String type) {
-    return countDocuments(new IndexType(index, type));
+  public long countDocuments(Index index) {
+    return client().prepareSearch(index)
+      .setQuery(matchAllQuery())
+      .setSize(0).get().getHits().getTotalHits();
   }
 
   public long countDocuments(IndexType indexType) {
-    return client().prepareSearch(indexType).setSize(0).get().getHits().getTotalHits();
+    return client().prepareSearch(indexType.getMainType())
+      .setQuery(getDocumentsQuery(indexType))
+      .setSize(0).get().getHits().getTotalHits();
   }
 
   /**
@@ -206,10 +212,27 @@ public class EsTester extends ExternalResource {
   }
 
   /**
-   * Get all the indexed documents (no paginated results). Results are not sorted.
+   * Get all the indexed documents (no paginated results) in the specified index, whatever their type. Results are not sorted.
+   */
+  public List<SearchHit> getDocuments(Index index) {
+    SearchRequestBuilder req = SHARED_NODE.client()
+      .prepareSearch(index.getName())
+      .setQuery(matchAllQuery());
+    return getDocuments(req);
+  }
+
+  /**
+   * Get all the indexed documents (no paginated results) of the specified type. Results are not sorted.
    */
   public List<SearchHit> getDocuments(IndexType indexType) {
-    SearchRequestBuilder req = SHARED_NODE.client().prepareSearch(indexType.getIndex()).setTypes(indexType.getType()).setQuery(matchAllQuery());
+    IndexType.IndexMainType mainType = indexType.getMainType();
+    SearchRequestBuilder req = SHARED_NODE.client()
+      .prepareSearch(mainType.getIndex().getName())
+      .setQuery(getDocumentsQuery(indexType));
+    return getDocuments(req);
+  }
+
+  private List<SearchHit> getDocuments(SearchRequestBuilder req) {
     EsUtils.optimizeScrollRequest(req);
     req.setScroll(new TimeValue(60000))
       .setSize(100);
@@ -227,6 +250,20 @@ public class EsTester extends ExternalResource {
     return result;
   }
 
+  private QueryBuilder getDocumentsQuery(IndexType indexType) {
+    if (!indexType.getMainType().getIndex().acceptsRelations()) {
+      return matchAllQuery();
+    }
+
+    if (indexType instanceof IndexRelationType) {
+      return new TermQueryBuilder(FIELD_INDEX_TYPE, ((IndexRelationType) indexType).getName());
+    }
+    if (indexType instanceof IndexType.IndexMainType) {
+      return new TermQueryBuilder(FIELD_INDEX_TYPE, ((IndexType.IndexMainType) indexType).getType());
+    }
+    throw new IllegalArgumentException("Unsupported IndexType " + indexType.getClass());
+  }
+
   /**
    * Get a list of a specific field from all indexed documents.
    */
@@ -242,11 +279,11 @@ public class EsTester extends ExternalResource {
   }
 
   public void lockWrites(IndexType index) {
-    setIndexSettings(index.getIndex(), ImmutableMap.of("index.blocks.write", "true"));
+    setIndexSettings(index.getMainType().getIndex().getName(), ImmutableMap.of("index.blocks.write", "true"));
   }
 
   public void unlockWrites(IndexType index) {
-    setIndexSettings(index.getIndex(), ImmutableMap.of("index.blocks.write", "false"));
+    setIndexSettings(index.getMainType().getIndex().getName(), ImmutableMap.of("index.blocks.write", "false"));
   }
 
   private void setIndexSettings(String index, Map<String, Object> settings) {
@@ -266,39 +303,39 @@ public class EsTester extends ExternalResource {
     }
   }
 
-  private static List<IndexDefinition.Index> createIndices(IndexDefinition... definitions) {
-    IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
+  private static List<BuiltIndex> createIndices(IndexDefinition... definitions) {
+    IndexDefinitionContext context = new IndexDefinitionContext();
     Stream.of(definitions).forEach(d -> d.define(context));
 
-    List<IndexDefinition.Index> result = new ArrayList<>();
+    List<BuiltIndex> result = new ArrayList<>();
     for (NewIndex newIndex : context.getIndices().values()) {
-      IndexDefinition.Index index = new IndexDefinition.Index(newIndex);
+      BuiltIndex index = newIndex.build();
 
-      deleteIndexIfExists(index.getName());
+      String indexName = index.getMainType().getIndex().getName();
+      deleteIndexIfExists(indexName);
 
       // create index
       Settings.Builder settings = Settings.builder();
       settings.put(index.getSettings());
       CreateIndexResponse indexResponse = SHARED_NODE.client().admin().indices()
-        .prepareCreate(index.getName())
+        .prepareCreate(indexName)
         .setSettings(settings)
         .get();
       if (!indexResponse.isAcknowledged()) {
-        throw new IllegalStateException("Failed to create index " + index.getName());
+        throw new IllegalStateException("Failed to create index " + indexName);
       }
-      SHARED_NODE.client().admin().cluster().prepareHealth(index.getName()).setWaitForStatus(ClusterHealthStatus.YELLOW).get();
+      SHARED_NODE.client().admin().cluster().prepareHealth(indexName).setWaitForStatus(ClusterHealthStatus.YELLOW).get();
 
       // create types
-      for (Map.Entry<String, IndexDefinition.Type> entry : index.getTypes().entrySet()) {
-        PutMappingResponse mappingResponse = SHARED_NODE.client().admin().indices().preparePutMapping(index.getName())
-          .setType(entry.getKey())
-          .setSource(entry.getValue().getAttributes())
-          .get();
-        if (!mappingResponse.isAcknowledged()) {
-          throw new IllegalStateException("Failed to create type " + entry.getKey());
-        }
+      String typeName = index.getMainType().getType();
+      PutMappingResponse mappingResponse = SHARED_NODE.client().admin().indices().preparePutMapping(indexName)
+        .setType(typeName)
+        .setSource(index.getAttributes())
+        .get();
+      if (!mappingResponse.isAcknowledged()) {
+        throw new IllegalStateException("Failed to create type " + typeName);
       }
-      SHARED_NODE.client().admin().cluster().prepareHealth(index.getName()).setWaitForStatus(ClusterHealthStatus.YELLOW).get();
+      SHARED_NODE.client().admin().cluster().prepareHealth(indexName).setWaitForStatus(ClusterHealthStatus.YELLOW).get();
       result.add(index);
     }
     return result;
@@ -324,10 +361,20 @@ public class EsTester extends ExternalResource {
         .put(NetworkModule.HTTP_ENABLED.getKey(), false)
         .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), "single-node")
         .build();
-      Node node = new Node(settings);
+      Node node = new TesterNode(settings);
       return node.start();
     } catch (Exception e) {
       throw new IllegalStateException("Fail to start embedded Elasticsearch", e);
     }
   }
+
+  private static class TesterNode extends Node {
+    public TesterNode(Settings preparedSettings) {
+      super(
+        InternalSettingsPreparer.prepareEnvironment(preparedSettings, null),
+        ImmutableList.of(
+          // install ParentJoin plugin required to create field of type "join"
+          ParentJoinPlugin.class));
+    }
+  }
 }
index 79d4ef24eeacde486b1e4180d45cdab7d3c907f1..d199ed9323aac4e1ec3fe52e39e2ef5471e37cf8 100644 (file)
 package org.sonar.server.es;
 
 import com.google.common.collect.Maps;
-import java.util.Map;
 
-import static org.sonar.server.es.FakeIndexDefinition.INT_FIELD;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.INT_FIELD;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
 
 public class FakeDoc extends BaseDoc {
-  public FakeDoc(Map<String, Object> fields) {
-    super(fields);
+  public FakeDoc() {
+    super(TYPE_FAKE, Maps.newHashMap());
   }
 
   @Override
@@ -34,20 +34,6 @@ public class FakeDoc extends BaseDoc {
     return null;
   }
 
-  @Override
-  public String getRouting() {
-    return null;
-  }
-
-  @Override
-  public String getParent() {
-    return null;
-  }
-
-  public FakeDoc() {
-    super(Maps.newHashMap());
-  }
-
   public int getInt() {
     return getField(INT_FIELD);
   }
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/FakeIndexDefinition.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/FakeIndexDefinition.java
deleted file mode 100644 (file)
index 5bb78ce..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.es;
-
-import org.elasticsearch.cluster.metadata.IndexMetaData;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
-
-public class FakeIndexDefinition implements IndexDefinition {
-
-  public static final String INDEX = "fakes";
-  public static final String TYPE = "fake";
-  public static final IndexType INDEX_TYPE_FAKE = new IndexType("fakes", "fake");
-  public static final String INT_FIELD = "intField";
-
-  private int replicas = 0;
-
-  public FakeIndexDefinition setReplicas(int replicas) {
-    this.replicas = replicas;
-    return this;
-  }
-
-  @Override
-  public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(INDEX, newBuilder(new MapSettings().asConfig()).build());
-    index.getSettings().put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicas);
-    index.getSettings().put("index.refresh_interval", "-1");
-    NewIndex.NewIndexType type = index.createType(INDEX_TYPE_FAKE.getType());
-    type.createIntegerField(INT_FIELD);
-  }
-
-  public static FakeDoc newDoc(int value) {
-    return new FakeDoc().setInt(value);
-  }
-}
index 83140b5a8737e284a57603d72d8eeba35538167d..ed7adadab3e1d7e19b0a79b0141590967f647a86 100644 (file)
  */
 package org.sonar.server.es;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Locale;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.newindex.SettingsConfiguration;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
+@RunWith(DataProviderRunner.class)
 public class IndexDefinitionContextTest {
-  private NewIndex.SettingsConfiguration emptySettingsConfiguration = newBuilder(new MapSettings().asConfig()).build();
+  private SettingsConfiguration emptySettingsConfiguration = newBuilder(new MapSettings().asConfig()).build();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
 
   @Test
   public void create_indices() {
     IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
 
-    context.create("issues", emptySettingsConfiguration);
-    context.create("measures", emptySettingsConfiguration);
-    assertThat(context.getIndices().keySet()).containsOnly("issues", "measures");
+    context.create(Index.withRelations("issues"), emptySettingsConfiguration);
+    context.create(Index.simple("users"), emptySettingsConfiguration);
+    assertThat(context.getIndices().keySet())
+      .containsOnly("issues", "users");
   }
 
   @Test
-  public void fail_to_create_twice_the_same_index() {
+  @UseDataProvider("paarOfIndicesWithSameName")
+  public void fail_to_create_twice_index_with_given_name(Index index1, Index index2) {
     IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
 
-    context.create("issues", emptySettingsConfiguration);
-    try {
-      context.create("issues", emptySettingsConfiguration);
-      fail();
-    } catch (IllegalArgumentException ok) {
-      assertThat(ok).hasMessage("Index already exists: issues");
-    }
+    context.create(index1, emptySettingsConfiguration);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index already exists: " + index1.getName());
+
+    context.create(index2, emptySettingsConfiguration);
+  }
+
+  @DataProvider
+  public static Object[][] paarOfIndicesWithSameName() {
+    String indexName = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+    return new Object[][] {
+      {Index.simple(indexName), Index.simple(indexName)},
+      {Index.withRelations(indexName), Index.withRelations(indexName)},
+      {Index.simple(indexName), Index.withRelations(indexName)},
+      {Index.withRelations(indexName), Index.simple(indexName)},
+    };
   }
 }
index 4c8fb8f74c8497ce8a932f376e30f86c96bae670..c746a57f812e09f3052a92ab798a66272375ddd4 100644 (file)
  */
 package org.sonar.server.es;
 
-import org.junit.Test;
-
 import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.SettingsConfiguration;
+import org.sonar.server.es.newindex.TestNewIndex;
+import org.sonar.server.es.newindex.TypeMapping;
 
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
 
 public class IndexDefinitionHashTest {
+  private final SettingsConfiguration settingsConfiguration = settingsConfigurationOf(new MapSettings());
+
+  @Test
+  public void hash_changes_if_mainType_is_different() {
+    Index simpleIndex = Index.simple("foo");
+    Index withRelationsIndex = Index.withRelations("foo");
+    IndexMainType mainTypeBar = IndexMainType.main(simpleIndex, "bar");
+    TestNewIndex indexSimpleBar = new TestNewIndex(mainTypeBar, settingsConfiguration);
+    TestNewIndex indexSimpleDonut = new TestNewIndex(IndexMainType.main(simpleIndex, "donut"), settingsConfiguration);
+    TestNewIndex indexWithRelationsBar = new TestNewIndex(IndexMainType.main(withRelationsIndex, "bar"), settingsConfiguration);
+
+    assertThat(hashOf(indexSimpleBar))
+      .isEqualTo(hashOf(new TestNewIndex(mainTypeBar, settingsConfiguration)))
+      .isNotEqualTo(hashOf(indexSimpleDonut))
+      .isNotEqualTo(hashOf(indexWithRelationsBar));
+    assertThat(hashOf(indexSimpleDonut))
+      .isNotEqualTo(hashOf(indexWithRelationsBar));
+  }
+
+  @Test
+  public void hash_changes_if_relations_are_different() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    TestNewIndex indexNoRelation = new TestNewIndex(mainType, settingsConfiguration);
+    TestNewIndex indexOneRelation = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut1");
+    TestNewIndex indexOneOtherRelation = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut2");
+    TestNewIndex indexTwoRelations = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut1")
+      .addRelation("donut2");
+    TestNewIndex indexTwoOtherRelations = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut1")
+      .addRelation("donut3");
+
+    assertThat(hashOf(indexNoRelation))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfiguration)))
+      .isNotEqualTo(hashOf(indexOneRelation))
+      .isNotEqualTo(hashOf(indexOneOtherRelation))
+      .isNotEqualTo(hashOf(indexTwoRelations))
+      .isNotEqualTo(hashOf(indexTwoOtherRelations));
+    assertThat(hashOf(indexOneRelation))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfiguration).addRelation("donut1")))
+      .isNotEqualTo(hashOf(indexOneOtherRelation))
+      .isNotEqualTo(hashOf(indexTwoRelations))
+      .isNotEqualTo(hashOf(indexTwoOtherRelations));
+    assertThat(hashOf(indexTwoRelations))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfiguration)
+        .addRelation("donut1")
+        .addRelation("donut2")))
+      .isNotEqualTo(hashOf(indexOneRelation))
+      .isNotEqualTo(hashOf(indexOneOtherRelation))
+      .isNotEqualTo(hashOf(indexTwoOtherRelations));
+  }
+
+  @Test
+  public void hash_is_the_same_if_only_relations_order_changes() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    TestNewIndex indexTwoRelations = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut1")
+      .addRelation("donut2")
+      .addRelation("donut3");
+    TestNewIndex indexTwoRelationsOtherOrder = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut2")
+      .addRelation("donut1")
+      .addRelation("donut3");
+    TestNewIndex indexTwoRelationsOtherOrder2 = new TestNewIndex(mainType, settingsConfiguration)
+      .addRelation("donut2")
+      .addRelation("donut3")
+      .addRelation("donut1");
+
+    assertThat(hashOf(indexTwoRelations))
+      .isEqualTo(hashOf(indexTwoRelationsOtherOrder))
+      .isEqualTo(hashOf(indexTwoRelationsOtherOrder2));
+  }
+
+  @Test
+  public void hash_changes_if_fields_on_main_type_mapping_are_different() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    TestNewIndex indexNoField = new TestNewIndex(mainType, settingsConfiguration);
+    TestNewIndex indexOneField = new TestNewIndex(mainType, settingsConfiguration);
+    indexOneField.getMainTypeMapping()
+      .createIntegerField("field1");
+    TestNewIndex indexOneFieldAgain = new TestNewIndex(mainType, settingsConfiguration);
+    indexOneFieldAgain.getMainTypeMapping()
+      .createIntegerField("field1");
+    TestNewIndex indexOneOtherField = new TestNewIndex(mainType, settingsConfiguration);
+    indexOneOtherField.getMainTypeMapping()
+      .createIntegerField("field2");
+    TestNewIndex indexTwoFields = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoFields.getMainTypeMapping()
+      .createIntegerField("field1")
+      .createIntegerField("field2");
+    TestNewIndex indexTwoFieldsAgain = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoFieldsAgain.getMainTypeMapping()
+      .createIntegerField("field1")
+      .createIntegerField("field2");
+    TestNewIndex indexTwoOtherFields = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoOtherFields.getMainTypeMapping()
+      .createIntegerField("field1")
+      .createIntegerField("field3");
+
+    assertThat(hashOf(indexNoField))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfiguration)))
+      .isNotEqualTo(hashOf(indexOneField))
+      .isNotEqualTo(hashOf(indexOneOtherField))
+      .isNotEqualTo(hashOf(indexTwoFields))
+      .isNotEqualTo(hashOf(indexTwoOtherFields));
+    assertThat(hashOf(indexOneField))
+      .isEqualTo(hashOf(indexOneFieldAgain))
+      .isNotEqualTo(hashOf(indexOneOtherField))
+      .isNotEqualTo(hashOf(indexTwoFields))
+      .isNotEqualTo(hashOf(indexTwoOtherFields));
+    assertThat(hashOf(indexTwoFields))
+      .isEqualTo(hashOf(indexTwoFieldsAgain))
+      .isNotEqualTo(hashOf(indexOneField))
+      .isNotEqualTo(hashOf(indexOneOtherField))
+      .isNotEqualTo(hashOf(indexTwoOtherFields));
+  }
+
+  @Test
+  public void hash_is_the_same_if_only_fields_order_changes() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    TestNewIndex indexTwoFields = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoFields.getMainTypeMapping()
+      .createBooleanField("donut1")
+      .createBooleanField("donut2")
+      .createBooleanField("donut3");
+    TestNewIndex indexTwoFieldsOtherOrder = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoFieldsOtherOrder.getMainTypeMapping()
+      .createBooleanField("donut2")
+      .createBooleanField("donut1")
+      .createBooleanField("donut3");
+    TestNewIndex indexTwoFieldsOtherOrder2 = new TestNewIndex(mainType, settingsConfiguration);
+    indexTwoFieldsOtherOrder2.getMainTypeMapping()
+      .createBooleanField("donut2")
+      .createBooleanField("donut3")
+      .createBooleanField("donut1");
+
+    assertThat(hashOf(indexTwoFields))
+      .isEqualTo(hashOf(indexTwoFieldsOtherOrder))
+      .isEqualTo(hashOf(indexTwoFieldsOtherOrder2));
+  }
+
+  @Test
+  public void hash_changes_if_field_type_changes() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    String fieldName = "field1";
+
+    computeAndVerifyAllDifferentHashesOnMapping(mainType,
+      (mapping) -> mapping.createBooleanField(fieldName),
+      (mapping) -> mapping.createIntegerField(fieldName),
+      (mapping) -> mapping.createByteField(fieldName),
+      (mapping) -> mapping.createDateTimeField(fieldName),
+      (mapping) -> mapping.createDoubleField(fieldName),
+      (mapping) -> mapping.createLongField(fieldName),
+      (mapping) -> mapping.createShortField(fieldName),
+      (mapping) -> mapping.createUuidPathField(fieldName),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).build(),
+      (mapping) -> mapping.textFieldBuilder(fieldName).build(),
+      (mapping) -> mapping.nestedFieldBuilder(fieldName).addKeywordField("bar").build());
+  }
+
+  @Test
+  public void hash_changes_if_keyword_options_change() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    String fieldName = "field1";
+
+    computeAndVerifyAllDifferentHashesOnMapping(mainType,
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableNorms().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableSearch().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableNorms().disableSearch().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().disableSearch().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSearch().build());
+  }
+
+  @Test
+  public void hash_is_the_same_if_only_order_of_keyword_options_change() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    String fieldName = "field1";
+
+    computeAndVerifyAllSameHashesOnMapping(mainType,
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableNorms().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().disableSortingAndAggregating().build());
+    computeAndVerifyAllSameHashesOnMapping(mainType,
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableSearch().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSearch().disableSortingAndAggregating().build());
+    computeAndVerifyAllSameHashesOnMapping(mainType,
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSearch().disableNorms().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().disableSearch().build());
+    computeAndVerifyAllSameHashesOnMapping(mainType,
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSortingAndAggregating().disableSearch().disableNorms().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSearch().disableNorms().disableSortingAndAggregating().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().disableSearch().disableSortingAndAggregating().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableNorms().disableSortingAndAggregating().disableSearch().build(),
+      (mapping) -> mapping.keywordFieldBuilder(fieldName).disableSearch().disableSortingAndAggregating().disableNorms().build());
+  }
+
+  @Test
+  public void hash_changes_if_textFieldBuilder_options_change() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    String fieldName = "field1";
+
+    computeAndVerifyAllDifferentHashesOnMapping(mainType,
+      (mapping) -> mapping.textFieldBuilder(fieldName).build(),
+      (mapping) -> mapping.textFieldBuilder(fieldName).disableSearch().build(),
+      (mapping) -> mapping.textFieldBuilder(fieldName).disableNorms().build(),
+      (mapping) -> mapping.textFieldBuilder(fieldName).disableNorms().disableSearch().build());
+  }
+
+  @Test
+  public void hash_is_the_same_if_only_order_of_textFieldBuilder_options_change() {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    String fieldName = "field1";
+
+    computeAndVerifyAllSameHashesOnMapping(mainType,
+      (mapping) -> mapping.textFieldBuilder(fieldName).disableSearch().disableNorms().build(),
+      (mapping) -> mapping.textFieldBuilder(fieldName).disableNorms().disableSearch().build());
+  }
+
+  @SafeVarargs
+  private final void computeAndVerifyAllSameHashesOnMapping(IndexMainType mainType, Consumer<TypeMapping>... fieldTypes) {
+    List<Consumer<TypeMapping>> fieldTypes1 = Arrays.asList(fieldTypes);
+    List<TestNewIndex> mainIndices = fieldTypes1.stream()
+      .map(consumer -> {
+        TestNewIndex mainTypeMapping = new TestNewIndex(mainType, settingsConfiguration);
+        consumer.accept(mainTypeMapping.getMainTypeMapping());
+        return mainTypeMapping;
+      })
+      .collect(toList());
+    List<TestNewIndex> relationIndices = fieldTypes1.stream()
+      .map(consumer -> {
+        TestNewIndex relationTypeMapping = new TestNewIndex(mainType, settingsConfiguration);
+        consumer.accept(relationTypeMapping.createRelationMapping("donut"));
+        return relationTypeMapping;
+      })
+      .collect(toList());
+
+    Set<String> mainHashes = mainIndices.stream()
+      .map(IndexDefinitionHashTest::hashOf)
+      .collect(toSet());
+    Set<String> relationHashes = relationIndices.stream()
+      .map(IndexDefinitionHashTest::hashOf)
+      .collect(toSet());
+
+    assertThat(mainHashes)
+      // verify hashing is stable
+      .isEqualTo(mainIndices.stream().map(IndexDefinitionHashTest::hashOf).collect(toSet()))
+      .doesNotContainAnyElementsOf(relationHashes)
+      .hasSize(1);
+    assertThat(relationHashes)
+      // verify hashing is stable
+      .isEqualTo(relationIndices.stream().map(IndexDefinitionHashTest::hashOf).collect(toSet()))
+      .doesNotContainAnyElementsOf(mainHashes)
+      .hasSize(1);
+  }
+
+  @SafeVarargs
+  private final void computeAndVerifyAllDifferentHashesOnMapping(IndexMainType mainType, Consumer<TypeMapping>... fieldTypes) {
+    List<TestNewIndex> mainIndices = Arrays.stream(fieldTypes)
+      .map(consumer -> {
+        TestNewIndex mainTypeMapping = new TestNewIndex(mainType, settingsConfiguration);
+        consumer.accept(mainTypeMapping.getMainTypeMapping());
+        return mainTypeMapping;
+      })
+      .collect(toList());
+    List<TestNewIndex> relationIndices = Arrays.stream(fieldTypes)
+      .map(consumer -> {
+        TestNewIndex relationTypeMapping = new TestNewIndex(mainType, settingsConfiguration);
+        consumer.accept(relationTypeMapping.createRelationMapping("donut"));
+        return relationTypeMapping;
+      })
+      .collect(toList());
+
+    Set<String> mainHashes = mainIndices.stream()
+      .map(IndexDefinitionHashTest::hashOf)
+      .collect(toSet());
+    Set<String> relationHashes = relationIndices.stream()
+      .map(IndexDefinitionHashTest::hashOf)
+      .collect(toSet());
+
+    assertThat(mainHashes)
+      // verify hashing is stable
+      .isEqualTo(mainIndices.stream().map(IndexDefinitionHashTest::hashOf).collect(toSet()))
+      .doesNotContainAnyElementsOf(relationHashes)
+      .hasSize(fieldTypes.length);
+    assertThat(relationHashes)
+      // verify hashing is stable
+      .isEqualTo(relationIndices.stream().map(IndexDefinitionHashTest::hashOf).collect(toSet()))
+      .doesNotContainAnyElementsOf(mainHashes)
+      .hasSize(fieldTypes.length);
+  }
 
   @Test
-  public void of() {
-    IndexDefinition.Index indexV1 = new IndexDefinition.Index(createIndex());
-    String hashV1 = IndexDefinitionHash.of(indexV1);
-    assertThat(hashV1).isNotEmpty();
-    // always the same
-    assertThat(hashV1).isEqualTo(IndexDefinitionHash.of(indexV1));
+  public void hash_changes_if_clustering_is_enabled_or_not() {
+    Index index = Index.simple("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    MapSettings empty = new MapSettings();
+    MapSettings clusterDisabled = new MapSettings().setProperty(CLUSTER_ENABLED.getKey(), false);
+    MapSettings clusterEnabled = new MapSettings().setProperty(CLUSTER_ENABLED.getKey(), true);
 
-    NewIndex newIndexV2 = createIndex();
-    newIndexV2.getTypes().get("fake").createIntegerField("max");
-    String hashV2 = IndexDefinitionHash.of(new IndexDefinition.Index(newIndexV2));
-    assertThat(hashV2).isNotEmpty().isNotEqualTo(hashV1);
+    assertThat(hashOf(new TestNewIndex(mainType, settingsConfigurationOf(empty))))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfigurationOf(empty))))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, settingsConfigurationOf(clusterDisabled))))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, settingsConfigurationOf(clusterEnabled))));
   }
 
-  private NewIndex createIndex() {
-    NewIndex newIndex = new NewIndex("fakes", newBuilder(new MapSettings().asConfig()).build());
-    NewIndex.NewIndexType mapping = newIndex.createType("fake");
-    mapping.setAttribute("list_attr", Arrays.asList("foo", "bar"));
-    mapping.keywordFieldBuilder("key").build();
-    mapping.createDateTimeField("updatedAt");
-    return newIndex;
+  @Test
+  public void hash_changes_if_number_of_shards_changes() {
+    Index index = Index.simple("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    Configuration emptySettings = new MapSettings().asConfig();
+    SettingsConfiguration defaultNbOfShards = SettingsConfiguration.newBuilder(emptySettings)
+      .build();
+    SettingsConfiguration specifiedDefaultNbOfShards = SettingsConfiguration.newBuilder(emptySettings)
+      .setDefaultNbOfShards(5)
+      .build();
+    SettingsConfiguration specifyDefaultNbOfShards = SettingsConfiguration.newBuilder(new MapSettings()
+      .setProperty("sonar.search." + index.getName() + ".shards", 1)
+      .asConfig())
+      .setDefaultNbOfShards(1)
+      .build();
+    SettingsConfiguration specifiedNbOfShards = SettingsConfiguration.newBuilder(new MapSettings()
+      .setProperty("sonar.search." + index.getName() + ".shards", 10)
+      .asConfig())
+      .setDefaultNbOfShards(5)
+      .build();
+
+    assertThat(hashOf(new TestNewIndex(mainType, defaultNbOfShards)))
+      // verify hash is stable
+      .isEqualTo(hashOf(new TestNewIndex(mainType, defaultNbOfShards)))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, specifyDefaultNbOfShards)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, specifiedDefaultNbOfShards)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, specifiedNbOfShards)));
+    assertThat(hashOf(new TestNewIndex(mainType, specifiedDefaultNbOfShards)))
+      // verify hash is stable
+      .isEqualTo(hashOf(new TestNewIndex(mainType, specifiedDefaultNbOfShards)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, specifyDefaultNbOfShards)));
   }
 
+  @Test
+  public void hash_changes_if_refreshInterval_changes() {
+    Index index = Index.simple("foo");
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    Configuration emptySettings = new MapSettings().asConfig();
+    SettingsConfiguration defaultRefreshInterval = SettingsConfiguration.newBuilder(emptySettings)
+      .build();
+    SettingsConfiguration noRefreshInterval = SettingsConfiguration.newBuilder(emptySettings)
+      .setRefreshInterval(-1)
+      .build();
+    SettingsConfiguration refreshInterval30 = SettingsConfiguration.newBuilder(emptySettings)
+      .setRefreshInterval(30)
+      .build();
+    SettingsConfiguration someRefreshInterval = SettingsConfiguration.newBuilder(emptySettings)
+      .setRefreshInterval(56)
+      .build();
+
+    assertThat(hashOf(new TestNewIndex(mainType, defaultRefreshInterval)))
+      // verify hash is stable
+      .isEqualTo(hashOf(new TestNewIndex(mainType, defaultRefreshInterval)))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, refreshInterval30)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, noRefreshInterval)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, someRefreshInterval)));
+    assertThat(hashOf(new TestNewIndex(mainType, noRefreshInterval)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, someRefreshInterval)));
+  }
+
+  private static SettingsConfiguration settingsConfigurationOf(MapSettings settings) {
+    return SettingsConfiguration.newBuilder(settings.asConfig()).build();
+  }
+
+  private static String hashOf(TestNewIndex newIndex) {
+    return IndexDefinitionHash.of(newIndex.build());
+  }
 }
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/IndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/IndexTest.java
new file mode 100644 (file)
index 0000000..65cf708
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Locale;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class IndexTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void simple_index_constructor_fails_with_IAE_if_index_name_is_null_or_empty(String nullOrEmpty) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name can't be null nor empty");
+
+    Index.simple(nullOrEmpty);
+  }
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void withRelations_index_constructor_fails_with_IAE_if_index_name_is_null_or_empty(String nullOrEmpty) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name can't be null nor empty");
+
+    Index.withRelations(nullOrEmpty);
+
+  }
+
+  @DataProvider
+  public static Object[][] nullOrEmpty() {
+    return new Object[][] {
+      {null},
+      {""}
+    };
+  }
+
+  @Test
+  public void simple_index_name_must_not_contain_upper_case_char() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name must be lower-case letters or '_all': Issues");
+
+    Index.simple("Issues");
+  }
+
+  @Test
+  public void withRelations_index_name_must_not_contain_upper_case_char() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name must be lower-case letters or '_all': Issues");
+
+    Index.withRelations("Issues");
+  }
+
+  @Test
+  public void simple_index_name_can_not_contain_underscore_except__all_keyword() {
+    // doesn't fail
+    Index.simple("_all");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name must be lower-case letters or '_all': _");
+
+    Index.simple("_");
+  }
+
+  @Test
+  public void withRelations_index_name_can_not_contain_underscore_except__all_keyword() {
+    // doesn't fail
+    Index.withRelations("_all");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index name must be lower-case letters or '_all': _");
+
+    Index.withRelations("_");
+  }
+
+  @Test
+  public void simple_index_does_not_accept_relations() {
+    Index underTest = Index.simple("foo");
+
+    assertThat(underTest.acceptsRelations()).isFalse();
+  }
+
+  @Test
+  public void withRelations_index_does_not_accept_relations() {
+    Index underTest = Index.withRelations("foo");
+
+    assertThat(underTest.acceptsRelations()).isTrue();
+  }
+
+  @Test
+  public void getName_returns_constructor_parameter() {
+    String indexName = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+
+    assertThat(Index.simple(indexName).getName()).isEqualTo(indexName);
+    assertThat(Index.withRelations(indexName).getName()).isEqualTo(indexName);
+  }
+
+  @Test
+  public void getJoinField_throws_ISE_on_simple_index() {
+    Index underTest = Index.simple("foo");
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Only index accepting relations has a join field");
+
+    underTest.getJoinField();
+  }
+
+  @Test
+  public void getJoinField_returns_name_based_on_index_name() {
+    String indexName = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+    Index underTest = Index.withRelations(indexName);
+
+    assertThat(underTest.getJoinField()).isEqualTo("join_" + indexName);
+  }
+
+  @Test
+  public void equals_is_based_on_name_and_acceptRelations_flag() {
+    assertThat(Index.simple("foo"))
+      .isEqualTo(Index.simple("foo"))
+      .isNotEqualTo(Index.simple("bar"))
+      .isNotEqualTo(Index.withRelations("foo"));
+  }
+
+  @Test
+  public void hashcode_is_based_on_name_and_acceptRelations_flag() {
+    assertThat(Index.simple("foo").hashCode())
+      .isEqualTo(Index.simple("foo").hashCode())
+      .isNotEqualTo(Index.simple("bar").hashCode())
+      .isNotEqualTo(Index.withRelations("foo").hashCode());
+  }
+
+}
index a6df6010ff132a5084f82dd54c8170468bf5261d..dbcf45eaec85d5aa4a1ea323e606778258f3e650 100644 (file)
  * 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.es;
+package org.sonar.server.es;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.IndexType.IndexRelationType;
+import org.sonar.server.es.IndexType.SimpleIndexMainType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+@RunWith(DataProviderRunner.class)
 public class IndexTypeTest {
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
   @Test
-  public void format_and_parse() {
-    IndexType type1 = new IndexType("foo", "bar");
+  public void parseMainType_from_main_type_without_relations() {
+    IndexMainType type1 = IndexType.main(Index.simple("foo"), "bar");
     assertThat(type1.format()).isEqualTo("foo/bar");
 
-    IndexType type2 = IndexType.parse(type1.format());
-    assertThat(type2.equals(type1)).isTrue();
+    SimpleIndexMainType type2 = IndexType.parseMainType(type1.format());
+    assertThat(type2)
+      .extracting(SimpleIndexMainType::getIndex, SimpleIndexMainType::getType)
+      .containsExactly("foo", "bar");
+  }
+
+  @Test
+  public void parseMainType_from_maintype_with_relations() {
+    IndexMainType type1 = IndexType.main(Index.withRelations("foo"), "bar");
+    assertThat(type1.format()).isEqualTo("foo/bar");
+
+    SimpleIndexMainType type2 = IndexType.parseMainType(type1.format());
+    assertThat(type2)
+      .extracting(SimpleIndexMainType::getIndex, SimpleIndexMainType::getType)
+      .containsExactly("foo", "bar");
+  }
+
+  @Test
+  public void parseMainType_from_relationtype() {
+    IndexMainType mainType = IndexType.main(Index.withRelations("foo"), "bar");
+    IndexRelationType type1 = IndexType.relation(mainType, "donut");
+    assertThat(type1.format()).isEqualTo("foo/bar/donut");
+
+    SimpleIndexMainType type2 = IndexType.parseMainType(type1.format());
+    assertThat(type2)
+      .extracting(SimpleIndexMainType::getIndex, SimpleIndexMainType::getType)
+      .containsExactly("foo", "bar");
   }
 
   @Test
@@ -44,20 +77,79 @@ public class IndexTypeTest {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Unsupported IndexType value: foo");
 
-    IndexType.parse("foo");
+    IndexType.parseMainType("foo");
+  }
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void main_fails_with_IAE_if_index_name_is_null_or_empty(String nullOrEmpty) {
+    Index index = Index.simple("foo");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("type name can't be null nor empty");
+
+    IndexType.main(index, nullOrEmpty);
+  }
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void relation_fails_with_IAE_if_index_name_is_null_or_empty(String nullOrEmpty) {
+    Index index = Index.withRelations("foo");
+    IndexMainType mainType = IndexType.main(index, "foobar");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("type name can't be null nor empty");
+
+    IndexType.relation(mainType, nullOrEmpty);
+  }
+
+  @DataProvider
+  public static Object[][] nullOrEmpty() {
+    return new Object[][] {
+      {null},
+      {""}
+    };
+  }
+
+  @Test
+  public void IndexMainType_equals_and_hashCode() {
+    IndexMainType type1 = IndexType.main(Index.simple("foo"), "bar");
+    IndexMainType type1b = IndexType.main(Index.simple("foo"), "bar");
+    IndexMainType type1c = IndexType.main(Index.withRelations("foo"), "bar");
+    IndexMainType type2 = IndexType.main(Index.simple("foo"), "baz");
+
+    assertThat(type1)
+      .isEqualTo(type1)
+      .isEqualTo(type1b)
+      .isNotEqualTo(type1c)
+      .isNotEqualTo(type2);
+
+    assertThat(type1.hashCode()).isEqualTo(type1.hashCode());
+    assertThat(type1.hashCode()).isEqualTo(type1b.hashCode());
+    assertThat(type1.hashCode()).isNotEqualTo(type1c.hashCode());
+    assertThat(type2.hashCode()).isNotEqualTo(type1.hashCode());
   }
 
   @Test
-  public void equals_and_hashCode() {
-    IndexType type1 = new IndexType("foo", "bar");
-    IndexType type1b = new IndexType("foo", "bar");
-    IndexType type2 = new IndexType("foo", "baz");
+  public void IndexRelationType_equals_and_hashCode() {
+    IndexMainType mainType1 = IndexType.main(Index.withRelations("foo"), "bar");
+    IndexMainType mainType2 = IndexType.main(Index.withRelations("foo"), "baz");
+    IndexRelationType type1 = IndexType.relation(mainType1, "donut");
+    IndexRelationType type1b = IndexType.relation(mainType1, "donut");
+    IndexRelationType type2 = IndexType.relation(mainType1, "donuz");
+    IndexRelationType type3 = IndexType.relation(mainType2, "donut");
 
-    assertThat(type1.equals(type1)).isTrue();
-    assertThat(type1.equals(type1b)).isTrue();
-    assertThat(type1.equals(type2)).isFalse();
+    assertThat(type1)
+      .isEqualTo(type1)
+      .isEqualTo(type1b)
+      .isNotEqualTo(type2)
+      .isNotEqualTo(type3);
 
     assertThat(type1.hashCode()).isEqualTo(type1.hashCode());
     assertThat(type1.hashCode()).isEqualTo(type1b.hashCode());
+    assertThat(type2.hashCode()).isNotEqualTo(type1.hashCode());
+    assertThat(type3.hashCode())
+      .isNotEqualTo(type2.hashCode())
+      .isNotEqualTo(type1.hashCode());
   }
 }
index 014727d6df351ac1146999cd6a9676d336f1e31c..17ba47fd6e46d571bcd3f330a104e573a3e439cd 100644 (file)
@@ -24,10 +24,11 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.config.Configuration;
+import org.sonar.server.es.newindex.SettingsConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 public class NewIndexSettingsConfigurationTest {
   @Rule
@@ -45,7 +46,7 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setDefaultNbOfShards_fails_with_IAE_if_argument_is_zero() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("defaultNbOfShards must be >= 1");
@@ -55,7 +56,7 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setDefaultNbOfShards_fails_with_IAE_if_argument_is_less_than_zero() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("defaultNbOfShards must be >= 1");
@@ -65,14 +66,14 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setDefaultNbOfShards_accepts_1() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     assertThat(underTest.setDefaultNbOfShards(1).build().getDefaultNbOfShards()).isEqualTo(1);
   }
 
   @Test
   public void setDefaultNbOfShards_accepts_any_int_greater_than_1() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     int value = 1 + new Random().nextInt(200);
 
@@ -86,7 +87,7 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setRefreshInterval_fails_with_IAE_if_argument_is_zero() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("refreshInterval must be either -1 or strictly positive");
@@ -96,7 +97,7 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setRefreshInterval_fails_with_IAE_if_argument_is_less_than_minus_1() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("refreshInterval must be either -1 or strictly positive");
@@ -106,14 +107,14 @@ public class NewIndexSettingsConfigurationTest {
 
   @Test
   public void setRefreshInterval_accepts_minus_1() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     assertThat(underTest.setRefreshInterval(-1).build().getRefreshInterval()).isEqualTo(-1);
   }
 
   @Test
   public void setRefreshInterval_accepts_any_int_greater_than_1() {
-    NewIndex.SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
+    SettingsConfiguration.Builder underTest = newBuilder(mockConfiguration);
 
     int value = 1 + new Random().nextInt(200);
 
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/NewIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/NewIndexTest.java
deleted file mode 100644 (file)
index fab2c8c..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.es;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import org.elasticsearch.cluster.metadata.IndexMetaData;
-import org.elasticsearch.common.settings.Settings;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-import static org.junit.Assert.fail;
-import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
-import static org.sonar.process.ProcessProperties.Property.SEARCH_REPLICAS;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
-
-public class NewIndexTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private MapSettings settings = new MapSettings();
-  private NewIndex.SettingsConfiguration defaultSettingsConfiguration = newBuilder(settings.asConfig()).build();
-
-  @Test
-  public void getName_returns_constructor_argument() {
-    assertThat(new NewIndex("foo", defaultSettingsConfiguration).getName()).isEqualTo("foo");
-  }
-
-  @Test
-  public void no_types_of_none_are_specified() {
-    assertThat(new NewIndex("foo", defaultSettingsConfiguration).getTypes()).isEmpty();
-  }
-
-  @Test
-  public void verify_default_index_settings_in_standalone() {
-    Settings underTest = new NewIndex("issues", defaultSettingsConfiguration).getSettings().build();
-
-    assertThat(underTest.get("index.number_of_shards")).isNotEmpty();
-    assertThat(underTest.get("index.mapper.dynamic")).isEqualTo("false");
-    assertThat(underTest.get("index.refresh_interval")).isEqualTo("30s");
-    assertThat(underTest.get("index.number_of_shards")).isEqualTo("1");
-    assertThat(underTest.get("index.number_of_replicas")).isEqualTo("0");
-  }
-
-  @Test
-  public void verify_default_index_settings_in_cluster() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    Settings underTest = new NewIndex("issues", defaultSettingsConfiguration).getSettings().build();
-
-    assertThat(underTest.get("index.number_of_shards")).isNotEmpty();
-    assertThat(underTest.get("index.mapper.dynamic")).isEqualTo("false");
-    assertThat(underTest.get("index.refresh_interval")).isEqualTo("30s");
-    assertThat(underTest.get("index.number_of_shards")).isEqualTo("1");
-    assertThat(underTest.get("index.number_of_replicas")).isEqualTo("1");
-  }
-
-  @Test
-  public void index_name_is_lower_case() {
-    try {
-      new NewIndex("Issues", defaultSettingsConfiguration);
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Index name must be lower-case: Issues");
-    }
-  }
-
-  @Test
-  public void define_fields() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("issue");
-    mapping.setAttribute("dynamic", "true");
-    mapping.setProperty("foo_field", ImmutableMap.of("type", "keyword"));
-    mapping.createBooleanField("boolean_field");
-    mapping.createByteField("byte_field");
-    mapping.createDateTimeField("dt_field");
-    mapping.createDoubleField("double_field");
-    mapping.createIntegerField("int_field");
-    mapping.createLongField("long_field");
-    mapping.createShortField("short_field");
-    mapping.createUuidPathField("uuid_path_field");
-
-    mapping = index.getTypes().get("issue");
-    assertThat(mapping).isNotNull();
-    assertThat(mapping.getAttributes().get("dynamic")).isEqualTo("true");
-    assertThat(mapping.getProperty("foo_field")).isInstanceOf(Map.class);
-    assertThat((Map) mapping.getProperty("foo_field")).containsEntry("type", "keyword");
-    assertThat((Map) mapping.getProperty("byte_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("double_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("dt_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("int_field")).containsEntry("type", "integer");
-    assertThat((Map) mapping.getProperty("long_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("short_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("uuid_path_field")).isNotEmpty();
-    assertThat((Map) mapping.getProperty("unknown")).isNull();
-  }
-
-  @Test
-  public void define_string_field() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("issue");
-    mapping.keywordFieldBuilder("basic_field").build();
-    mapping.keywordFieldBuilder("not_searchable_field").disableSearch().build();
-    mapping.keywordFieldBuilder("all_capabilities_field")
-      .addSubFields(
-        DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER,
-        DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER,
-        DefaultIndexSettingsElement.SORTABLE_ANALYZER)
-      .build();
-    mapping.keywordFieldBuilder("dumb_text_storage")
-      .disableSearch()
-      .disableNorms()
-      .disableSortingAndAggregating()
-      .build();
-
-    Map<String, Object> props = (Map) mapping.getProperty("basic_field");
-    assertThat(props.get("type")).isEqualTo("keyword");
-    assertThat(props.get("index")).isEqualTo("true");
-    assertThat(props.get("fields")).isNull();
-
-    props = (Map) mapping.getProperty("not_searchable_field");
-    assertThat(props.get("type")).isEqualTo("keyword");
-    assertThat(props.get("index")).isEqualTo("false");
-    assertThat(props.get("norms")).isEqualTo("true");
-    assertThat(props.get("store")).isEqualTo("false");
-    assertThat(props.get("doc_values")).isEqualTo("true");
-    assertThat(props.get("fields")).isNull();
-
-    props = (Map) mapping.getProperty("all_capabilities_field");
-    assertThat(props.get("type")).isEqualTo("keyword");
-    // no need to test values, it's not the scope of this test
-    assertThat((Map) props.get("fields")).isNotEmpty();
-
-    props = (Map) mapping.getProperty("dumb_text_storage");
-    assertThat(props.get("type")).isEqualTo("keyword");
-    assertThat(props.get("index")).isEqualTo("false");
-    assertThat(props.get("norms")).isEqualTo("false");
-    assertThat(props.get("store")).isEqualTo("false");
-    assertThat(props.get("doc_values")).isEqualTo("false");
-    assertThat(props.get("fields")).isNull();
-  }
-
-  @Test
-  public void define_nested_field() {
-    NewIndex index = new NewIndex("projectmeasures", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("projectmeasures");
-
-    mapping.nestedFieldBuilder("measures")
-      .addKeywordField("key")
-      .addDoubleField("value")
-      .build();
-    Map<String, Object> result = (Map) mapping.getProperty("measures");
-
-    assertThat(result.get("type")).isEqualTo("nested");
-    Map<String, Map<String, Object>> subProperties = (Map) result.get("properties");
-    assertThat(subProperties.get("key").get("type")).isEqualTo("keyword");
-    assertThat(subProperties.get("value").get("type")).isEqualTo("double");
-  }
-
-  @Test
-  public void fail_when_nested_with_no_field() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("At least one sub-field must be declared in nested property 'measures'");
-
-    NewIndex index = new NewIndex("projectmeasures", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("project_measures");
-
-    mapping.nestedFieldBuilder("measures").build();
-  }
-
-  @Test
-  public void use_doc_values_by_default() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("issue");
-    mapping.keywordFieldBuilder("the_doc_value").build();
-
-    Map<String, Object> props = (Map) mapping.getProperty("the_doc_value");
-    assertThat(props.get("type")).isEqualTo("keyword");
-    assertThat(props.get("doc_values")).isEqualTo("true");
-  }
-
-  @Test
-  public void default_shards_and_replicas() {
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("5");
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
-  }
-
-  @Test
-  public void five_shards_and_one_replica_by_default_on_cluster() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("5");
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
-  }
-
-  @Test
-  public void customize_number_of_shards() {
-    settings.setProperty("sonar.search.issues.shards", "3");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("3");
-    // keep default value
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
-  }
-
-  @Test
-  public void default_number_of_replicas_on_standalone_instance_must_be_0() {
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
-  }
-
-  @Test
-  public void default_number_of_replicas_on_non_enabled_cluster_must_be_0() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "false");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
-  }
-
-  @Test
-  public void default_number_of_replicas_on_cluster_instance_must_be_1() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
-  }
-
-  @Test
-  public void when_number_of_replicas_on_cluster_is_specified_to_zero_default_value_must_not_be_used() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    settings.setProperty(SEARCH_REPLICAS.getKey(), "0");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
-  }
-
-  @Test
-  public void index_defined_with_specified_number_of_replicas_when_cluster_enabled() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    settings.setProperty(SEARCH_REPLICAS.getKey(), "3");
-    NewIndex index = new NewIndex("issues", newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
-
-    assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("3");
-  }
-
-  @Test
-  public void fail_when_replica_customization_cant_be_parsed() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
-    settings.setProperty(SEARCH_REPLICAS.getKey(), "ꝱꝲꝳପ");
-    NewIndex.SettingsConfiguration settingsConfiguration = newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build();
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("The property 'sonar.search.replicas' is not an int value: For input string: \"ꝱꝲꝳପ\"");
-
-    new NewIndex("issues", settingsConfiguration);
-  }
-
-  @Test
-  public void in_standalone_searchReplicas_is_not_overridable() {
-    settings.setProperty(SEARCH_REPLICAS.getKey(), "5");
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-
-    assertThat(index.getSettings().get("index.number_of_replicas")).isEqualTo("0");
-  }
-
-  @Test
-  public void index_with_source() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("issue");
-    mapping.setEnableSource(true);
-
-    mapping = index.getTypes().get("issue");
-    assertThat(mapping).isNotNull();
-    assertThat(getAttributeAsMap(mapping, "_source")).containsExactly(entry("enabled", true));
-  }
-
-  @Test
-  public void index_without_source() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    NewIndex.NewIndexType mapping = index.createType("issue");
-    mapping.setEnableSource(false);
-
-    mapping = index.getTypes().get("issue");
-    assertThat(mapping).isNotNull();
-    assertThat(getAttributeAsMap(mapping, "_source")).containsExactly(entry("enabled", false));
-  }
-
-  @Test
-  public void index_requires_project_authorization() {
-    NewIndex index = new NewIndex("issues", defaultSettingsConfiguration);
-    index.createType("issue")
-      // creates a second type "authorization" and configures _parent and _routing fields
-      .requireProjectAuthorization();
-
-    // issue type
-    NewIndex.NewIndexType issueType = index.getTypes().get("issue");
-    assertThat(getAttributeAsMap(issueType, "_parent")).containsExactly(entry("type", "authorization"));
-    assertThat(getAttributeAsMap(issueType, "_routing")).containsExactly(entry("required", true));
-
-    // authorization type
-    NewIndex.NewIndexType authorizationType = index.getTypes().get("authorization");
-    assertThat(getAttributeAsMap(authorizationType, "_parent")).isNull();
-    assertThat(getAttributeAsMap(authorizationType, "_routing")).containsExactly(entry("required", true));
-  }
-
-  private static Map<String, Object> getAttributeAsMap(NewIndex.NewIndexType type, String attributeKey) {
-    return (Map<String, Object>) type.getAttributes().get(attributeKey);
-  }
-}
index 92e8f8c1f6868fac04457fb5fde09559696ec6bb..8bceb9261e4c099c5feddafa5407d055748f383c 100644 (file)
@@ -34,7 +34,7 @@ import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 
 public class OneToManyResilientIndexingListenerTest {
 
@@ -47,16 +47,16 @@ public class OneToManyResilientIndexingListenerTest {
 
   @Test
   public void ES_QUEUE_rows_are_deleted_when_all_docs_are_successfully_indexed() {
-    EsQueueDto item1 = insertInQueue(INDEX_TYPE_ISSUE, "P1");
-    EsQueueDto item2 = insertInQueue(INDEX_TYPE_ISSUE, "P2");
-    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.INDEX_TYPE_COMPONENT, "P1");
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
+    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
     db.commit();
 
     // does not contain outOfScopeItem
     IndexingListener underTest = newListener(asList(item1, item2));
 
-    DocId issue1 = newDocId(INDEX_TYPE_ISSUE, "I1");
-    DocId issue2 = newDocId(INDEX_TYPE_ISSUE, "I2");
+    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
+    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
     underTest.onSuccess(asList(issue1, issue2));
     assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
 
@@ -71,16 +71,16 @@ public class OneToManyResilientIndexingListenerTest {
 
   @Test
   public void ES_QUEUE_rows_are_not_deleted_on_partial_error() {
-    EsQueueDto item1 = insertInQueue(INDEX_TYPE_ISSUE, "P1");
-    EsQueueDto item2 = insertInQueue(INDEX_TYPE_ISSUE, "P2");
-    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.INDEX_TYPE_COMPONENT, "P1");
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
+    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
     db.commit();
 
     // does not contain outOfScopeItem
     IndexingListener underTest = newListener(asList(item1, item2));
 
-    DocId issue1 = newDocId(INDEX_TYPE_ISSUE, "I1");
-    DocId issue2 = newDocId(INDEX_TYPE_ISSUE, "I2");
+    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
+    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
     underTest.onSuccess(asList(issue1, issue2));
     assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
 
@@ -93,8 +93,9 @@ public class OneToManyResilientIndexingListenerTest {
     assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
   }
 
-  private static DocId newDocId(IndexType indexType, String id) {
-    return new DocId(indexType, id);
+  private static DocId newDocId(IndexType.IndexRelationType indexType, String id) {
+    IndexType.IndexMainType mainType = indexType.getMainType();
+    return new DocId(mainType.getIndex().getName(), mainType.getType(), id);
   }
 
   private IndexingListener newListener(Collection<EsQueueDto> items) {
index ce5ecdb6df118046d707c5abbc3ff02e8f78a831..c46a0ad373248c8dbf80322fca4e5d3175bcaea5 100644 (file)
@@ -34,7 +34,7 @@ import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 
 public class OneToOneResilientIndexingListenerTest {
 
@@ -47,9 +47,9 @@ public class OneToOneResilientIndexingListenerTest {
 
   @Test
   public void onSuccess_deletes_rows_from_ES_QUEUE_table() {
-    EsQueueDto item1 = insertInQueue(INDEX_TYPE_ISSUE, "foo");
-    EsQueueDto item2 = insertInQueue(INDEX_TYPE_ISSUE, "bar");
-    EsQueueDto item3 = insertInQueue(INDEX_TYPE_ISSUE, "baz");
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "bar");
+    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "baz");
     db.commit();
 
     IndexingListener underTest = newListener(asList(item1, item2, item3));
@@ -76,10 +76,10 @@ public class OneToOneResilientIndexingListenerTest {
    */
   @Test
   public void onSuccess_deletes_all_the_rows_with_same_doc_id() {
-    EsQueueDto item1 = insertInQueue(INDEX_TYPE_ISSUE, "foo");
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
     // same id as item1
-    EsQueueDto item2 = insertInQueue(INDEX_TYPE_ISSUE, item1.getDocId());
-    EsQueueDto item3 = insertInQueue(INDEX_TYPE_ISSUE, "bar");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, item1.getDocId());
+    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "bar");
     db.commit();
 
     IndexingListener underTest = newListener(asList(item1, item2, item3));
@@ -89,7 +89,8 @@ public class OneToOneResilientIndexingListenerTest {
   }
 
   private static DocId toDocId(EsQueueDto dto) {
-    return new DocId(IndexType.parse(dto.getDocType()), dto.getDocId());
+    IndexType.SimpleIndexMainType mainType = IndexType.parseMainType(dto.getDocType());
+    return new DocId(mainType.getIndex(), mainType.getType(), dto.getDocId());
   }
 
   private IndexingListener newListener(Collection<EsQueueDto> items) {
index f0c5b4d796b73592fc4e6f226d7b3bccd3597422..a9ed154f9fcf2c23bb5776668f6aec47beaac5ea 100644 (file)
  */
 package org.sonar.server.es.metadata;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Locale;
+import java.util.Random;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 
+@RunWith(DataProviderRunner.class)
 public class MetadataIndexTest {
 
   @Rule
   public EsTester es = EsTester.createCustom(new MetadataIndexDefinitionBridge(), new FakeIndexDefinition());
   private final MetadataIndex underTest = new MetadataIndex(es.client());
-  private final String index = randomAlphanumeric(20);
+  private final String indexName = randomAlphabetic(20).toLowerCase(Locale.ENGLISH);
+  private final Index index = new Random().nextBoolean() ? Index.simple(indexName) : Index.withRelations(indexName);
 
   @Test
-  public void type_should_be_not_initialized_by_default() {
-    IndexType indexType = new IndexType("examples", "example");
+  @UseDataProvider("mainOrRelationType")
+  public void type_should_be_not_initialized_by_default(IndexType indexType) {
     assertThat(underTest.getInitialized(indexType)).isFalse();
   }
 
   @Test
-  public void type_should_be_initialized_after_explicitly_set_to_initialized() {
-    IndexType indexType = new IndexType("examples", "example");
+  @UseDataProvider("mainOrRelationType")
+  public void type_should_be_initialized_after_explicitly_set_to_initialized(IndexType indexType) {
+
     underTest.setInitialized(indexType, true);
     assertThat(underTest.getInitialized(indexType)).isTrue();
   }
 
+  @DataProvider
+  public static Object[][] mainOrRelationType() {
+    IndexMainType mainType = IndexType.main(Index.withRelations("examples"), "example");
+    return new Object[][] {
+      {mainType},
+      {IndexType.relation(mainType, "doo")}
+    };
+  }
+
   @Test
   public void hash_should_be_empty_by_default() {
     assertThat(underTest.getHash(index)).isEmpty();
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FakeIndexDefinition.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FakeIndexDefinition.java
new file mode 100644 (file)
index 0000000..d6dfd76
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.FakeDoc;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexDefinition;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+
+public class FakeIndexDefinition implements IndexDefinition {
+
+  public static final String INDEX = "fakes";
+  public static final String TYPE = "fake";
+  public static final Index DESCRIPTOR = Index.simple(INDEX);
+  public static final IndexMainType TYPE_FAKE = IndexType.main(DESCRIPTOR, TYPE);
+  public static final String INT_FIELD = "intField";
+
+  private int replicas = 0;
+
+  public FakeIndexDefinition setReplicas(int replicas) {
+    this.replicas = replicas;
+    return this;
+  }
+
+  @Override
+  public void define(IndexDefinitionContext context) {
+    NewIndex index = context.create(DESCRIPTOR, newBuilder(new MapSettings().asConfig()).build());
+    index.getSettings().put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicas);
+    index.getSettings().put("index.refresh_interval", "-1");
+    index.createTypeMapping(TYPE_FAKE)
+      .createIntegerField(INT_FIELD);
+  }
+
+  public static FakeDoc newDoc(int value) {
+    return new FakeDoc().setInt(value);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FieldAwareTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/FieldAwareTest.java
new file mode 100644 (file)
index 0000000..99af814
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import java.util.Random;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.fail;
+
+public class FieldAwareTest {
+
+  @Test
+  public void indexType_is_a_reserved_field_name_whatever_the_case() {
+    Stream<BiConsumer<TestFieldAware, String>> fieldSetters = Stream.of(
+      (testFieldAware, fieldName) -> testFieldAware.createBooleanField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.createByteField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.createDateTimeField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.createDoubleField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.createIntegerField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.createLongField(fieldName),
+      (testFieldAware, fieldName) -> testFieldAware.keywordFieldBuilder(fieldName).build(),
+      (testFieldAware, fieldName) -> testFieldAware.textFieldBuilder(fieldName).build(),
+      (testFieldAware, fieldName) -> testFieldAware.nestedFieldBuilder(fieldName).addKeywordField("foo").build()
+    );
+
+    fieldSetters.forEach(c -> {
+      TestFieldAware underTest = new TestFieldAware();
+      // should not fail for other field name
+      c.accept(underTest, randomAlphabetic(1 + new Random().nextInt(10)));
+      // fails whatever the case
+      Stream.of("indexType", "indextype", "InDexType", "INDEXTYPE")
+        .forEach(illegalFieldName -> {
+          try {
+            c.accept(underTest, illegalFieldName);
+            fail("should have thrown a IllegalArgumentException");
+          } catch (IllegalArgumentException e) {
+            assertThat(e).hasMessage("indexType is a reserved field name");
+          }
+        });
+    });
+  }
+
+  private static class TestFieldAware extends FieldAware<TestFieldAware> {
+    private String fieldName;
+    private Object attributes;
+
+    @Override
+    TestFieldAware setFieldImpl(String fieldName, Object attributes) {
+      this.fieldName = fieldName;
+      this.attributes = attributes;
+      return this;
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewAuthorizedIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewAuthorizedIndexTest.java
new file mode 100644 (file)
index 0000000..4628a01
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Locale;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+
+public class NewAuthorizedIndexTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private String someIndexName = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+  private Index someIndex = Index.withRelations(someIndexName);
+  private MapSettings settings = new MapSettings();
+  private SettingsConfiguration defaultSettingsConfiguration = newBuilder(settings.asConfig()).build();
+
+  @Test
+  public void constructor_fails_with_IAE_if_index_does_not_support_relations() {
+    Index simpleIndex = Index.simple(someIndexName);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Index must accept relations");
+
+    new NewAuthorizedIndex(simpleIndex, defaultSettingsConfiguration);
+  }
+
+  @Test
+  public void getMainType_returns_main_type_of_authorization_for_index_of_constructor() {
+    NewAuthorizedIndex underTest = new NewAuthorizedIndex(someIndex, defaultSettingsConfiguration);
+
+    assertThat(underTest.getMainType()).isEqualTo(IndexType.main(someIndex, "auth"));
+  }
+
+  @Test
+  public void build_fails_if_no_relation_mapping_has_been_created() {
+    NewAuthorizedIndex underTest = new NewAuthorizedIndex(someIndex, defaultSettingsConfiguration);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("At least one relation mapping must be defined");
+
+    underTest.build();
+  }
+
+  @Test
+  public void build_enforces_routing() {
+    NewAuthorizedIndex underTest = new NewAuthorizedIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.relation(underTest.getMainType(), "donut"));
+
+    BuiltIndex<NewAuthorizedIndex> builtIndex = underTest.build();
+
+    assertThat(getAttributeAsMap(builtIndex, "_routing"))
+      .containsOnly(entry("required", true));
+  }
+
+  @Test
+  public void build_defines_type_field() {
+    NewAuthorizedIndex underTest = new NewAuthorizedIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.relation(underTest.getMainType(), "donut"));
+
+    BuiltIndex<NewAuthorizedIndex> builtIndex = underTest.build();
+
+    Map<String, Object> properties = getProperties(builtIndex);
+    assertThat(getFieldAsMap(properties, "indexType"))
+      .isEqualTo(ImmutableMap.of(
+        "type", "keyword",
+        "norms", false,
+        "store", false,
+        "doc_values", false));
+  }
+
+  @Test
+  public void constructor_creates_mapping_for_authorization_type() {
+    NewAuthorizedIndex underTest = new NewAuthorizedIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.relation(underTest.getMainType(), "donut"));
+
+    BuiltIndex<NewAuthorizedIndex> builtIndex = underTest.build();
+
+    Map<String, Object> properties = getProperties(builtIndex);
+    assertThat(getFieldAsMap(properties, "auth_groupIds"))
+      .containsOnly(entry("type", "long"));
+    assertThat(getFieldAsMap(properties, "auth_userIds"))
+      .containsOnly(entry("type", "long"));
+    assertThat(getFieldAsMap(properties, "auth_allowAnyone"))
+      .containsOnly(entry("type", "boolean"));
+  }
+
+  private static Map<String, Object> getProperties(BuiltIndex<?> index) {
+    return getAttributeAsMap(index, "properties");
+  }
+
+  @SuppressWarnings("unchecked")
+  private static Map<String, Object> getAttributeAsMap(BuiltIndex<?> index, String attributeKey) {
+    return (Map<String, Object>) index.getAttributes().get(attributeKey);
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<String, Object> getFieldAsMap(Map<String, Object> properties, String fieldName) {
+    return (Map<String, Object>) properties.get(fieldName);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewIndexTest.java
new file mode 100644 (file)
index 0000000..e11262c
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Map;
+import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.common.settings.Settings;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.Property.SEARCH_REPLICAS;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+
+@RunWith(DataProviderRunner.class)
+public class NewIndexTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private static final String someIndexName = randomAlphabetic(5).toLowerCase();
+  private MapSettings settings = new MapSettings();
+  private SettingsConfiguration defaultSettingsConfiguration = newBuilder(settings.asConfig()).build();
+
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void getRelations_returns_empty_if_no_relation_added(Index index) {
+    NewIndex<?> newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration);
+
+    assertThat(newIndex.getRelations()).isEmpty();
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void verify_default_index_settings_in_standalone(Index index) {
+    Settings underTest = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration)
+      .getSettings().build();
+
+    assertThat(underTest.get("index.number_of_shards")).isNotEmpty();
+    assertThat(underTest.get("index.mapper.dynamic")).isEqualTo("false");
+    assertThat(underTest.get("index.refresh_interval")).isEqualTo("30s");
+    assertThat(underTest.get("index.number_of_shards")).isEqualTo("1");
+    assertThat(underTest.get("index.number_of_replicas")).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void verify_default_index_settings_in_cluster(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    Settings underTest = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration).getSettings().build();
+
+    assertThat(underTest.get("index.number_of_shards")).isNotEmpty();
+    assertThat(underTest.get("index.mapper.dynamic")).isEqualTo("false");
+    assertThat(underTest.get("index.refresh_interval")).isEqualTo("30s");
+    assertThat(underTest.get("index.number_of_shards")).isEqualTo("1");
+    assertThat(underTest.get("index.number_of_replicas")).isEqualTo("1");
+  }
+
+  @Test
+  @UseDataProvider("indexAndTypeMappings")
+  public void define_fields(NewIndex<?> newIndex, TypeMapping typeMapping) {
+    typeMapping.setField("foo_field", ImmutableMap.of("type", "keyword"));
+    typeMapping.createBooleanField("boolean_field");
+    typeMapping.createByteField("byte_field");
+    typeMapping.createDateTimeField("dt_field");
+    typeMapping.createDoubleField("double_field");
+    typeMapping.createIntegerField("int_field");
+    typeMapping.createLongField("long_field");
+    typeMapping.createShortField("short_field");
+    typeMapping.createUuidPathField("uuid_path_field");
+
+    assertThat(newIndex.getProperty("foo_field")).isInstanceOf(Map.class);
+    assertThat((Map) newIndex.getProperty("foo_field")).containsEntry("type", "keyword");
+    assertThat((Map) newIndex.getProperty("byte_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("double_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("dt_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("int_field")).containsEntry("type", "integer");
+    assertThat((Map) newIndex.getProperty("long_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("short_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("uuid_path_field")).isNotEmpty();
+    assertThat((Map) newIndex.getProperty("unknown")).isNull();
+  }
+
+  @Test
+  @UseDataProvider("indexAndTypeMappings")
+  public void define_string_field(NewIndex<?> newIndex, TypeMapping typeMapping) {
+    typeMapping.keywordFieldBuilder("basic_field").build();
+    typeMapping.keywordFieldBuilder("not_searchable_field").disableSearch().build();
+    typeMapping.keywordFieldBuilder("all_capabilities_field")
+      .addSubFields(
+        DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER,
+        DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER,
+        DefaultIndexSettingsElement.SORTABLE_ANALYZER)
+      .build();
+    typeMapping.keywordFieldBuilder("dumb_text_storage")
+      .disableSearch()
+      .disableNorms()
+      .disableSortingAndAggregating()
+      .build();
+
+    Map<String, Object> props = (Map) newIndex.getProperty("basic_field");
+    assertThat(props.get("type")).isEqualTo("keyword");
+    assertThat(props.get("index")).isEqualTo("true");
+    assertThat(props.get("fields")).isNull();
+
+    props = (Map) newIndex.getProperty("not_searchable_field");
+    assertThat(props.get("type")).isEqualTo("keyword");
+    assertThat(props.get("index")).isEqualTo("false");
+    assertThat(props.get("norms")).isEqualTo("true");
+    assertThat(props.get("store")).isEqualTo("false");
+    assertThat(props.get("doc_values")).isEqualTo("true");
+    assertThat(props.get("fields")).isNull();
+
+    props = (Map) newIndex.getProperty("all_capabilities_field");
+    assertThat(props.get("type")).isEqualTo("keyword");
+    // no need to test values, it's not the scope of this test
+    assertThat((Map) props.get("fields")).isNotEmpty();
+
+    props = (Map) newIndex.getProperty("dumb_text_storage");
+    assertThat(props.get("type")).isEqualTo("keyword");
+    assertThat(props.get("index")).isEqualTo("false");
+    assertThat(props.get("norms")).isEqualTo("false");
+    assertThat(props.get("store")).isEqualTo("false");
+    assertThat(props.get("doc_values")).isEqualTo("false");
+    assertThat(props.get("fields")).isNull();
+  }
+
+  @Test
+  @UseDataProvider("indexAndTypeMappings")
+  public void define_nested_field(NewIndex<?> newIndex, TypeMapping typeMapping) {
+    typeMapping.nestedFieldBuilder("measures")
+      .addKeywordField("key")
+      .addDoubleField("value")
+      .build();
+    Map<String, Object> result = (Map) newIndex.getProperty("measures");
+
+    assertThat(result.get("type")).isEqualTo("nested");
+    Map<String, Map<String, Object>> subProperties = (Map) result.get("properties");
+    assertThat(subProperties.get("key").get("type")).isEqualTo("keyword");
+    assertThat(subProperties.get("value").get("type")).isEqualTo("double");
+  }
+
+  @Test
+  @UseDataProvider("indexAndTypeMappings")
+  public void fail_when_nested_with_no_field(NewIndex<?> newIndex, TypeMapping typeMapping) {
+    NestedFieldBuilder<TypeMapping> nestedFieldBuilder = typeMapping.nestedFieldBuilder("measures");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At least one sub-field must be declared in nested property 'measures'");
+
+    nestedFieldBuilder.build();
+  }
+
+  @Test
+  @UseDataProvider("indexAndTypeMappings")
+  public void use_doc_values_by_default(NewIndex<?> newIndex, TypeMapping typeMapping) {
+    typeMapping.keywordFieldBuilder("the_doc_value").build();
+
+    Map<String, Object> props = (Map) newIndex.getProperty("the_doc_value");
+    assertThat(props.get("type")).isEqualTo("keyword");
+    assertThat(props.get("doc_values")).isEqualTo("true");
+  }
+
+  @DataProvider
+  public static Object[][] indexAndTypeMappings() {
+    String indexName = randomAlphabetic(5).toLowerCase();
+    MapSettings settings = new MapSettings();
+    SettingsConfiguration defaultSettingsConfiguration = newBuilder(settings.asConfig()).build();
+    Index index = Index.withRelations(indexName);
+    IndexMainType mainType = IndexType.main(index, "foo");
+    SimplestNewIndex newIndex = new SimplestNewIndex(mainType, defaultSettingsConfiguration);
+    TypeMapping mainMapping = newIndex.createTypeMapping(mainType);
+    TypeMapping relationMapping = newIndex.createTypeMapping(IndexType.relation(mainType, "bar"));
+    return new Object[][] {
+      {newIndex, mainMapping},
+      {newIndex, relationMapping},
+    };
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void default_shards_and_replicas(Index index) {
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("5");
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void five_shards_and_one_replica_by_default_on_cluster(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("5");
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void customize_number_of_shards(Index index) {
+    settings.setProperty("sonar.search." + index.getName() + ".shards", "3");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSetting(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("3");
+    // keep default value
+    assertThat(newIndex.getSetting(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void default_number_of_replicas_on_standalone_instance_must_be_0(Index index) {
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void default_number_of_replicas_on_non_enabled_cluster_must_be_0(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "false");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void default_number_of_replicas_on_cluster_instance_must_be_1(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void when_number_of_replicas_on_cluster_is_specified_to_zero_default_value_must_not_be_used(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    settings.setProperty(SEARCH_REPLICAS.getKey(), "0");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void index_defined_with_specified_number_of_replicas_when_cluster_enabled(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    settings.setProperty(SEARCH_REPLICAS.getKey(), "3");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build());
+
+    assertThat(newIndex.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("3");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void fail_when_replica_customization_cant_be_parsed(Index index) {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), "true");
+    settings.setProperty(SEARCH_REPLICAS.getKey(), "ꝱꝲꝳପ");
+    SettingsConfiguration settingsConfiguration = newBuilder(settings.asConfig()).setDefaultNbOfShards(5).build();
+    IndexMainType mainType = IndexType.main(index, "foo");
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("The property 'sonar.search.replicas' is not an int value: For input string: \"ꝱꝲꝳପ\"");
+
+    new SimplestNewIndex(mainType, settingsConfiguration);
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void in_standalone_searchReplicas_is_not_overridable(Index index) {
+    settings.setProperty(SEARCH_REPLICAS.getKey(), "5");
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration);
+
+    assertThat(newIndex.getSettings().get("index.number_of_replicas")).isEqualTo("0");
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void index_with_source(Index index) {
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration);
+    newIndex.setEnableSource(true);
+
+    assertThat(newIndex).isNotNull();
+    assertThat(getAttributeAsMap(newIndex, "_source")).containsExactly(entry("enabled", true));
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void index_without_source(Index index) {
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration);
+    newIndex.setEnableSource(false);
+
+    assertThat(getAttributeAsMap(newIndex, "_source")).containsExactly(entry("enabled", false));
+  }
+
+  @Test
+  public void createTypeMapping_with_IndexRelationType_fails_with_ISE_if_index_does_not_allow_relations() {
+    IndexType.IndexRelationType indexRelationType = IndexType.relation(IndexType.main(Index.withRelations(someIndexName), "bar"), "bar");
+
+    Index index = Index.simple(someIndexName);
+    IndexMainType mainType = IndexType.main(index, "foo");
+    NewIndex underTest = new NewIndex(index, defaultSettingsConfiguration) {
+      @Override
+      public IndexMainType getMainType() {
+        return mainType;
+      }
+
+      @Override
+      public BuiltIndex build() {
+        throw new UnsupportedOperationException("build not implemented");
+      }
+    };
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Index is not configured to accept relations. Update IndexDefinition.Descriptor instance for this index");
+
+    underTest.createTypeMapping(indexRelationType);
+  }
+
+  @DataProvider
+  public static Object[][] indexWithAndWithoutRelations() {
+    return new Object[][] {
+      {Index.simple(someIndexName)},
+      {Index.withRelations(someIndexName)}
+    };
+  }
+
+  private static Map<String, Object> getAttributeAsMap(NewIndex newIndex, String attributeKey) {
+    return (Map<String, Object>) newIndex.getAttributes().get(attributeKey);
+  }
+
+  private static final class SimplestNewIndex extends NewIndex<SimplestNewIndex> {
+    private final IndexMainType mainType;
+
+    public SimplestNewIndex(IndexMainType mainType, SettingsConfiguration settingsConfiguration) {
+      super(mainType.getIndex(), settingsConfiguration);
+      this.mainType = mainType;
+    }
+
+    @Override
+    public IndexMainType getMainType() {
+      return mainType;
+    }
+
+    @Override
+    public BuiltIndex<SimplestNewIndex> build() {
+      return new BuiltIndex<>(this);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewRegularIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewRegularIndexTest.java
new file mode 100644 (file)
index 0000000..9289430
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Locale;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.NORMS;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.STORE;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.TYPE;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
+
+@RunWith(DataProviderRunner.class)
+public class NewRegularIndexTest {
+  private static final String SOME_INDEX_NAME = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private MapSettings settings = new MapSettings();
+  private SettingsConfiguration defaultSettingsConfiguration = newBuilder(settings.asConfig()).build();
+
+  @Test
+  @UseDataProvider("indexes")
+  public void getMainType_fails_with_ISE_if_createTypeMapping_with_IndexMainType_has_not_been_called(Index index) {
+    NewRegularIndex newIndex = new NewRegularIndex(index, defaultSettingsConfiguration);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Main type has not been defined");
+
+    newIndex.getMainType();
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void createTypeMapping_with_IndexMainType_fails_with_ISE_if_called_twice(Index index) {
+    NewRegularIndex underTest = new NewRegularIndex(index, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(index, "foo"));
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Main type can only be defined once");
+
+    underTest.createTypeMapping(IndexType.main(index, "foo"));
+  }
+
+  @Test
+  public void createTypeMapping_with_IndexRelationType_fails_with_ISE_if_called_before_createType_with_IndexMainType() {
+    Index index = Index.withRelations(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(index, defaultSettingsConfiguration);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Mapping for main type must be created first");
+
+    underTest.createTypeMapping(IndexType.relation(IndexType.main(index, "foo"), "bar"));
+  }
+
+  @Test
+  public void createTypeMapping_with_IndexRelationType_fails_with_IAE_if_mainType_does_not_match_defined_one() {
+    Index index = Index.withRelations(SOME_INDEX_NAME);
+    IndexType.IndexMainType mainType = IndexType.main(index, "foo");
+    NewRegularIndex underTest = new NewRegularIndex(index, defaultSettingsConfiguration);
+    underTest.createTypeMapping(mainType);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("main type of relation must be "+ mainType);
+
+    underTest.createTypeMapping(IndexType.relation(IndexType.main(index, "donut"), "bar"));
+  }
+
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void build_fails_with_ISE_if_no_mainType_is_defined(Index index) {
+    NewRegularIndex underTest = new NewRegularIndex(index, defaultSettingsConfiguration);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Mapping for main type must be defined");
+
+    underTest.build();
+  }
+
+  @DataProvider
+  public static Object[][] indexWithAndWithoutRelations() {
+    return new Object[][] {
+      {Index.simple(SOME_INDEX_NAME)},
+      {Index.withRelations(SOME_INDEX_NAME)}
+    };
+  }
+
+  @Test
+  public void build_fails_with_ISE_if_index_accepts_relations_and_none_is_defined() {
+    Index index = Index.withRelations(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(index, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(index, "foo"));
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("At least one relation must be defined when index accepts relations");
+
+    underTest.build();
+  }
+
+  @Test
+  public void build_does_not_enforce_routing_if_mainType_does_not_accepts_relations() {
+    Index someIndex = Index.simple(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(someIndex, "foo"));
+
+    BuiltIndex<NewRegularIndex> builtIndex = underTest.build();
+
+    assertThat(builtIndex.getAttributes().get("_routing"))
+      .isNull();
+  }
+
+  @Test
+  public void build_enforces_routing_if_mainType_accepts_relations() {
+    Index someIndex = Index.withRelations(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(someIndex, "foo"));
+    underTest.createTypeMapping(IndexType.relation(underTest.getMainType(), "bar"));
+
+    BuiltIndex<NewRegularIndex> builtIndex = underTest.build();
+
+    assertThat((Map<String, Object>) builtIndex.getAttributes().get("_routing"))
+      .contains(entry("required", true));
+  }
+
+  @Test
+  public void build_does_not_define_type_field_if_index_does_not_accept_relations() {
+    Index someIndex = Index.simple(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(someIndex, "foo"));
+
+    BuiltIndex<NewRegularIndex> builtIndex = underTest.build();
+
+    Map<String, Object> properties = (Map<String, Object>) builtIndex.getAttributes().get("properties");
+    assertThat(properties.get("indexType"))
+      .isNull();
+  }
+
+  @Test
+  public void build_defines_type_field_if_index_accepts_relations() {
+    Index someIndex = Index.withRelations(SOME_INDEX_NAME);
+    NewRegularIndex underTest = new NewRegularIndex(someIndex, defaultSettingsConfiguration);
+    underTest.createTypeMapping(IndexType.main(someIndex, "foo"));
+    underTest.createTypeMapping(IndexType.relation(underTest.getMainType(), "bar"));
+
+    BuiltIndex<NewRegularIndex> builtIndex = underTest.build();
+
+    Map<String, Object> properties = (Map<String, Object>) builtIndex.getAttributes().get("properties");
+    assertThat((Map) properties.get("indexType"))
+      .isEqualTo(ImmutableMap.of(
+        TYPE, "keyword",
+        NORMS, false,
+        STORE, false,
+        "doc_values", false));
+  }
+
+  @DataProvider
+  public static Object[][] indexes() {
+    String someIndexName = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+    return new Object[][] {
+      {Index.simple(someIndexName)},
+      {Index.withRelations(someIndexName)}
+    };
+  }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/TestNewIndex.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/TestNewIndex.java
new file mode 100644 (file)
index 0000000..01cd8e8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.es.newindex;
+
+import org.sonar.server.es.IndexType;
+
+public final class TestNewIndex extends NewIndex<TestNewIndex> {
+  private final IndexType.IndexMainType mainType;
+  private final TypeMapping mainTypeMapping;
+
+  public TestNewIndex(IndexType.IndexMainType mainType, SettingsConfiguration settingsConfiguration) {
+    super(mainType.getIndex(), settingsConfiguration);
+    this.mainType = mainType;
+    mainTypeMapping = super.createTypeMapping(mainType);
+  }
+
+  @Override
+  public IndexType.IndexMainType getMainType() {
+    return mainType;
+  }
+
+  public TypeMapping getMainTypeMapping() {
+    return mainTypeMapping;
+  }
+
+  @Override
+  public BuiltIndex<TestNewIndex> build() {
+    return new BuiltIndex<>(this);
+  }
+
+  public TestNewIndex addRelation(String name) {
+    super.createTypeMapping(IndexType.relation(mainType, name));
+    return this;
+  }
+
+  public TypeMapping createRelationMapping(String name) {
+    return super.createTypeMapping(IndexType.relation(mainType, name));
+  }
+}
index 1b2dd6ca3601027873033c8e3207b691f2fc3f66..88ea79f5b407d1fc83457c87d45f95a39893f85a 100644 (file)
  */
 package org.sonar.server.es.request;
 
+import java.util.Locale;
+import java.util.Random;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.common.unit.TimeValue;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Index;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 
@@ -41,21 +44,21 @@ public class ProxyCreateIndexRequestBuilderTest {
 
   @Test
   public void create_index() {
-    CreateIndexRequestBuilder requestBuilder = es.client().prepareCreate(generateNewIndexName());
+    CreateIndexRequestBuilder requestBuilder = es.client().prepareCreate(generateNewIndex());
     requestBuilder.get();
   }
 
   @Test
   public void to_string() {
-    String indexName = generateNewIndexName();
-    assertThat(es.client().prepareCreate(indexName).toString()).contains("ES create index '" + indexName + "'");
+    Index index = generateNewIndex();
+    assertThat(es.client().prepareCreate(index).toString()).contains("ES create index '" + index.getName() + "'");
   }
 
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    CreateIndexRequestBuilder requestBuilder = es.client().prepareCreate(generateNewIndexName());
+    CreateIndexRequestBuilder requestBuilder = es.client().prepareCreate(generateNewIndex());
     requestBuilder.get();
     assertThat(logTester.logs()).hasSize(1);
   }
@@ -63,7 +66,7 @@ public class ProxyCreateIndexRequestBuilderTest {
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareCreate(generateNewIndexName()).get("1");
+      es.client().prepareCreate(generateNewIndex()).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -73,7 +76,7 @@ public class ProxyCreateIndexRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareCreate(generateNewIndexName()).get(TimeValue.timeValueMinutes(1));
+      es.client().prepareCreate(generateNewIndex()).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -83,15 +86,16 @@ public class ProxyCreateIndexRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareCreate(generateNewIndexName()).execute();
+      es.client().prepareCreate(generateNewIndex()).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
     }
   }
 
-  private static String generateNewIndexName(){
-    return "index_" + Long.toString(System2.INSTANCE.now());
+  private static Index generateNewIndex(){
+    String name = randomAlphabetic(10).toLowerCase(Locale.ENGLISH);
+    return new Random().nextBoolean() ? Index.simple(name) : Index.withRelations(name);
   }
 
 }
index ed3c07391df91c06c20125453a6a4c10fd7cfd48..e5303f75c59364ccf8304066b95f3a580cdcf163 100644 (file)
@@ -25,7 +25,9 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -34,24 +36,26 @@ public class ProxyDeleteRequestBuilderTest {
 
   @Rule
   public EsTester es = EsTester.createCustom(new FakeIndexDefinition());
-
   @Rule
   public LogTester logTester = new LogTester();
 
+  private final Index index = Index.simple("fakes");
+  private final IndexType.IndexMainType mainType = IndexType.main(index, "fake");
+
   @Test
   public void delete() {
-    es.client().prepareDelete("fakes", "fake", "the_id").get();
+    es.client().prepareDelete(mainType, "the_id").get();
   }
 
   @Test
   public void to_string() {
-    assertThat(es.client().prepareDelete("fakes", "fake", "the_id").toString()).isEqualTo("ES delete request of doc the_id in index fakes/fake");
+    assertThat(es.client().prepareDelete(mainType, "the_id").toString()).isEqualTo("ES delete request of doc the_id in index fakes/fake");
   }
 
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
-    es.client().prepareDelete("fakes", "fake", "the_id").get();
+    es.client().prepareDelete(mainType, "the_id").get();
 
     assertThat(logTester.logs()).hasSize(1);
   }
@@ -59,7 +63,7 @@ public class ProxyDeleteRequestBuilderTest {
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareDelete("fakes", "fake", "the_id").get("1");
+      es.client().prepareDelete(mainType, "the_id").get("1");
       fail();
     } catch (UnsupportedOperationException e) {
       assertThat(e).hasMessage("Not yet implemented");
@@ -69,7 +73,7 @@ public class ProxyDeleteRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareDelete("fakes", "fake", "the_id").get(TimeValue.timeValueMinutes(1));
+      es.client().prepareDelete(mainType, "the_id").get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (UnsupportedOperationException e) {
       assertThat(e).hasMessage("Not yet implemented");
@@ -79,7 +83,7 @@ public class ProxyDeleteRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareDelete("fakes", "fake", "the_id").execute();
+      es.client().prepareDelete(mainType, "the_id").execute();
       fail();
     } catch (UnsupportedOperationException e) {
       assertThat(e).hasMessage("execute() should not be called as it's used for asynchronous");
index ed72c7f428fd5abb4ad539fb32ecf8cca8d2f7f5..2c30ddd99316ae539b9edd339c0ad8ebc0e8d40b 100644 (file)
  */
 package org.sonar.server.es.request;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import org.elasticsearch.action.get.GetRequestBuilder;
 import org.elasticsearch.common.unit.TimeValue;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 import org.sonar.server.es.IndexType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
-import static org.sonar.server.es.FakeIndexDefinition.INDEX_TYPE_FAKE;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
 
+@RunWith(DataProviderRunner.class)
 public class ProxyGetRequestBuilderTest {
 
   @Rule
@@ -45,14 +51,15 @@ public class ProxyGetRequestBuilderTest {
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    es.client().prepareGet(INDEX_TYPE_FAKE, "ruleKey")
+    es.client().prepareGet(TYPE_FAKE, "ruleKey")
       .get();
     assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1);
   }
 
   @Test
-  public void fail_to_get_bad_query() {
-    GetRequestBuilder requestBuilder = es.client().prepareGet(new IndexType("unknown", "test"), "rule1");
+  @UseDataProvider("mainAndRelationWithUnknownIndex")
+  public void prepareGet_fails_if_index_unknown(IndexType indexType) {
+    GetRequestBuilder requestBuilder = es.client().prepareGet(indexType, "rule1");
     try {
       requestBuilder.get();
       fail();
@@ -62,10 +69,19 @@ public class ProxyGetRequestBuilderTest {
     }
   }
 
+  @DataProvider
+  public static Object[][] mainAndRelationWithUnknownIndex() {
+    IndexType.IndexMainType mainType = IndexType.main(Index.withRelations("unknown"), "test");
+    return new Object[][] {
+      {mainType},
+      {IndexType.relation(mainType, "donut")}
+    };
+  }
+
   @Test
   public void get_with_string_timeout_is_not_implemented() {
     try {
-      es.client().prepareGet(INDEX_TYPE_FAKE, "ruleKey").get("1");
+      es.client().prepareGet(TYPE_FAKE, "ruleKey").get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -75,7 +91,7 @@ public class ProxyGetRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareGet(INDEX_TYPE_FAKE, "ruleKey").get(TimeValue.timeValueMinutes(1));
+      es.client().prepareGet(TYPE_FAKE, "ruleKey").get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -85,7 +101,7 @@ public class ProxyGetRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareGet(INDEX_TYPE_FAKE, "ruleKey").execute();
+      es.client().prepareGet(TYPE_FAKE, "ruleKey").execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index 084095536d1c397b0b141dd3ad0f394c51e9b4d7..cab9b711576756b74ad0bbf9c521185683450816 100644 (file)
  */
 package org.sonar.server.es.request;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import org.elasticsearch.action.DocWriteResponse.Result;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.index.IndexResponse;
 import org.elasticsearch.common.unit.TimeValue;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
 
+@RunWith(DataProviderRunner.class)
 public class ProxyIndexRequestBuilderTest {
 
   @Rule
@@ -44,7 +52,7 @@ public class ProxyIndexRequestBuilderTest {
 
   @Test
   public void index_with_index_type_and_id() {
-    IndexResponse response = es.client().prepareIndex(FakeIndexDefinition.INDEX_TYPE_FAKE)
+    IndexResponse response = es.client().prepareIndex(TYPE_FAKE)
       .setSource(FakeIndexDefinition.newDoc(42).getFields())
       .get();
     assertThat(response.getResult()).isSameAs(Result.CREATED);
@@ -53,7 +61,7 @@ public class ProxyIndexRequestBuilderTest {
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
-    IndexResponse response = es.client().prepareIndex(FakeIndexDefinition.INDEX_TYPE_FAKE)
+    IndexResponse response = es.client().prepareIndex(TYPE_FAKE)
       .setSource(FakeIndexDefinition.newDoc(42).getFields())
       .get();
     assertThat(response.getResult()).isSameAs(Result.CREATED);
@@ -61,33 +69,31 @@ public class ProxyIndexRequestBuilderTest {
   }
 
   @Test
-  public void fail_if_bad_query() {
-    IndexRequestBuilder requestBuilder = es.client().prepareIndex(new IndexType("unknownIndex", "unknownType"));
+  @UseDataProvider("mainOrRelationType")
+  public void fail_if_bad_query(IndexType indexType) {
+    IndexRequestBuilder requestBuilder = es.client().prepareIndex(indexType);
     try {
       requestBuilder.get();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
-      assertThat(e.getMessage()).contains("Fail to execute ES index request for key 'null' on index 'unknownIndex' on type 'unknownType'");
+      assertThat(e.getMessage()).contains("Fail to execute ES index request for key 'null' on index 'foo' on type 'bar'");
     }
   }
 
-  @Test
-  public void fail_if_bad_query_with_basic_profiling() {
-    IndexRequestBuilder requestBuilder = es.client().prepareIndex(new IndexType("unknownIndex", "unknownType"));
-    try {
-      requestBuilder.get();
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalStateException.class);
-      assertThat(e.getMessage()).contains("Fail to execute ES index request for key 'null' on index 'unknownIndex' on type 'unknownType'");
-    }
+  @DataProvider
+  public static Object[][] mainOrRelationType() {
+    IndexMainType mainType = IndexType.main(Index.withRelations("foo"), "bar");
+    return new Object[][] {
+      {mainType},
+      {IndexType.relation(mainType, "donut")}
+    };
   }
 
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareIndex(FakeIndexDefinition.INDEX_TYPE_FAKE).get("1");
+      es.client().prepareIndex(TYPE_FAKE).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -97,7 +103,7 @@ public class ProxyIndexRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareIndex(FakeIndexDefinition.INDEX_TYPE_FAKE).get(TimeValue.timeValueMinutes(1));
+      es.client().prepareIndex(TYPE_FAKE).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -107,7 +113,7 @@ public class ProxyIndexRequestBuilderTest {
   @Test
   public void do_not_support_execute_method() {
     try {
-      es.client().prepareIndex(FakeIndexDefinition.INDEX_TYPE_FAKE).execute();
+      es.client().prepareIndex(TYPE_FAKE).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index 96db5c90eff82a908632a7c8cc95a792c7844d65..312585d5d004e7fef539323d583b5e8bfee0e0a4 100644 (file)
@@ -25,7 +25,8 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -40,41 +41,28 @@ public class ProxyIndicesExistsRequestBuilderTest {
 
   @Test
   public void exists() {
-    assertThat(es.client().prepareIndicesExist(FakeIndexDefinition.INDEX).get().isExists()).isTrue();
-    assertThat(es.client().prepareIndicesExist("unknown").get().isExists()).isFalse();
+    assertThat(es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).get().isExists()).isTrue();
+    assertThat(es.client().prepareIndicesExist(Index.simple("unknown")).get().isExists()).isFalse();
   }
 
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    es.client().prepareIndicesExist(FakeIndexDefinition.INDEX).get();
+    es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).get();
 
     assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1);
   }
 
-  @Test
-  public void fail_to_exists() {
-    try {
-      es.client().prepareIndicesExist().get();
-
-      // expected to fail because elasticsearch is not correctly configured, but that does not matter
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalStateException.class);
-      assertThat(e.getMessage()).contains("Fail to execute ES indices exists request");
-    }
-  }
-
   @Test
   public void to_string() {
-    assertThat(es.client().prepareIndicesExist(FakeIndexDefinition.INDEX).toString()).isEqualTo("ES indices exists request on indices 'fakes'");
+    assertThat(es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).toString()).isEqualTo("ES indices exists request on indices 'fakes'");
   }
 
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareIndicesExist().get("1");
+      es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -84,7 +72,7 @@ public class ProxyIndicesExistsRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareIndicesExist().get(TimeValue.timeValueMinutes(1));
+      es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -94,7 +82,7 @@ public class ProxyIndicesExistsRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareIndicesExist().execute();
+      es.client().prepareIndicesExist(FakeIndexDefinition.DESCRIPTOR).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index e12b3b7f84f2c1d5d96b6ac76d388a5e5172a02b..d159cfe0a45e7bb39e508a8be718d9998f76fc89 100644 (file)
@@ -25,7 +25,8 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -40,12 +41,12 @@ public class ProxyIndicesStatsRequestBuilderTest {
 
   @Test
   public void stats() {
-    es.client().prepareStats(FakeIndexDefinition.INDEX).get();
+    es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).get();
   }
 
   @Test
   public void to_string() {
-    assertThat(es.client().prepareStats(FakeIndexDefinition.INDEX).setIndices("rules").toString()).isEqualTo("ES indices stats request on indices 'rules'");
+    assertThat(es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).setIndices("rules").toString()).isEqualTo("ES indices stats request on indices 'rules'");
     assertThat(es.client().prepareStats().toString()).isEqualTo("ES indices stats request");
   }
 
@@ -53,7 +54,7 @@ public class ProxyIndicesStatsRequestBuilderTest {
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    es.client().prepareStats(FakeIndexDefinition.INDEX).get();
+    es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).get();
 
     assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1);
   }
@@ -61,7 +62,7 @@ public class ProxyIndicesStatsRequestBuilderTest {
   @Test
   public void fail_to_stats() {
     try {
-      es.client().prepareStats("unknown").get();
+      es.client().prepareStats(Index.simple("unknown")).get();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
@@ -72,7 +73,7 @@ public class ProxyIndicesStatsRequestBuilderTest {
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareStats(FakeIndexDefinition.INDEX).get("1");
+      es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -82,7 +83,7 @@ public class ProxyIndicesStatsRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareStats(FakeIndexDefinition.INDEX).get(TimeValue.timeValueMinutes(1));
+      es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -92,7 +93,7 @@ public class ProxyIndicesStatsRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareStats(FakeIndexDefinition.INDEX).execute();
+      es.client().prepareStats(FakeIndexDefinition.DESCRIPTOR).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index df01d50a0a67ad17f2bca77e1de98aabef610737..68b028ecfe57ea54fc424613d3cf1065d40225dc 100644 (file)
@@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
index 00c7e1b19be750fbe4f9046806947133759ac3f5..36d8ba25e1182ec4f8cc7fab933e0b27e6cfaef1 100644 (file)
@@ -29,7 +29,7 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -44,7 +44,7 @@ public class ProxyPutMappingRequestBuilderTest {
 
   @Test
   public void put_mapping() {
-    PutMappingRequestBuilder requestBuilder = es.client().preparePutMapping(FakeIndexDefinition.INDEX)
+    PutMappingRequestBuilder requestBuilder = es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR)
       .setType(FakeIndexDefinition.TYPE)
       .setSource(mapDomain());
     requestBuilder.get();
@@ -52,9 +52,9 @@ public class ProxyPutMappingRequestBuilderTest {
 
   @Test
   public void to_string() {
-    assertThat(es.client().preparePutMapping(FakeIndexDefinition.INDEX).setSource(mapDomain()).toString())
+    assertThat(es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).setSource(mapDomain()).toString())
       .isEqualTo("ES put mapping request on indices 'fakes' with source '{\"dynamic\":false,\"_all\":{\"enabled\":false}}'");
-    assertThat(es.client().preparePutMapping(FakeIndexDefinition.INDEX).setType(FakeIndexDefinition.TYPE).setSource(mapDomain()).toString())
+    assertThat(es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).setType(FakeIndexDefinition.TYPE).setSource(mapDomain()).toString())
       .isEqualTo("ES put mapping request on indices 'fakes' on type 'fake' with source '{\"dynamic\":false,\"_all\":{\"enabled\":false}}'");
   }
 
@@ -62,7 +62,7 @@ public class ProxyPutMappingRequestBuilderTest {
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    PutMappingRequestBuilder requestBuilder = es.client().preparePutMapping(FakeIndexDefinition.INDEX)
+    PutMappingRequestBuilder requestBuilder = es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR)
       .setType(FakeIndexDefinition.TYPE)
       .setSource(mapDomain());
     requestBuilder.get();
@@ -73,7 +73,7 @@ public class ProxyPutMappingRequestBuilderTest {
   @Test
   public void fail_on_bad_query() {
     try {
-      es.client().preparePutMapping().get();
+      es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).get();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
@@ -84,7 +84,7 @@ public class ProxyPutMappingRequestBuilderTest {
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().preparePutMapping().get("1");
+      es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -94,7 +94,7 @@ public class ProxyPutMappingRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().preparePutMapping().get(TimeValue.timeValueMinutes(1));
+      es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -104,7 +104,7 @@ public class ProxyPutMappingRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().preparePutMapping().execute();
+      es.client().preparePutMapping(FakeIndexDefinition.DESCRIPTOR).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index 19f8fb29e99bac72cf5e8ac0177f2f7f89cd5ef3..12b68a7af9e7b93fe34ac43047d0e91b18392679 100644 (file)
@@ -26,7 +26,8 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -41,21 +42,20 @@ public class ProxyRefreshRequestBuilderTest {
 
   @Test
   public void refresh() {
-    RefreshRequestBuilder requestBuilder = es.client().prepareRefresh(FakeIndexDefinition.INDEX);
+    RefreshRequestBuilder requestBuilder = es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR);
     requestBuilder.get();
   }
 
   @Test
   public void to_string() {
-    assertThat(es.client().prepareRefresh(FakeIndexDefinition.INDEX).toString()).isEqualTo("ES refresh request on indices 'fakes'");
-    assertThat(es.client().prepareRefresh().toString()).isEqualTo("ES refresh request");
+    assertThat(es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR).toString()).isEqualTo("ES refresh request on indices 'fakes'");
   }
 
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    RefreshRequestBuilder requestBuilder = es.client().prepareRefresh(FakeIndexDefinition.INDEX);
+    RefreshRequestBuilder requestBuilder = es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR);
     requestBuilder.get();
     assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1);
   }
@@ -63,7 +63,7 @@ public class ProxyRefreshRequestBuilderTest {
   @Test
   public void fail_to_refresh() {
     try {
-      es.client().prepareRefresh("unknown").get();
+      es.client().prepareRefresh(Index.simple("unknown")).get();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
@@ -74,7 +74,7 @@ public class ProxyRefreshRequestBuilderTest {
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareRefresh(FakeIndexDefinition.INDEX).get("1");
+      es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -84,7 +84,7 @@ public class ProxyRefreshRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareRefresh(FakeIndexDefinition.INDEX).get(TimeValue.timeValueMinutes(1));
+      es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -94,7 +94,7 @@ public class ProxyRefreshRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareRefresh(FakeIndexDefinition.INDEX).execute();
+      es.client().prepareRefresh(FakeIndexDefinition.DESCRIPTOR).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index 03ff5c1318fcd8261ee1b9d046e028c009b51f5d..7edb2970d31a9c612e16854a9baa57caaaff2706 100644 (file)
@@ -25,8 +25,8 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
-import org.sonar.server.es.IndexType;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -41,40 +41,43 @@ public class ProxySearchRequestBuilderTest {
 
   @Test
   public void search() {
-    es.client().prepareSearch(FakeIndexDefinition.INDEX).get();
+    es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).get();
   }
 
   @Test
   public void to_string() {
-    assertThat(es.client().prepareSearch(FakeIndexDefinition.INDEX).setTypes(FakeIndexDefinition.TYPE).toString()).contains("ES search request '").contains(
-      "' on indices '[fakes]' on types '[fake]'");
-    assertThat(es.client().prepareSearch(FakeIndexDefinition.INDEX).toString()).contains("ES search request '").contains("' on indices '[fakes]'");
-    assertThat(es.client().prepareSearch(new IndexType[0]).toString()).contains("ES search request");
+    assertThat(es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).setTypes(FakeIndexDefinition.TYPE).toString()).contains("ES search request '")
+      .contains("' on indices '[fakes]' on types '[fake]'");
+    assertThat(es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).toString())
+      .contains("ES search request '")
+      .contains("' on indices '[fakes]'");
   }
 
   @Test
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    es.client().prepareSearch(FakeIndexDefinition.INDEX).get();
+    es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).get();
     assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1);
   }
 
   @Test
   public void fail_to_search_bad_query() {
     try {
-      es.client().prepareSearch("non-existing-index").get();
+      es.client().prepareSearch(Index.simple("unknown")).get();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
-      assertThat(e.getMessage()).contains("Fail to execute ES search request 'SearchRequest{").contains("}' on indices '[non-existing-index]'");
+      assertThat(e.getMessage())
+        .contains("Fail to execute ES search request 'SearchRequest{")
+        .contains("}' on indices '[unknown]'");
     }
   }
 
   @Test
   public void get_with_string_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareSearch(FakeIndexDefinition.INDEX).get("1");
+      es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).get("1");
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -84,7 +87,7 @@ public class ProxySearchRequestBuilderTest {
   @Test
   public void get_with_time_value_timeout_is_not_yet_implemented() {
     try {
-      es.client().prepareSearch(FakeIndexDefinition.INDEX).get(TimeValue.timeValueMinutes(1));
+      es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).get(TimeValue.timeValueMinutes(1));
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented");
@@ -94,7 +97,7 @@ public class ProxySearchRequestBuilderTest {
   @Test
   public void execute_should_throw_an_unsupported_operation_exception() {
     try {
-      es.client().prepareSearch(FakeIndexDefinition.INDEX).execute();
+      es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR).execute();
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(UnsupportedOperationException.class).hasMessage("execute() should not be called as it's used for asynchronous");
index 5a4961b7dd6c7014a7cfe721fe30e25a8d3a0df7..859f5a643f27775b41a9b71034de82484dab2b56 100644 (file)
@@ -26,7 +26,7 @@ import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -43,7 +43,7 @@ public class ProxySearchScrollRequestBuilderTest {
   public void trace_logs() {
     logTester.setLevel(LoggerLevel.TRACE);
 
-    SearchResponse response = es.client().prepareSearch(FakeIndexDefinition.INDEX)
+    SearchResponse response = es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR)
       .setScroll(TimeValue.timeValueMinutes(1))
       .get();
     logTester.clear();
@@ -55,7 +55,7 @@ public class ProxySearchScrollRequestBuilderTest {
   public void no_trace_logs() {
     logTester.setLevel(LoggerLevel.DEBUG);
 
-    SearchResponse response = es.client().prepareSearch(FakeIndexDefinition.INDEX)
+    SearchResponse response = es.client().prepareSearch(FakeIndexDefinition.DESCRIPTOR)
       .setScroll(TimeValue.timeValueMinutes(1))
       .get();
     logTester.clear();
index 12412646e35d22c1baf956915455f071dd4f4b52..109c1e6452d7a7ee4deabf3ffe2f5f78b512c915 100644 (file)
@@ -49,7 +49,6 @@ public class ProxyWebServerHealthRequestBuilderTest {
 
   @Test
   public void to_string() {
-    assertThat(es.client().prepareHealth("rules").toString()).isEqualTo("ES cluster health request on indices 'rules'");
     assertThat(es.client().prepareHealth().toString()).isEqualTo("ES cluster health request");
   }
 
index a9dd93ec4867ece5cf96687929307cb0f63607ce..43bd47022fb828a21155267eb9cc34acd8c48106 100644 (file)
@@ -21,10 +21,13 @@ package org.sonar.server.issue.index;
 
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.NewIndex;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
 
 public class IssueIndexDefinitionTest {
 
@@ -36,12 +39,14 @@ public class IssueIndexDefinitionTest {
     def.define(underTest);
 
     assertThat(underTest.getIndices()).hasSize(1);
-    NewIndex issuesIndex = underTest.getIndices().get("issues");
-    assertThat(issuesIndex).isNotNull();
-    assertThat(issuesIndex.getTypes().keySet()).containsOnly("issue", "authorization");
+    NewIndex<?> issuesIndex = underTest.getIndices().get("issues");
+    IndexType.IndexMainType mainType = IndexType.main(Index.withRelations("issues"), TYPE_AUTHORIZATION);
+    assertThat(issuesIndex.getMainType()).isEqualTo(mainType);
+    assertThat(issuesIndex.getRelationsStream())
+      .containsOnly(IndexType.relation(mainType, "issue"));
 
     // no cluster by default
-    assertThat(issuesIndex.getSettings().get("index.number_of_shards")).isEqualTo("5");
-    assertThat(issuesIndex.getSettings().get("index.number_of_replicas")).isEqualTo("0");
+    assertThat(issuesIndex.getSetting("index.number_of_shards")).isEqualTo("5");
+    assertThat(issuesIndex.getSetting("index.number_of_replicas")).isEqualTo("0");
   }
 }
index 4354c53010812c6f046be8d08c2d011dce2cd609..67264571c846ea995f0a5e81e1f455b33cd0287f 100644 (file)
@@ -56,7 +56,7 @@ 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.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
 import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
 import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
@@ -82,13 +82,13 @@ public class IssueIndexerTest {
 
   @Test
   public void test_getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(INDEX_TYPE_ISSUE);
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ISSUE);
   }
 
   @Test
   public void test_getAuthorizationScope() {
     AuthorizationScope scope = underTest.getAuthorizationScope();
-    assertThat(scope.getIndexType().getIndex()).isEqualTo(INDEX_TYPE_ISSUE.getIndex());
+    assertThat(scope.getIndexType().getIndex()).isEqualTo(IssueIndexDefinition.DESCRIPTOR);
     assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
 
     Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
@@ -118,7 +118,7 @@ public class IssueIndexerTest {
 
     underTest.indexOnStartup(emptySet());
 
-    IssueDoc doc = es.getDocuments(INDEX_TYPE_ISSUE, IssueDoc.class).get(0);
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
     assertThat(doc.getId()).isEqualTo(issue.getKey());
     assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
     assertThat(doc.assigneeUuid()).isEqualTo(issue.getAssigneeUuid());
@@ -131,8 +131,6 @@ public class IssueIndexerTest {
     assertThat(doc.creationDate()).isEqualToIgnoringMillis(issue.getIssueCreationDate());
     assertThat(doc.directoryPath()).isEqualTo(dir.path());
     assertThat(doc.filePath()).isEqualTo(file.path());
-    assertThat(doc.getParent()).isEqualTo(project.uuid());
-    assertThat(doc.getRouting()).isEqualTo(project.uuid());
     assertThat(doc.language()).isEqualTo(issue.getLanguage());
     assertThat(doc.line()).isEqualTo(issue.getLine());
     // functional date
@@ -152,7 +150,7 @@ public class IssueIndexerTest {
 
     underTest.indexOnStartup(emptySet());
 
-    IssueDoc doc = es.getDocuments(INDEX_TYPE_ISSUE, IssueDoc.class).get(0);
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
     assertThat(doc.getCwe()).containsExactlyInAnyOrder("123", "863");
     assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder("a3");
     assertThat(doc.getSansTop25()).containsExactlyInAnyOrder(SANS_TOP_25_POROUS_DEFENSES);
@@ -160,7 +158,7 @@ public class IssueIndexerTest {
 
   @Test
   public void indexOnStartup_does_not_fail_on_errors_and_does_enable_recovery_mode() {
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
     db.issues().insertIssue(organization);
 
     try {
@@ -170,7 +168,7 @@ public class IssueIndexerTest {
     } finally {
       assertThatIndexHasSize(0);
       assertThatEsQueueTableHasSize(0);
-      es.unlockWrites(INDEX_TYPE_ISSUE);
+      es.unlockWrites(TYPE_ISSUE);
     }
   }
 
@@ -200,7 +198,7 @@ public class IssueIndexerTest {
 
     underTest.indexOnAnalysis(project.uuid());
 
-    assertThat(es.getDocuments(INDEX_TYPE_ISSUE))
+    assertThat(es.getDocuments(TYPE_ISSUE))
       .extracting(SearchHit::getId)
       .containsExactlyInAnyOrder(issue.getKey(), "orphan");
   }
@@ -211,7 +209,7 @@ public class IssueIndexerTest {
    */
   @Test
   public void indexOnAnalysis_does_not_fail_on_errors_and_does_not_enable_recovery_mode() {
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
     IssueDto issue = db.issues().insertIssue(organization);
 
     try {
@@ -221,7 +219,7 @@ public class IssueIndexerTest {
     } finally {
       assertThatIndexHasSize(0);
       assertThatEsQueueTableHasSize(0);
-      es.unlockWrites(INDEX_TYPE_ISSUE);
+      es.unlockWrites(TYPE_ISSUE);
     }
   }
 
@@ -273,7 +271,7 @@ public class IssueIndexerTest {
   public void errors_during_project_deletion_are_recovered() {
     addIssueToIndex("P1", "I1");
     assertThatIndexHasSize(1);
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
 
     IndexingResult result = indexProject("P1", ProjectIndexer.Cause.PROJECT_DELETION);
     assertThat(result.getTotal()).isEqualTo(1L);
@@ -285,7 +283,7 @@ public class IssueIndexerTest {
     assertThat(result.getFailures()).isEqualTo(1L);
     assertThatIndexHasSize(1);
 
-    es.unlockWrites(INDEX_TYPE_ISSUE);
+    es.unlockWrites(TYPE_ISSUE);
 
     result = recover();
     assertThat(result.getTotal()).isEqualTo(1L);
@@ -338,7 +336,7 @@ public class IssueIndexerTest {
     db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
 
     // index is read-only
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
 
     underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
 
@@ -348,7 +346,7 @@ public class IssueIndexerTest {
     assertThatEsQueueTableHasSize(2);
 
     // re-enable write on index
-    es.unlockWrites(INDEX_TYPE_ISSUE);
+    es.unlockWrites(TYPE_ISSUE);
 
     // emulate the recovery daemon
     IndexingResult result = recover();
@@ -361,7 +359,7 @@ public class IssueIndexerTest {
 
   @Test
   public void recovery_does_not_fail_if_unsupported_docIdType() {
-    EsQueueDto item = EsQueueDto.create(INDEX_TYPE_ISSUE.format(), "I1", "unknown", "P1");
+    EsQueueDto item = EsQueueDto.create(TYPE_ISSUE.format(), "I1", "unknown", "P1");
     db.getDbClient().esQueueDao().insert(db.getSession(), item);
     db.commit();
 
@@ -375,7 +373,7 @@ public class IssueIndexerTest {
 
   @Test
   public void indexing_recovers_multiple_errors_on_the_same_issue() {
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
     IssueDto issue = db.issues().insertIssue(organization);
 
     // three changes on the same issue
@@ -387,7 +385,7 @@ public class IssueIndexerTest {
     // three attempts of indexing are stored in es_queue recovery table
     assertThatEsQueueTableHasSize(3);
 
-    es.unlockWrites(INDEX_TYPE_ISSUE);
+    es.unlockWrites(TYPE_ISSUE);
     recover();
 
     assertThatIndexHasOnly(issue);
@@ -402,7 +400,7 @@ public class IssueIndexerTest {
     IssueDto issue1 = db.issues().insertIssue(IssueTesting.newIssue(rule, project, file));
     IssueDto issue2 = db.issues().insertIssue(IssueTesting.newIssue(rule, project, file));
 
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
 
     IndexingResult result = indexProject(project.uuid(), ProjectIndexer.Cause.PROJECT_DELETION);
     assertThat(result.getTotal()).isEqualTo(2L);
@@ -414,7 +412,7 @@ public class IssueIndexerTest {
     assertThat(result.getFailures()).isEqualTo(2L);
     assertThatIndexHasSize(0);
 
-    es.unlockWrites(INDEX_TYPE_ISSUE);
+    es.unlockWrites(TYPE_ISSUE);
 
     result = recover();
     assertThat(result.getTotal()).isEqualTo(2L);
@@ -446,7 +444,7 @@ public class IssueIndexerTest {
   @Test
   public void deleteByKeys_does_not_recover_from_errors() {
     addIssueToIndex("P1", "Issue1");
-    es.lockWrites(INDEX_TYPE_ISSUE);
+    es.lockWrites(TYPE_ISSUE);
 
     try {
       // FIXME : test also message
@@ -455,7 +453,7 @@ public class IssueIndexerTest {
     } finally {
       assertThatIndexHasOnly("Issue1");
       assertThatEsQueueTableHasSize(0);
-      es.unlockWrites(INDEX_TYPE_ISSUE);
+      es.unlockWrites(TYPE_ISSUE);
     }
   }
 
@@ -481,7 +479,7 @@ public class IssueIndexerTest {
     new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()))
       .index(asList(issueDoc).iterator());
 
-    assertThat(es.countDocuments(INDEX_TYPE_ISSUE)).isEqualTo(1L);
+    assertThat(es.countDocuments(TYPE_ISSUE)).isEqualTo(1L);
   }
 
   @Test
@@ -495,7 +493,7 @@ public class IssueIndexerTest {
 
     underTest.indexOnStartup(emptySet());
 
-    IssueDoc doc = es.getDocuments(INDEX_TYPE_ISSUE, IssueDoc.class).get(0);
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
     assertThat(doc.getId()).isEqualTo(issue.getKey());
     assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
     assertThat(doc.componentUuid()).isEqualTo(file.uuid());
@@ -505,22 +503,22 @@ public class IssueIndexerTest {
   }
 
   private void addIssueToIndex(String projectUuid, String issueKey) {
-    es.putDocuments(INDEX_TYPE_ISSUE,
+    es.putDocuments(TYPE_ISSUE,
       newDoc().setKey(issueKey).setProjectUuid(projectUuid));
   }
 
   private void assertThatIndexHasSize(long expectedSize) {
-    assertThat(es.countDocuments(INDEX_TYPE_ISSUE)).isEqualTo(expectedSize);
+    assertThat(es.countDocuments(TYPE_ISSUE)).isEqualTo(expectedSize);
   }
 
   private void assertThatIndexHasOnly(IssueDto... expectedIssues) {
-    assertThat(es.getDocuments(INDEX_TYPE_ISSUE))
+    assertThat(es.getDocuments(TYPE_ISSUE))
       .extracting(SearchHit::getId)
       .containsExactlyInAnyOrder(Arrays.stream(expectedIssues).map(IssueDto::getKey).toArray(String[]::new));
   }
 
   private void assertThatIndexHasOnly(String... expectedKeys) {
-    List<IssueDoc> issues = es.getDocuments(INDEX_TYPE_ISSUE, IssueDoc.class);
+    List<IssueDoc> issues = es.getDocuments(TYPE_ISSUE, IssueDoc.class);
     assertThat(issues).extracting(IssueDoc::key).containsOnly(expectedKeys);
   }
 
index 8136c1e9c7a3b9a526d76833b337c3c5d03e1878..11e543901f57f88ee05212f2a9d50e73541b8f2e 100644 (file)
@@ -43,12 +43,13 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_KEY_UPDATE;
 import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
 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.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ProjectMeasuresIndexerTest {
 
@@ -65,7 +66,7 @@ public class ProjectMeasuresIndexerTest {
   public void index_nothing() {
     underTest.indexOnStartup(emptySet());
 
-    assertThat(es.countDocuments(INDEX_TYPE_PROJECT_MEASURES)).isZero();
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
   }
 
   @Test
@@ -158,7 +159,7 @@ public class ProjectMeasuresIndexerTest {
     db.getDbClient().componentDao().delete(db.getSession(), project.getId());
     IndexingResult result = indexProject(project, PROJECT_DELETION);
 
-    assertThat(es.countDocuments(INDEX_TYPE_PROJECT_MEASURES)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isEqualTo(0);
     assertThat(result.getTotal()).isEqualTo(1L);
     assertThat(result.getSuccess()).isEqualTo(1L);
   }
@@ -170,13 +171,13 @@ public class ProjectMeasuresIndexerTest {
 
     underTest.index(db.getSession(), emptyList());
 
-    assertThat(es.countDocuments(INDEX_TYPE_PROJECT_MEASURES)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isEqualTo(0);
   }
 
   @Test
   public void errors_during_indexing_are_recovered() {
     ComponentDto project = db.components().insertPrivateProject();
-    es.lockWrites(INDEX_TYPE_PROJECT_MEASURES);
+    es.lockWrites(TYPE_PROJECT_MEASURES);
 
     IndexingResult result = indexProject(project, PROJECT_CREATION);
     assertThat(result.getTotal()).isEqualTo(1L);
@@ -186,10 +187,10 @@ public class ProjectMeasuresIndexerTest {
     result = recover();
     assertThat(result.getTotal()).isEqualTo(1L);
     assertThat(result.getFailures()).isEqualTo(1L);
-    assertThat(es.countDocuments(INDEX_TYPE_PROJECT_MEASURES)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isEqualTo(0);
     assertThatEsQueueTableHasSize(1);
 
-    es.unlockWrites(INDEX_TYPE_PROJECT_MEASURES);
+    es.unlockWrites(TYPE_PROJECT_MEASURES);
 
     result = recover();
     assertThat(result.getTotal()).isEqualTo(1L);
@@ -205,7 +206,7 @@ public class ProjectMeasuresIndexerTest {
 
     underTest.indexOnAnalysis(branch.uuid());
 
-    assertThat(es.countDocuments(INDEX_TYPE_PROJECT_MEASURES)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isEqualTo(0);
   }
 
   private IndexingResult indexProject(ComponentDto project, ProjectIndexer.Cause cause) {
@@ -217,8 +218,10 @@ public class ProjectMeasuresIndexerTest {
 
   private void assertThatProjectHasTag(ComponentDto project, String expectedTag) {
     SearchRequestBuilder request = es.client()
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
-      .setQuery(boolQuery().filter(termQuery(FIELD_TAGS, expectedTag)));
+      .prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
+      .setQuery(boolQuery()
+        .filter(termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()))
+        .filter(termQuery(FIELD_TAGS, expectedTag)));
     assertThat(request.get().getHits().getHits())
       .extracting(SearchHit::getId)
       .contains(project.uuid());
@@ -229,12 +232,12 @@ public class ProjectMeasuresIndexerTest {
   }
 
   private void assertThatIndexContainsOnly(SnapshotDto... expectedProjects) {
-    assertThat(es.getIds(INDEX_TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
+    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
       Arrays.stream(expectedProjects).map(SnapshotDto::getComponentUuid).toArray(String[]::new));
   }
 
   private void assertThatIndexContainsOnly(ComponentDto... expectedProjects) {
-    assertThat(es.getIds(INDEX_TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
+    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
       Arrays.stream(expectedProjects).map(ComponentDto::uuid).toArray(String[]::new));
   }
 
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationDocTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/permission/index/AuthorizationDocTest.java
new file mode 100644 (file)
index 0000000..0d4e536
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import java.util.Random;
+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.server.es.Index;
+import org.sonar.server.es.IndexType;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.fail;
+
+@RunWith(DataProviderRunner.class)
+public class AuthorizationDocTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void idOf_returns_argument_with_a_prefix() {
+    String s = randomAlphabetic(12);
+
+    assertThat(AuthorizationDoc.idOf(s)).isEqualTo("auth_" + s);
+  }
+
+  @Test
+  public void idOf_fails_with_NPE_if_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("projectUuid can't be null");
+
+    AuthorizationDoc.idOf(null);
+  }
+
+  @Test
+  public void projectUuidOf_fails_with_NPE_if_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    AuthorizationDoc.projectUuidOf(null);
+  }
+
+  @Test
+  public void projectUuidOf_returns_substring_if_starts_with_id_prefix() {
+    assertThat(AuthorizationDoc.projectUuidOf("auth_")).isEmpty();
+
+    String id = randomAlphabetic(1 + new Random().nextInt(10));
+    assertThat(AuthorizationDoc.projectUuidOf("auth_" + id)).isEqualTo(id);
+  }
+
+  @Test
+  public void projectUuidOf_returns_argument_if_does_not_starts_with_id_prefix() {
+    String id = randomAlphabetic(1 + new Random().nextInt(10));
+    assertThat(AuthorizationDoc.projectUuidOf(id)).isEqualTo(id);
+    assertThat(AuthorizationDoc.projectUuidOf("")).isEqualTo("");
+  }
+
+  @Test
+  public void getId_fails_with_NPE_if_IndexPermissions_has_null_projectUuid() {
+    IndexPermissions dto = new IndexPermissions(null, null);
+    IndexType.IndexMainType mainType = IndexType.main(Index.simple("foo"), "bar");
+    AuthorizationDoc underTest = AuthorizationDoc.fromDto(mainType, dto);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("projectUuid can't be null");
+
+    underTest.getId();
+  }
+
+  @Test
+  @UseDataProvider("dtos")
+  public void getId_returns_projectUuid_with_a_prefix(IndexPermissions dto) {
+    AuthorizationDoc underTest = AuthorizationDoc.fromDto(IndexType.main(Index.simple("foo"), "bar"), dto);
+
+    assertThat(underTest.getId()).isEqualTo("auth_" + dto.getProjectUuid());
+  }
+
+  @Test
+  @UseDataProvider("dtos")
+  public void getRouting_returns_projectUuid(IndexPermissions dto) {
+    AuthorizationDoc underTest = AuthorizationDoc.fromDto(IndexType.main(Index.simple("foo"), "bar"), dto);
+
+    assertThat(underTest.getRouting()).contains(dto.getProjectUuid());
+  }
+
+  @Test
+  public void fromDto_of_allowAnyone_is_false_and_no_user_nor_group() {
+    IndexPermissions underTest = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+
+    AuthorizationDoc doc = AuthorizationDoc.fromDto(IndexType.main(Index.simple("foo"), "bar"), underTest);
+
+    boolean auth_allowAnyone = doc.getField("auth_allowAnyone");
+    assertThat(auth_allowAnyone).isFalse();
+    List<Integer> userIds = doc.getField("auth_userIds");
+    assertThat(userIds).isEmpty();
+    List<Integer> groupIds = doc.getField("auth_groupIds");
+    assertThat(groupIds).isEmpty();
+  }
+
+  @Test
+  public void fromDto_defines_userIds_and_groupIds_if_allowAnyone_is_false() {
+    IndexPermissions underTest = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(underTest::addUserId);
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(underTest::addGroupId);
+
+    AuthorizationDoc doc = AuthorizationDoc.fromDto(IndexType.main(Index.simple("foo"), "bar"), underTest);
+
+    boolean auth_allowAnyone = doc.getField("auth_allowAnyone");
+    assertThat(auth_allowAnyone).isFalse();
+    List<Integer> userIds = doc.getField("auth_userIds");
+    assertThat(userIds).isEqualTo(underTest.getUserIds());
+    List<Integer> groupIds = doc.getField("auth_groupIds");
+    assertThat(groupIds).isEqualTo(underTest.getGroupIds());
+  }
+
+  @Test
+  public void fromDto_ignores_userIds_and_groupIds_if_allowAnyone_is_true() {
+    IndexPermissions underTest = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(underTest::addUserId);
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(underTest::addGroupId);
+    underTest.allowAnyone();
+
+    AuthorizationDoc doc = AuthorizationDoc.fromDto(IndexType.main(Index.simple("foo"), "bar"), underTest);
+
+    boolean auth_allowAnyone = doc.getField("auth_allowAnyone");
+    assertThat(auth_allowAnyone).isTrue();
+    try {
+      doc.getField("auth_userIds");
+      fail("should have thrown IllegalStateException");
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Field auth_userIds not specified in query options");
+    }
+    try {
+      doc.getField("auth_groupIds");
+      fail("should have thrown IllegalStateException");
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Field auth_groupIds not specified in query options");
+    }
+  }
+
+  @DataProvider
+  public static Object[][] dtos() {
+    IndexPermissions allowAnyone = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    allowAnyone.allowAnyone();
+    IndexPermissions someUserIds = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(someUserIds::addUserId);
+    IndexPermissions someGroupIds = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(someGroupIds::addGroupId);
+    IndexPermissions someGroupIdAndUserIs = new IndexPermissions(randomAlphabetic(3), randomAlphabetic(4));
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(someGroupIdAndUserIs::addUserId);
+    IntStream.range(0, 1 + new Random().nextInt(5)).forEach(someGroupIdAndUserIs::addGroupId);
+    return new Object[][] {
+      {allowAnyone},
+      {someUserIds},
+      {someGroupIds},
+      {someGroupIdAndUserIs}
+    };
+  }
+}
index 9d61119d2a0ee3134ff64da9f1d5acf14197156e..060f0e290e3419425d445874b9400ccf384e351f 100644 (file)
@@ -43,7 +43,7 @@ import static java.util.Arrays.stream;
 import static java.util.Collections.emptySet;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_ACTIVE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
 
 public class ActiveRuleIndexerTest {
 
@@ -73,13 +73,13 @@ public class ActiveRuleIndexerTest {
 
   @Test
   public void getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(INDEX_TYPE_ACTIVE_RULE);
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ACTIVE_RULE);
   }
 
   @Test
   public void indexOnStartup_does_nothing_if_no_data() {
     underTest.indexOnStartup(emptySet());
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isZero();
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
   }
 
   @Test
@@ -88,7 +88,7 @@ public class ActiveRuleIndexerTest {
 
     underTest.indexOnStartup(emptySet());
 
-    List<ActiveRuleDoc> docs = es.getDocuments(INDEX_TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
+    List<ActiveRuleDoc> docs = es.getDocuments(TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
     assertThat(docs).hasSize(1);
     verify(docs.get(0), profile1, activeRule);
     assertThatEsQueueTableIsEmpty();
@@ -112,44 +112,44 @@ public class ActiveRuleIndexerTest {
 
     underTest.commitAndIndex(db.getSession(), Collections.emptyList());
 
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(0);
     assertThatEsQueueTableIsEmpty();
   }
 
   @Test
   public void commitAndIndex_keeps_elements_to_recover_in_ES_QUEUE_on_errors() {
     ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
-    es.lockWrites(INDEX_TYPE_ACTIVE_RULE);
+    es.lockWrites(TYPE_ACTIVE_RULE);
 
     commitAndIndex(rule1, ar);
 
-    EsQueueDto expectedItem = EsQueueDto.create(INDEX_TYPE_ACTIVE_RULE.format(), "" + ar.getId(), "activeRuleId", valueOf(ar.getRuleId()));
+    EsQueueDto expectedItem = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "ar_" + ar.getId(), "activeRuleId", valueOf(ar.getRuleId()));
     assertThatEsQueueContainsExactly(expectedItem);
-    es.unlockWrites(INDEX_TYPE_ACTIVE_RULE);
+    es.unlockWrites(TYPE_ACTIVE_RULE);
   }
 
   @Test
   public void commitAndIndex_deletes_the_documents_that_dont_exist_in_database() {
     ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
     indexAll();
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(1);
 
     db.getDbClient().activeRuleDao().delete(db.getSession(), ar.getKey());
     commitAndIndex(rule1, ar);
 
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(0);
     assertThatEsQueueTableIsEmpty();
   }
 
   @Test
   public void index_fails_and_deletes_doc_if_docIdType_is_unsupported() {
-    EsQueueDto item = EsQueueDto.create(INDEX_TYPE_ACTIVE_RULE.format(), "the_id", "unsupported", "the_routing");
+    EsQueueDto item = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "the_id", "unsupported", "the_routing");
     db.getDbClient().esQueueDao().insert(db.getSession(), item);
 
     underTest.index(db.getSession(), asList(item));
 
     assertThatEsQueueTableIsEmpty();
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(0);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(0);
   }
 
   @Test
@@ -169,11 +169,11 @@ public class ActiveRuleIndexerTest {
   public void commitDeletionOfProfiles_does_nothing_if_profiles_are_not_indexed() {
     db.qualityProfiles().activateRule(profile1, rule1);
     indexAll();
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(1);
 
     underTest.commitDeletionOfProfiles(db.getSession(), singletonList(profile2));
 
-    assertThat(es.countDocuments(INDEX_TYPE_ACTIVE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isEqualTo(1);
   }
 
   private void assertThatEsQueueTableIsEmpty() {
@@ -194,16 +194,16 @@ public class ActiveRuleIndexerTest {
   }
 
   private void verifyOnlyIndexed(ActiveRuleDto... expected) {
-    List<String> docs = es.getIds(INDEX_TYPE_ACTIVE_RULE);
+    List<String> docs = es.getIds(TYPE_ACTIVE_RULE);
     assertThat(docs).hasSize(expected.length);
     for (ActiveRuleDto activeRuleDto : expected) {
-      assertThat(docs).contains(activeRuleDto.getId().toString());
+      assertThat(docs).contains("ar_" + activeRuleDto.getId());
     }
   }
 
   private void verify(ActiveRuleDoc doc1, QProfileDto profile, ActiveRuleDto activeRule) {
     assertThat(doc1)
-      .matches(doc -> doc.getId().equals("" + activeRule.getId()))
+      .matches(doc -> doc.getId().equals("ar_" + activeRule.getId()))
       .matches(doc -> doc.getRuleProfileUuid().equals(profile.getRulesProfileUuid()))
       .matches(doc -> doc.getSeverity().equals(activeRule.getSeverityString()));
   }
index f71eec9c26414f3573d656783c365bb75a076e6c..81bd2172c7477085036fbe5e6b1879fdd42e3666 100644 (file)
@@ -27,18 +27,20 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.NewIndex;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
-import static org.sonar.server.es.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_ID;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_REPOSITORY;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE;
 
 public class RuleIndexDefinitionTest {
 
@@ -54,13 +56,16 @@ public class RuleIndexDefinitionTest {
     underTest.define(context);
 
     assertThat(context.getIndices()).hasSize(1);
-    NewIndex ruleIndex = context.getIndices().get("rules");
-    assertThat(ruleIndex).isNotNull();
-    assertThat(ruleIndex.getTypes().keySet()).containsOnly("activeRule", "ruleExtension", "rule");
+    NewIndex<?> ruleIndex = context.getIndices().get("rules");
+    assertThat(ruleIndex.getMainType())
+      .isEqualTo(IndexType.main(Index.withRelations("rules"), "rule"));
+    assertThat(ruleIndex.getRelationsStream())
+      .extracting(IndexType.IndexRelationType::getName)
+      .containsOnly("activeRule", "ruleExtension");
 
     // no cluster by default
-    assertThat(ruleIndex.getSettings().get("index.number_of_shards")).isEqualTo("2");
-    assertThat(ruleIndex.getSettings().get("index.number_of_replicas")).isEqualTo("0");
+    assertThat(ruleIndex.getSetting("index.number_of_shards")).isEqualTo("2");
+    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("0");
   }
 
   @Test
@@ -70,7 +75,7 @@ public class RuleIndexDefinitionTest {
     underTest.define(context);
 
     NewIndex ruleIndex = context.getIndices().get("rules");
-    assertThat(ruleIndex.getSettings().get("index.number_of_replicas")).isEqualTo("1");
+    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("1");
   }
 
   @Test
@@ -82,13 +87,13 @@ public class RuleIndexDefinitionTest {
       "quick", "brown", "fox", "jump", "over", "lazi", "dog");
 
     // the following method fails if PUT fails
-    tester.putDocuments(INDEX_TYPE_RULE, new RuleDoc(ImmutableMap.of(
+    tester.putDocuments(TYPE_RULE, new RuleDoc(ImmutableMap.of(
       FIELD_RULE_ID, "123",
       FIELD_RULE_HTML_DESCRIPTION, longText,
       FIELD_RULE_REPOSITORY, "squid",
       FIELD_RULE_KEY, "squid:S001")));
-    assertThat(tester.countDocuments(INDEX_TYPE_RULE)).isEqualTo(1);
-    assertThat(tester.client().prepareSearch(INDEX_TYPE_RULE.getIndex()).setQuery(matchQuery(ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), "brown fox jumps lazy"))
+    assertThat(tester.countDocuments(TYPE_RULE)).isEqualTo(1);
+    assertThat(tester.client().prepareSearch(TYPE_RULE.getIndex()).setQuery(matchQuery(ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), "brown fox jumps lazy"))
       .get().getHits().getTotalHits()).isEqualTo(1);
   }
 
@@ -110,7 +115,7 @@ public class RuleIndexDefinitionTest {
   }
 
   private List<AnalyzeResponse.AnalyzeToken> analyzeIndexedTokens(String text) {
-    return tester.client().nativeClient().admin().indices().prepareAnalyze(INDEX_TYPE_RULE.getIndex(),
+    return tester.client().nativeClient().admin().indices().prepareAnalyze(TYPE_RULE.getIndex().getName(),
       text)
       .setField(ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION))
       .execute().actionGet().getTokens();
index 3068233aaa5c74e871cdd21533781c88a4e6b056..e69c0042c08cf84dcfd6de695bcdfa36da010a8e 100644 (file)
@@ -82,9 +82,9 @@ import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
 import static org.sonar.server.rule.index.RuleIndex.FACET_REPOSITORIES;
 import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS;
 import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_ACTIVE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
 
 public class RuleIndexTest {
 
@@ -296,7 +296,7 @@ public class RuleIndexTest {
     createRuleMetadata(rule2, organization, setTags("tag2"));
     index();
 
-    assertThat(es.countDocuments(INDEX_TYPE_RULE_EXTENSION)).isEqualTo(4);
+    assertThat(es.countDocuments(TYPE_RULE_EXTENSION)).isEqualTo(4);
     // tag2s in filter
     RuleQuery query = new RuleQuery().setOrganization(organization).setTags(of("tag2s"));
     verifySearch(query, rule2);
@@ -588,8 +588,8 @@ public class RuleIndexTest {
   }
 
   private void index() {
-    ruleIndexer.indexOnStartup(Sets.newHashSet(INDEX_TYPE_RULE, INDEX_TYPE_RULE_EXTENSION));
-    activeRuleIndexer.indexOnStartup(Sets.newHashSet(INDEX_TYPE_ACTIVE_RULE));
+    ruleIndexer.indexOnStartup(Sets.newHashSet(TYPE_RULE, TYPE_RULE_EXTENSION));
+    activeRuleIndexer.indexOnStartup(Sets.newHashSet(TYPE_ACTIVE_RULE));
   }
 
   private RuleQuery newRuleQuery() {
@@ -1102,8 +1102,8 @@ public class RuleIndexTest {
     index();
 
     // inactive rules on profile
-    List<SearchHit> ruleDocs = es.getDocuments(INDEX_TYPE_RULE);
-    List<SearchHit> activeRuleDocs = es.getDocuments(INDEX_TYPE_ACTIVE_RULE);
+    List<SearchHit> ruleDocs = es.getDocuments(TYPE_RULE);
+    List<SearchHit> activeRuleDocs = es.getDocuments(TYPE_ACTIVE_RULE);
     assertThat(underTest.searchAll(new RuleQuery().setActivation(false).setQProfile(profile2)))
       .containsOnly(rule2.getId(), rule3.getId());
 
index 83123f4c57f803454c98461911ea9ce9237e3f31..6161448d09191a5c6149e162d6dc0afb3bb26f18 100644 (file)
@@ -43,6 +43,8 @@ import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE_EXTENSION;
 
 public class RuleIndexerTest {
 
@@ -75,7 +77,7 @@ public class RuleIndexerTest {
   @Test
   public void index_nothing() {
     underTest.index(dbSession, emptyList());
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(0L);
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(0L);
   }
 
   @Test
@@ -83,7 +85,7 @@ public class RuleIndexerTest {
     dbClient.ruleDao().insert(dbSession, rule);
     underTest.commitAndIndex(dbSession, rule.getId());
 
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(1);
   }
 
   @Test
@@ -92,13 +94,13 @@ public class RuleIndexerTest {
     dbClient.ruleDao().insert(dbSession, rule.setStatus(RuleStatus.READY));
     dbSession.commit();
     underTest.commitAndIndex(dbTester.getSession(), rule.getId());
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(1);
 
     // Remove rule
     dbTester.getDbClient().ruleDao().update(dbTester.getSession(), rule.setStatus(RuleStatus.READY).setUpdatedAt(2000000000000L));
     underTest.commitAndIndex(dbTester.getSession(), rule.getId());
 
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(1);
   }
 
   @Test
@@ -115,7 +117,7 @@ public class RuleIndexerTest {
       .setScope(RuleExtensionScope.organization(organization.getUuid()));
     assertThat(
       es.client()
-        .prepareSearch(RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION)
+        .prepareSearch(TYPE_RULE_EXTENSION.getMainType())
         .setQuery(termQuery("_id", doc.getId()))
         .get()
         .getHits()
@@ -136,13 +138,13 @@ public class RuleIndexerTest {
     RuleExtensionDoc doc = new RuleExtensionDoc()
       .setRuleId(rule.getId())
       .setScope(RuleExtensionScope.organization(organization.getUuid()));
-    assertThat(es.getIds(RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION)).contains(doc.getId());
+    assertThat(es.getIds(TYPE_RULE_EXTENSION)).contains(doc.getId());
 
     // update db table "rules_metadata" with empty tags and delete tags from index
     metadata = RuleTesting.newRuleMetadata(rule, organization).setTags(emptySet());
     dbTester.getDbClient().ruleDao().insertOrUpdate(dbTester.getSession(), metadata);
     underTest.commitAndIndex(dbTester.getSession(), rule.getId(), organization);
-    assertThat(es.getIds(RuleIndexDefinition.INDEX_TYPE_RULE_EXTENSION)).doesNotContain(doc.getId());
+    assertThat(es.getIds(TYPE_RULE_EXTENSION)).doesNotContain(doc.getId());
   }
 
   @Test
@@ -151,6 +153,6 @@ public class RuleIndexerTest {
     RuleDefinitionDto rule = dbTester.rules().insert(r -> r.setDescription(description));
     underTest.commitAndIndex(dbTester.getSession(), rule.getId());
 
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(1);
   }
 }
index df336a60359b6f21907f58a1a4128e5cd0b23f35..4e7e8fedc093d5faea5afc32737b4daf495b5308 100644 (file)
@@ -21,8 +21,10 @@ package org.sonar.server.user.index;
 
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.NewIndex;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -37,11 +39,12 @@ public class UserIndexDefinitionTest {
 
     assertThat(underTest.getIndices()).hasSize(1);
     NewIndex index = underTest.getIndices().get("users");
-    assertThat(index).isNotNull();
-    assertThat(index.getTypes().keySet()).containsOnly("user");
+    assertThat(index.getMainType())
+      .isEqualTo(IndexType.main(Index.simple("users"), "user"));
+    assertThat(index.getRelationsStream()).isEmpty();
 
     // no cluster by default
-    assertThat(index.getSettings().get("index.number_of_shards")).isEqualTo("1");
-    assertThat(index.getSettings().get("index.number_of_replicas")).isEqualTo("0");
+    assertThat(index.getSetting("index.number_of_shards")).isEqualTo("1");
+    assertThat(index.getSetting("index.number_of_replicas")).isEqualTo("0");
   }
 }
index 11939f9aa28faa45689e0d5c7aeb8188ddde6df3..1775f3de25b0a1b82363aa3e150e27b876d1ddc8 100644 (file)
@@ -34,7 +34,7 @@ import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.user.index.UserIndexDefinition.INDEX_TYPE_USER;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
 
 public class UserIndexTest {
 
@@ -53,9 +53,9 @@ public class UserIndexTest {
     UserDoc user1 = newUser("user1", asList("user_1", "u1"));
     UserDoc user2 = newUser("user_with_same_email_as_user1", asList("user_2")).setEmail(user1.email());
     UserDoc user3 = newUser("inactive_user_with_same_scm_as_user1", user1.scmAccounts()).setActive(false);
-    es.putDocuments(INDEX_TYPE_USER, user1);
-    es.putDocuments(INDEX_TYPE_USER, user2);
-    es.putDocuments(INDEX_TYPE_USER, user3);
+    es.putDocuments(TYPE_USER, user1);
+    es.putDocuments(TYPE_USER, user2);
+    es.putDocuments(TYPE_USER, user3);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.scmAccounts().get(0), ORGANIZATION_UUID)).extractingResultOf("login").containsOnly(user1.login());
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.login(), ORGANIZATION_UUID)).extractingResultOf("login").containsOnly(user1.login());
@@ -71,7 +71,7 @@ public class UserIndexTest {
   public void getAtMostThreeActiveUsersForScmAccount_ignores_inactive_user() {
     String scmAccount = "scmA";
     UserDoc user = newUser(USER1_LOGIN, singletonList(scmAccount)).setActive(false);
-    es.putDocuments(INDEX_TYPE_USER, user);
+    es.putDocuments(TYPE_USER, user);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user.login(), ORGANIZATION_UUID)).isEmpty();
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(scmAccount, ORGANIZATION_UUID)).isEmpty();
@@ -84,10 +84,10 @@ public class UserIndexTest {
     UserDoc user2 = newUser("user2", Collections.emptyList()).setEmail(email);
     UserDoc user3 = newUser("user3", Collections.emptyList()).setEmail(email);
     UserDoc user4 = newUser("user4", Collections.emptyList()).setEmail(email);
-    es.putDocuments(INDEX_TYPE_USER, user1);
-    es.putDocuments(INDEX_TYPE_USER, user2);
-    es.putDocuments(INDEX_TYPE_USER, user3);
-    es.putDocuments(INDEX_TYPE_USER, user4);
+    es.putDocuments(TYPE_USER, user1);
+    es.putDocuments(TYPE_USER, user2);
+    es.putDocuments(TYPE_USER, user3);
+    es.putDocuments(TYPE_USER, user4);
 
     // restrict results to 3 users
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(email, ORGANIZATION_UUID)).hasSize(3);
@@ -96,7 +96,7 @@ public class UserIndexTest {
   @Test
   public void getAtMostThreeActiveUsersForScmAccount_is_case_sensitive_for_login() {
     UserDoc user = newUser("the_login", singletonList("John.Smith"));
-    es.putDocuments(INDEX_TYPE_USER, user);
+    es.putDocuments(TYPE_USER, user);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_login", ORGANIZATION_UUID)).hasSize(1);
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_Login", ORGANIZATION_UUID)).isEmpty();
@@ -105,7 +105,7 @@ public class UserIndexTest {
   @Test
   public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_email() {
     UserDoc user = newUser("the_login", "the_EMAIL@corp.com", singletonList("John.Smith"));
-    es.putDocuments(INDEX_TYPE_USER, user);
+    es.putDocuments(TYPE_USER, user);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_EMAIL@corp.com", ORGANIZATION_UUID)).hasSize(1);
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_email@corp.com", ORGANIZATION_UUID)).hasSize(1);
@@ -115,7 +115,7 @@ public class UserIndexTest {
   @Test
   public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_scm_account() {
     UserDoc user = newUser("the_login", singletonList("John.Smith"));
-    es.putDocuments(INDEX_TYPE_USER, user);
+    es.putDocuments(TYPE_USER, user);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("John.Smith", ORGANIZATION_UUID)).hasSize(1);
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN.SMIth", ORGANIZATION_UUID)).hasSize(1);
@@ -127,16 +127,16 @@ public class UserIndexTest {
   public void getAtMostThreeActiveUsersForScmAccount_search_only_user_within_given_organization() {
     UserDoc user1 = newUser("user1", singletonList("same_scm")).setOrganizationUuids(singletonList(ORGANIZATION_UUID));
     UserDoc user2 = newUser("user2", singletonList("same_scm")).setOrganizationUuids(singletonList("another_organization"));
-    es.putDocuments(INDEX_TYPE_USER, user1);
-    es.putDocuments(INDEX_TYPE_USER, user2);
+    es.putDocuments(TYPE_USER, user1);
+    es.putDocuments(TYPE_USER, user2);
 
     assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("same_scm", ORGANIZATION_UUID)).extractingResultOf("login").containsOnly(user1.login());
   }
 
   @Test
   public void searchUsers() {
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1"));
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER2_LOGIN, Collections.emptyList()).setEmail("email2"));
+    es.putDocuments(TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1"));
+    es.putDocuments(TYPE_USER, newUser(USER2_LOGIN, Collections.emptyList()).setEmail("email2"));
 
     assertThat(underTest.search(userQuery.build(), new SearchOptions()).getDocs()).hasSize(2);
     assertThat(underTest.search(userQuery.setTextQuery("user").build(), new SearchOptions()).getDocs()).hasSize(2);
@@ -149,9 +149,9 @@ public class UserIndexTest {
 
   @Test
   public void search_users_filter_by_organization_uuid() {
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1")
+    es.putDocuments(TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1")
       .setOrganizationUuids(newArrayList("O1", "O2")));
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER2_LOGIN, emptyList()).setEmail("email2")
+    es.putDocuments(TYPE_USER, newUser(USER2_LOGIN, emptyList()).setEmail("email2")
       .setOrganizationUuids(newArrayList("O2")));
 
     assertThat(underTest.search(userQuery.setOrganizationUuid("O42").build(), new SearchOptions()).getDocs()).isEmpty();
@@ -161,9 +161,9 @@ public class UserIndexTest {
 
   @Test
   public void search_users_filter_by_excluded_organization_uuid() {
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1")
+    es.putDocuments(TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1")
       .setOrganizationUuids(newArrayList("O1", "O2")));
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER2_LOGIN, emptyList()).setEmail("email2")
+    es.putDocuments(TYPE_USER, newUser(USER2_LOGIN, emptyList()).setEmail("email2")
       .setOrganizationUuids(newArrayList("O2")));
 
     assertThat(underTest.search(userQuery.setExcludedOrganizationUuid("O42").build(), new SearchOptions()).getDocs()).hasSize(2);
index a269a9b03264693a0711d894b81073d9cbe28499..8f4dcc30198129517768cf299eff8565f8eccd9f 100644 (file)
@@ -33,6 +33,7 @@ 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.tuple;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
 
 public class UserIndexerTest {
 
@@ -50,7 +51,7 @@ public class UserIndexerTest {
   public void index_nothing_on_startup() {
     underTest.indexOnStartup(new HashSet<>());
 
-    assertThat(es.countDocuments(UserIndexDefinition.INDEX_TYPE_USER)).isEqualTo(0L);
+    assertThat(es.countDocuments(TYPE_USER)).isEqualTo(0L);
   }
 
   @Test
@@ -59,7 +60,7 @@ public class UserIndexerTest {
 
     underTest.indexOnStartup(new HashSet<>());
 
-    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
     assertThat(docs).hasSize(1);
     UserDoc doc = docs.get(0);
     assertThat(doc.uuid()).isEqualTo(user.getUuid());
@@ -81,7 +82,7 @@ public class UserIndexerTest {
 
     underTest.indexOnStartup(new HashSet<>());
 
-    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
     assertThat(docs).hasSize(1);
     UserDoc doc = docs.get(0);
     assertThat(doc.uuid()).isEqualTo(user.getUuid());
@@ -96,7 +97,7 @@ public class UserIndexerTest {
 
     underTest.commitAndIndex(db.getSession(), user);
 
-    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
     assertThat(docs)
       .extracting(UserDoc::uuid)
       .containsExactlyInAnyOrder(user.getUuid())
@@ -115,7 +116,7 @@ public class UserIndexerTest {
 
     underTest.commitAndIndex(db.getSession(), user);
 
-    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
     assertThat(docs)
       .extracting(UserDoc::uuid, UserDoc::organizationUuids)
       .containsExactlyInAnyOrder(tuple(user.getUuid(), asList(organization1.getUuid(), organization2.getUuid())));
@@ -132,7 +133,7 @@ public class UserIndexerTest {
 
     underTest.commitAndIndex(db.getSession(), asList(user1, user2));
 
-    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
     assertThat(docs)
       .extracting(UserDoc::login, UserDoc::organizationUuids)
       .containsExactlyInAnyOrder(
index 3bed13177503fffd080302b5a3abd29663bfbed7..e18c49b422f3dba5b960e46b64f86ff66682c023 100644 (file)
@@ -21,8 +21,10 @@ package org.sonar.server.view.index;
 
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.NewIndex;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -37,10 +39,11 @@ public class ViewIndexDefinitionTest {
 
     assertThat(underTest.getIndices()).hasSize(1);
     NewIndex index = underTest.getIndices().get("views");
-    assertThat(index).isNotNull();
-    assertThat(index.getTypes().keySet()).containsOnly("view");
+    assertThat(index.getMainType())
+      .isEqualTo(IndexType.main(Index.simple("views"), "view"));
+    assertThat(index.getRelationsStream()).isEmpty();
 
-    assertThat(index.getSettings().get("index.number_of_shards")).isEqualTo("5");
-    assertThat(index.getSettings().get("index.number_of_replicas")).isEqualTo("0");
+    assertThat(index.getSetting("index.number_of_shards")).isEqualTo("5");
+    assertThat(index.getSetting("index.number_of_replicas")).isEqualTo("0");
   }
 }
index f82551ac90b3b52f0887f921f6d3816c555ef80e..e1ac8c4909491726b669ab8a8b96b49dbf3bb9e1 100644 (file)
@@ -27,7 +27,7 @@ import org.sonar.server.es.EsTester;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.view.index.ViewIndexDefinition.INDEX_TYPE_VIEW;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
 
 public class ViewIndexTest {
 
@@ -40,8 +40,8 @@ public class ViewIndexTest {
   public void find_all_view_uuids() {
     ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(singletonList("P1"));
     ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(singletonList("P2"));
-    es.putDocuments(INDEX_TYPE_VIEW, view1);
-    es.putDocuments(INDEX_TYPE_VIEW, view2);
+    es.putDocuments(TYPE_VIEW, view1);
+    es.putDocuments(TYPE_VIEW, view2);
 
     List<String> result = newArrayList(index.findAllViewUuids());
 
index 5d814b909b7f68430d58c502fa63ef7606280572..096b9fb661109d0e313bce139c51e177f4e4a6e1 100644 (file)
@@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.db.component.ComponentTesting.newProjectCopy;
-import static org.sonar.server.view.index.ViewIndexDefinition.INDEX_TYPE_VIEW;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
 
 public class ViewIndexerTest {
 
@@ -58,7 +58,7 @@ public class ViewIndexerTest {
   @Test
   public void index_nothing() {
     underTest.indexOnStartup(emptySet());
-    assertThat(es.countDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW)).isEqualTo(0L);
+    assertThat(es.countDocuments(TYPE_VIEW)).isEqualTo(0L);
   }
 
   @Test
@@ -67,7 +67,7 @@ public class ViewIndexerTest {
 
     underTest.indexOnStartup(emptySet());
 
-    List<ViewDoc> docs = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
     assertThat(docs).hasSize(4);
 
     Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
@@ -84,7 +84,7 @@ public class ViewIndexerTest {
 
     underTest.index("EFGH");
 
-    List<ViewDoc> docs = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
     assertThat(docs).hasSize(2);
 
     Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
@@ -97,7 +97,7 @@ public class ViewIndexerTest {
   public void index_view_doc() {
     underTest.index(new ViewDoc().setUuid("EFGH").setProjects(newArrayList("KLMN", "JKLM")));
 
-    List<ViewDoc> result = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
 
     assertThat(result).hasSize(1);
     ViewDoc view = result.get(0);
@@ -112,7 +112,7 @@ public class ViewIndexerTest {
     db.components().insertComponent(newProjectCopy("PC1", project, application));
 
     underTest.index(application.uuid());
-    List<ViewDoc> result = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
 
     assertThat(result).hasSize(1);
     ViewDoc resultApp = result.get(0);
@@ -127,7 +127,7 @@ public class ViewIndexerTest {
     db.components().insertComponent(newProjectCopy("PC1", project, application));
 
     underTest.indexOnStartup(emptySet());
-    List<ViewDoc> result = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
 
     assertThat(result).hasSize(1);
     ViewDoc resultApp = result.get(0);
@@ -153,7 +153,7 @@ public class ViewIndexerTest {
 
     underTest.index(applicationBranch1.uuid());
 
-    List<ViewDoc> result = es.getDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, ViewDoc.class);
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
     assertThat(result)
       .extracting(ViewDoc::uuid, ViewDoc::projects)
       .containsExactlyInAnyOrder(
@@ -165,16 +165,16 @@ public class ViewIndexerTest {
     ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(asList("P1"));
     ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(asList("P2", "P3", "P4"));
     ViewDoc view3 = new ViewDoc().setUuid("UUID3").setProjects(asList("P2", "P3", "P4"));
-    es.putDocuments(INDEX_TYPE_VIEW, view1);
-    es.putDocuments(INDEX_TYPE_VIEW, view2);
-    es.putDocuments(INDEX_TYPE_VIEW, view3);
+    es.putDocuments(TYPE_VIEW, view1);
+    es.putDocuments(TYPE_VIEW, view2);
+    es.putDocuments(TYPE_VIEW, view3);
 
-    assertThat(es.getDocumentFieldValues(INDEX_TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
+    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
       .containsOnly(view1.uuid(), view2.uuid(), view3.uuid());
 
     underTest.delete(dbSession, asList(view1.uuid(), view2.uuid()));
 
-    assertThat(es.getDocumentFieldValues(INDEX_TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
+    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
       .containsOnly(view3.uuid());
   }
 
index 72d1a0417930f9a3e0cec444f3c9e465e1c8071e..6b382ebe4523c028f94e2c6ec9a0fe30afdffb11 100644 (file)
@@ -61,9 +61,10 @@ import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_LA
 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;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 
 public class ComponentIndex {
 
@@ -82,7 +83,7 @@ public class ComponentIndex {
 
   public SearchIdResult<String> search(ComponentQuery query, SearchOptions searchOptions) {
     SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_COMPONENT)
+      .prepareSearch(TYPE_COMPONENT.getMainType())
       .setFetchSource(false)
       .setFrom(searchOptions.getOffset())
       .setSize(searchOptions.getLimit());
@@ -118,7 +119,7 @@ public class ComponentIndex {
     }
 
     SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_COMPONENT)
+      .prepareSearch(TYPE_COMPONENT.getMainType())
       .setQuery(createQuery(query, features))
       .addAggregation(createAggregation(query))
 
@@ -166,6 +167,7 @@ public class ComponentIndex {
 
   private QueryBuilder createQuery(SuggestionQuery query, ComponentTextSearchFeature... features) {
     BoolQueryBuilder esQuery = boolQuery();
+    esQuery.filter(termQuery(FIELD_INDEX_TYPE, TYPE_COMPONENT.getName()));
     esQuery.filter(authorizationTypeSupport.createQueryFilter());
     ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQuery.builder()
       .setQueryText(query.getQuery())
index 20f15f1c9038707b565c7e46a2a926cc9c9cdd08..c7f8dbbddd554fdca80f45bae452876fb7c305de 100644 (file)
@@ -53,7 +53,7 @@ import org.sonar.server.component.index.ComponentHitsPerQualifier;
 import org.sonar.server.component.index.ComponentIndex;
 import org.sonar.server.component.index.ComponentIndexResults;
 import org.sonar.server.component.index.SuggestionQuery;
-import org.sonar.server.es.DefaultIndexSettings;
+import org.sonar.server.es.newindex.DefaultIndexSettings;
 import org.sonar.server.favorite.FavoriteFinder;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Components.SuggestionsWsResponse;
@@ -70,7 +70,7 @@ import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.server.component.index.SuggestionQuery.DEFAULT_LIMIT;
-import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
+import static org.sonar.server.es.newindex.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.Common.Organization;
 import static org.sonarqube.ws.Components.SuggestionsWsResponse.newBuilder;
index c589c1861862d6596dbb45038c4d45344527caf1..7112f77b28a19021035619eefa5db853e845c4fc 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.es;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang.StringUtils;
@@ -36,10 +35,14 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.process.ProcessProperties;
-import org.sonar.server.es.IndexDefinition.Index;
 import org.sonar.server.es.metadata.EsDbCompatibility;
 import org.sonar.server.es.metadata.MetadataIndex;
 import org.sonar.server.es.metadata.MetadataIndexDefinition;
+import org.sonar.server.es.newindex.BuiltIndex;
+import org.sonar.server.es.newindex.NewIndex;
+
+import static org.sonar.server.es.metadata.MetadataIndexDefinition.DESCRIPTOR;
+import static org.sonar.server.es.metadata.MetadataIndexDefinition.TYPE_METADATA;
 
 /**
  * Creates/deletes all indices in Elasticsearch during server startup.
@@ -69,26 +72,29 @@ public class IndexCreator implements Startable {
   @Override
   public void start() {
     // create the "metadata" index first
-    if (!client.prepareIndicesExist(MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex()).get().isExists()) {
+    IndexType.IndexMainType metadataMainType = TYPE_METADATA;
+    if (!client.prepareIndicesExist(metadataMainType.getIndex()).get().isExists()) {
       IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
       metadataIndexDefinition.define(context);
       NewIndex index = context.getIndices().values().iterator().next();
-      createIndex(new Index(index), false);
+      createIndex(index.build(), false);
     }
 
     checkDbCompatibility(definitions.getIndices().values());
 
     // create indices that do not exist or that have a new definition (different mapping, cluster enabled, ...)
-    for (Index index : definitions.getIndices().values()) {
-      boolean exists = client.prepareIndicesExist(index.getName()).get().isExists();
-      if (exists && !index.getName().equals(MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex()) && hasDefinitionChange(index)) {
-        verifyNotBlueGreenDeployment(index.getName());
-        LOGGER.info("Delete Elasticsearch index {} (structure changed)", index.getName());
-        deleteIndex(index.getName());
+    for (BuiltIndex<?> builtIndex : definitions.getIndices().values()) {
+      Index index = builtIndex.getMainType().getIndex();
+      String indexName = index.getName();
+      boolean exists = client.prepareIndicesExist(index).get().isExists();
+      if (exists && !builtIndex.getMainType().equals(metadataMainType) && hasDefinitionChange(builtIndex)) {
+        verifyNotBlueGreenDeployment(indexName);
+        LOGGER.info("Delete Elasticsearch index {} (structure changed)", indexName);
+        deleteIndex(indexName);
         exists = false;
       }
       if (!exists) {
-        createIndex(index, true);
+        createIndex(builtIndex, true);
       }
     }
   }
@@ -104,18 +110,18 @@ public class IndexCreator implements Startable {
     // nothing to do
   }
 
-  private void createIndex(Index index, boolean useMetadata) {
+  private void createIndex(BuiltIndex<?> builtIndex, boolean useMetadata) {
+    Index index = builtIndex.getMainType().getIndex();
     LOGGER.info(String.format("Create index %s", index.getName()));
     Settings.Builder settings = Settings.builder();
-    settings.put(index.getSettings());
+    settings.put(builtIndex.getSettings());
     if (useMetadata) {
-      metadataIndex.setHash(index.getName(), IndexDefinitionHash.of(index));
-      for (IndexDefinition.Type type : index.getTypes().values()) {
-        metadataIndex.setInitialized(new IndexType(index.getName(), type.getName()), false);
-      }
+      metadataIndex.setHash(index, IndexDefinitionHash.of(builtIndex));
+      metadataIndex.setInitialized(builtIndex.getMainType(), false);
+      builtIndex.getRelationTypes().forEach(relationType -> metadataIndex.setInitialized(relationType, false));
     }
     CreateIndexResponse indexResponse = client
-      .prepareCreate(index.getName())
+      .prepareCreate(index)
       .setSettings(settings)
       .get();
     if (!indexResponse.isAcknowledged()) {
@@ -124,15 +130,13 @@ public class IndexCreator implements Startable {
     client.waitForStatus(ClusterHealthStatus.YELLOW);
 
     // create types
-    for (Map.Entry<String, IndexDefinition.Type> entry : index.getTypes().entrySet()) {
-      LOGGER.info(String.format("Create type %s/%s", index.getName(), entry.getKey()));
-      PutMappingResponse mappingResponse = client.preparePutMapping(index.getName())
-        .setType(entry.getKey())
-        .setSource(entry.getValue().getAttributes())
-        .get();
-      if (!mappingResponse.isAcknowledged()) {
-        throw new IllegalStateException("Failed to create type " + entry.getKey());
-      }
+    LOGGER.info("Create type {}", builtIndex.getMainType().format());
+    PutMappingResponse mappingResponse = client.preparePutMapping(index)
+      .setType(builtIndex.getMainType().getType())
+      .setSource(builtIndex.getAttributes())
+      .get();
+    if (!mappingResponse.isAcknowledged()) {
+      throw new IllegalStateException("Failed to create type " + builtIndex.getMainType().getType());
     }
     client.waitForStatus(ClusterHealthStatus.YELLOW);
   }
@@ -141,15 +145,15 @@ public class IndexCreator implements Startable {
     client.nativeClient().admin().indices().prepareDelete(indexName).get();
   }
 
-  private boolean hasDefinitionChange(Index index) {
-    return metadataIndex.getHash(index.getName())
+  private boolean hasDefinitionChange(BuiltIndex<?> index) {
+    return metadataIndex.getHash(index.getMainType().getIndex())
       .map(hash -> {
         String defHash = IndexDefinitionHash.of(index);
         return !StringUtils.equals(hash, defHash);
       }).orElse(true);
   }
 
-  private void checkDbCompatibility(Collection<Index> definitions) {
+  private void checkDbCompatibility(Collection<BuiltIndex> definitions) {
     List<String> existingIndices = loadExistingIndicesExceptMetadata(definitions);
     if (!existingIndices.isEmpty()) {
       boolean delete = false;
@@ -164,11 +168,13 @@ public class IndexCreator implements Startable {
     esDbCompatibility.markAsCompatible();
   }
 
-  private List<String> loadExistingIndicesExceptMetadata(Collection<Index> definitions) {
-    Set<String> definedNames = definitions.stream().map(IndexDefinition.Index::getName).collect(Collectors.toSet());
+  private List<String> loadExistingIndicesExceptMetadata(Collection<BuiltIndex> definitions) {
+    Set<String> definedNames = definitions.stream()
+      .map(t -> t.getMainType().getIndex().getName())
+      .collect(Collectors.toSet());
     return Arrays.stream(client.nativeClient().admin().indices().prepareGetIndex().get().getIndices())
       .filter(definedNames::contains)
-      .filter(index -> !MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex().equals(index))
+      .filter(index -> !DESCRIPTOR.getName().equals(index))
       .collect(Collectors.toList());
   }
 }
index 4439ca5d49488e860dee17f38e82d71ae1666e72..22b8b59b0c3f33aa8445800f02b9235efa37fa4f 100644 (file)
@@ -24,6 +24,8 @@ import java.util.Map;
 import org.picocontainer.Startable;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ServerSide;
+import org.sonar.server.es.newindex.BuiltIndex;
+import org.sonar.server.es.newindex.NewIndex;
 
 /**
  * This class collects definitions of all Elasticsearch indices during server startup
@@ -31,7 +33,7 @@ import org.sonar.api.server.ServerSide;
 @ServerSide
 public class IndexDefinitions implements Startable {
 
-  private final Map<String, IndexDefinition.Index> byKey = Maps.newHashMap();
+  private final Map<String, BuiltIndex> byKey = Maps.newHashMap();
   private final IndexDefinition[] defs;
   private final Configuration config;
 
@@ -40,7 +42,7 @@ public class IndexDefinitions implements Startable {
     this.config = config;
   }
 
-  public Map<String, IndexDefinition.Index> getIndices() {
+  public Map<String, BuiltIndex> getIndices() {
     return byKey;
   }
 
@@ -55,7 +57,7 @@ public class IndexDefinitions implements Startable {
       }
 
       for (Map.Entry<String, NewIndex> entry : context.getIndices().entrySet()) {
-        byKey.put(entry.getKey(), new IndexDefinition.Index(entry.getValue()));
+        byKey.put(entry.getKey(), entry.getValue().build());
       }
     }
   }
index cea201376afe3a165e5689709c96773c3b48203e..be64ce0a057b712c35c2a026607425e9f6035082 100644 (file)
@@ -72,12 +72,13 @@ public class IndexerStartupTask {
   }
 
   private Set<IndexType> getUninitializedTypes(StartupIndexer indexer) {
-    return indexer.getIndexTypes().stream().filter(indexType -> !metadataIndex.getInitialized(indexType)).collect(toSet());
+    return indexer.getIndexTypes().stream()
+      .filter(indexType -> !metadataIndex.getInitialized(indexType))
+      .collect(toSet());
   }
 
   private void setInitialized(IndexType indexType) {
-    String index = indexType.getIndex();
-    waitForIndexYellow(index);
+    waitForIndexYellow(indexType.getMainType().getIndex().getName());
     metadataIndex.setInitialized(indexType, true);
   }
 
index 9b38225af186c298419e3ff8218a36e9e4991993..f700fc635f51aa0761212cab7dc1188848a91bce 100644 (file)
@@ -64,7 +64,7 @@ public class RecoveryIndexer implements Startable {
   private final System2 system2;
   private final Configuration config;
   private final DbClient dbClient;
-  private final Map<IndexType, ResilientIndexer> indexersByType;
+  private final Map<String, Indexer> indexersByType;
   private final long minAgeInMs;
   private final long loopLimit;
 
@@ -73,11 +73,29 @@ public class RecoveryIndexer implements Startable {
     this.config = config;
     this.dbClient = dbClient;
     this.indexersByType = new HashMap<>();
-    Arrays.stream(indexers).forEach(i -> i.getIndexTypes().forEach(indexType -> indexersByType.put(indexType, i)));
+    Arrays.stream(indexers).forEach(i -> i.getIndexTypes().forEach(indexType -> indexersByType.put(indexType.format(), new Indexer(indexType, i))));
     this.minAgeInMs = getSetting(PROPERTY_MIN_AGE, DEFAULT_MIN_AGE_IN_MS);
     this.loopLimit = getSetting(PROPERTY_LOOP_LIMIT, DEFAULT_LOOP_LIMIT);
   }
 
+  private static final class Indexer {
+    private final IndexType indexType;
+    private final ResilientIndexer delegate;
+
+    private Indexer(IndexType indexType, ResilientIndexer delegate) {
+      this.indexType = indexType;
+      this.delegate = delegate;
+    }
+
+    public IndexType getIndexType() {
+      return indexType;
+    }
+
+    public ResilientIndexer getDelegate() {
+      return delegate;
+    }
+  }
+
   @Override
   public void start() {
     long delayInMs = getSetting(PROPERTY_DELAY, DEFAULT_DELAY_IN_MS);
@@ -116,7 +134,7 @@ public class RecoveryIndexer implements Startable {
       while (!items.isEmpty()) {
         IndexingResult loopResult = new IndexingResult();
 
-        groupItemsByType(items).asMap().forEach((type, typeItems) -> loopResult.add(doIndex(dbSession, type, typeItems)));
+        groupItemsByDocType(items).asMap().forEach((type, typeItems) -> loopResult.add(doIndex(dbSession, type, typeItems)));
         result.add(loopResult);
 
         if (loopResult.getSuccessRatio() <= CIRCUIT_BREAKER_IN_PERCENT) {
@@ -138,19 +156,19 @@ public class RecoveryIndexer implements Startable {
     }
   }
 
-  private IndexingResult doIndex(DbSession dbSession, IndexType type, Collection<EsQueueDto> typeItems) {
-    LOGGER.trace(LOG_PREFIX + "processing {} {}", typeItems.size(), type);
+  private IndexingResult doIndex(DbSession dbSession, String docType, Collection<EsQueueDto> typeItems) {
+    LOGGER.trace(LOG_PREFIX + "processing {} [{}]", typeItems.size(), docType);
 
-    ResilientIndexer indexer = indexersByType.get(type);
+    Indexer indexer = indexersByType.get(docType);
     if (indexer == null) {
-      LOGGER.error(LOG_PREFIX + "ignore {} items with unsupported type {}", typeItems.size(), type);
+      LOGGER.error(LOG_PREFIX + "ignore {} items with unsupported type [{}]", typeItems.size(), docType);
       return new IndexingResult();
     }
-    return indexer.index(dbSession, typeItems);
+    return indexer.delegate.index(dbSession, typeItems);
   }
 
-  private static ListMultimap<IndexType, EsQueueDto> groupItemsByType(Collection<EsQueueDto> items) {
-    return items.stream().collect(MoreCollectors.index(i -> IndexType.parse(i.getDocType())));
+  private static ListMultimap<String, EsQueueDto> groupItemsByDocType(Collection<EsQueueDto> items) {
+    return items.stream().collect(MoreCollectors.index(EsQueueDto::getDocType));
   }
 
   private long getSetting(String key, long defaultValue) {
index 631bc02d99895985dfaf7cccf5dfcd1702f0fb10..8e5976d5f4d99b4a60217fe6bb2f06f6a961953f 100644 (file)
@@ -75,10 +75,12 @@ 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.IndexType;
 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.AuthorizationDoc;
 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.view.index.ViewIndexDefinition;
@@ -96,6 +98,7 @@ 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.es.IndexType.FIELD_INDEX_TYPE;
 import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME;
 import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES;
 import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR;
@@ -143,11 +146,12 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVE
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TYPE;
-import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
 import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_INSECURE_INTERACTION;
 import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_POROUS_DEFENSES;
 import static org.sonar.server.issue.index.SecurityStandardHelper.SANS_TOP_25_RISKY_RESOURCE;
 import static org.sonar.server.issue.index.SecurityStandardHelper.UNKNOWN_STANDARD;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_AUTHORS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
@@ -277,7 +281,7 @@ public class IssueIndex {
   }
 
   public SearchResponse search(IssueQuery query, SearchOptions options) {
-    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_TYPE_ISSUE);
+    SearchRequestBuilder requestBuilder = client.prepareSearch(TYPE_ISSUE.getMainType());
 
     configureSorting(query, requestBuilder);
     configurePagination(options, requestBuilder);
@@ -313,7 +317,7 @@ public class IssueIndex {
   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()]));
+      requestBuilder.setRouting(uuids.stream().map(AuthorizationDoc::idOf).toArray(String[]::new));
     }
   }
 
@@ -323,6 +327,7 @@ public class IssueIndex {
 
   private Map<String, QueryBuilder> createFilters(IssueQuery query) {
     Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__indexType", termQuery(FIELD_INDEX_TYPE, TYPE_ISSUE.getName()));
     filters.put("__authorization", createAuthorizationFilter());
 
     // Issue is assigned Filter
@@ -425,10 +430,11 @@ public class IssueIndex {
 
     BoolQueryBuilder viewsFilter = boolQuery();
     for (String viewUuid : viewUuids) {
+      IndexType.IndexMainType mainType = TYPE_VIEW;
       viewsFilter.should(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
         new TermsLookup(
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
+          mainType.getIndex().getName(),
+          mainType.getType(),
           viewUuid,
           ViewIndexDefinition.FIELD_PROJECTS)));
     }
@@ -635,7 +641,7 @@ public class IssueIndex {
   private OptionalLong getMinCreatedAt(Map<String, QueryBuilder> filters, QueryBuilder esQuery) {
     String facetNameAndField = CREATED_AT.getFieldName();
     SearchRequestBuilder esRequest = client
-      .prepareSearch(INDEX_TYPE_ISSUE)
+      .prepareSearch(TYPE_ISSUE.getMainType())
       .setSize(0);
     BoolQueryBuilder esFilter = boolQuery();
     filters.values().stream().filter(Objects::nonNull).forEach(esFilter::must);
@@ -710,7 +716,7 @@ public class IssueIndex {
 
   private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int size) {
     SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_ISSUE)
+      .prepareSearch(TYPE_ISSUE.getMainType())
       // Avoids returning search hits
       .setSize(0);
 
@@ -745,7 +751,7 @@ public class IssueIndex {
     if (projectUuids.isEmpty()) {
       return Collections.emptyList();
     }
-    SearchRequestBuilder request = client.prepareSearch(INDEX_TYPE_ISSUE)
+    SearchRequestBuilder request = client.prepareSearch(TYPE_ISSUE.getMainType())
       .setQuery(
         boolQuery()
           .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION))
@@ -787,8 +793,8 @@ public class IssueIndex {
       return Collections.emptyList();
     }
 
-    SearchRequestBuilder request = client.prepareSearch(INDEX_TYPE_ISSUE)
-      .setRouting(projectUuid)
+    SearchRequestBuilder request = client.prepareSearch(TYPE_ISSUE.getMainType())
+      .setRouting(AuthorizationDoc.idOf(projectUuid))
       .setQuery(
         boolQuery()
           .must(termsQuery(FIELD_ISSUE_BRANCH_UUID, branchUuids))
@@ -912,16 +918,17 @@ public class IssueIndex {
   private SearchRequestBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid, boolean isViewOrApp) {
     BoolQueryBuilder componentFilter = boolQuery();
     if (isViewOrApp) {
+      IndexType.IndexMainType mainType = TYPE_VIEW;
       componentFilter.filter(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
         new TermsLookup(
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getIndex(),
-          ViewIndexDefinition.INDEX_TYPE_VIEW.getType(),
+          mainType.getIndex().getName(),
+          mainType.getType(),
           projectUuid,
           ViewIndexDefinition.FIELD_PROJECTS)));
     } else {
       componentFilter.filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectUuid));
     }
-    return client.prepareSearch(INDEX_TYPE_ISSUE)
+    return client.prepareSearch(TYPE_ISSUE.getMainType())
       .setQuery(
         componentFilter
           .filter(termsQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name(), RuleType.VULNERABILITY.name()))
index 4c87f95d6463dc7738c726b5ada13bf6761fa9d8..2839c4acdf2375029fc5ef5b5b6b601ec513b1bd 100644 (file)
@@ -51,7 +51,7 @@ 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.newindex.DefaultIndexSettingsElement;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
@@ -86,6 +86,7 @@ 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.es.IndexType.FIELD_INDEX_TYPE;
 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;
@@ -96,7 +97,7 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIEL
 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.ProjectMeasuresIndexDefinition.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;
@@ -164,7 +165,7 @@ public class ProjectMeasuresIndex {
 
   public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
     SearchRequestBuilder requestBuilder = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
       .setFetchSource(false)
       .setFrom(searchOptions.getOffset())
       .setSize(searchOptions.getLimit());
@@ -181,7 +182,7 @@ public class ProjectMeasuresIndex {
 
   public ProjectMeasuresStatistics searchTelemetryStatistics() {
     SearchRequestBuilder request = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
       .setFetchSource(false)
       .setSize(0);
 
@@ -344,6 +345,7 @@ public class ProjectMeasuresIndex {
 
   private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
     Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__indexType", termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()));
     filters.put("__authorization", authorizationTypeSupport.createQueryFilter());
     Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
     query.getMetricCriteria().forEach(metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
@@ -429,7 +431,7 @@ public class ProjectMeasuresIndex {
     }
 
     SearchRequestBuilder searchQuery = client
-      .prepareSearch(INDEX_TYPE_PROJECT_MEASURES)
+      .prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
       .setQuery(authorizationTypeSupport.createQueryFilter())
       .setFetchSource(false)
       .setSize(0)
index 022bc73247a6033a7d0d29e6c12c15330f4c7bea..63a487a0a37483b48ed4f47635889f7dbe4232d8 100644 (file)
@@ -27,13 +27,13 @@ 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 org.sonar.server.es.newindex.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.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
 
index f58cf64be9217be309f991b631091480cde35c5e..f509a15b099f1d714d9c02f5b32541385eb054ba 100644 (file)
 package org.sonar.server.permission.index;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 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;
@@ -43,7 +43,6 @@ 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
@@ -54,7 +53,7 @@ public class PermissionIndexer implements ProjectIndexer {
   private final DbClient dbClient;
   private final EsClient esClient;
   private final Collection<AuthorizationScope> authorizationScopes;
-  private final Set<IndexType> indexTypes;
+  private final Map<String, IndexType> indexTypeByFormat;
 
   public PermissionIndexer(DbClient dbClient, EsClient esClient, NeedAuthorizationIndexer... needAuthorizationIndexers) {
     this(dbClient, esClient, Arrays.stream(needAuthorizationIndexers)
@@ -67,14 +66,14 @@ public class PermissionIndexer implements ProjectIndexer {
     this.dbClient = dbClient;
     this.esClient = esClient;
     this.authorizationScopes = authorizationScopes;
-    this.indexTypes = authorizationScopes.stream()
+    this.indexTypeByFormat = authorizationScopes.stream()
       .map(AuthorizationScope::getIndexType)
-      .collect(toSet(authorizationScopes.size()));
+      .collect(MoreCollectors.uniqueIndex(IndexType.IndexMainType::format, t -> t, authorizationScopes.size()));
   }
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return indexTypes;
+    return ImmutableSet.copyOf(indexTypeByFormat.values());
   }
 
   @Override
@@ -116,8 +115,8 @@ public class PermissionIndexer implements ProjectIndexer {
   }
 
   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)))
+    List<EsQueueDto> items = indexTypeByFormat.values().stream()
+      .flatMap(indexType -> projectUuids.stream().map(projectUuid -> EsQueueDto.create(indexType.format(), AuthorizationDoc.idOf(projectUuid), null, projectUuid)))
       .collect(toArrayList());
 
     dbClient.esQueueDao().insert(dbSession, items);
@@ -138,7 +137,7 @@ public class PermissionIndexer implements ProjectIndexer {
 
       authorizations.stream()
         .filter(scope.getProjectPredicate())
-        .map(dto -> newIndexRequest(dto, indexType))
+        .map(dto -> AuthorizationDoc.fromDto(indexType, dto).toIndexRequest())
         .forEach(bulkIndexer::add);
 
       bulkIndexer.stop();
@@ -152,8 +151,8 @@ public class PermissionIndexer implements ProjectIndexer {
     List<BulkIndexer> bulkIndexers = items.stream()
       .map(EsQueueDto::getDocType)
       .distinct()
-      .map(IndexType::parse)
-      .filter(indexTypes::contains)
+      .map(indexTypeByFormat::get)
+      .filter(Objects::nonNull)
       .map(indexType -> new BulkIndexer(esClient, indexType, Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items)))
       .collect(Collectors.toList());
 
@@ -164,37 +163,26 @@ public class PermissionIndexer implements ProjectIndexer {
     bulkIndexers.forEach(BulkIndexer::start);
 
     PermissionIndexerDao permissionIndexerDao = new PermissionIndexerDao();
-    Set<String> remainingProjectUuids = items.stream().map(EsQueueDto::getDocId).collect(MoreCollectors.toHashSet());
+    Set<String> remainingProjectUuids = items.stream().map(EsQueueDto::getDocId)
+      .map(AuthorizationDoc::projectUuidOf)
+      .collect(MoreCollectors.toHashSet());
     permissionIndexerDao.selectByUuids(dbClient, dbSession, remainingProjectUuids).forEach(p -> {
       remainingProjectUuids.remove(p.getProjectUuid());
-      bulkIndexers.forEach(bi -> bi.add(newIndexRequest(p, bi.getIndexType())));
+      bulkIndexers.forEach(bi -> bi.add(AuthorizationDoc.fromDto(bi.getIndexType(), p).toIndexRequest()));
     });
 
     // 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)));
+    remainingProjectUuids.forEach(projectUuid -> bulkIndexers.forEach(bi -> {
+      String authorizationDocId = AuthorizationDoc.idOf(projectUuid);
+      bi.addDeletion(bi.getIndexType(), authorizationDocId, authorizationDocId);
+    }));
 
     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()));
index 0a9024956a89511ca6848fcc6ffe8e263d738f2a..e4fc7c2bce65a88bf0556c417d60bf28a9d227ea 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.db.version.SqTables;
 import org.sonar.server.component.index.ComponentIndexDefinition;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexType;
 import org.sonar.server.issue.index.IssueIndexDefinition;
 import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
@@ -96,7 +97,8 @@ public class BackendCleanup {
       esClient.prepareClearCache().get();
 
       for (String index : esClient.prepareState().get().getState().getMetaData().getConcreteAllIndices()) {
-        clearIndex(new IndexType(index, index));
+        /*under the hood, type is not used to perform clearIndex, so it's ok it does not match any existing index*/
+        clearIndex(Index.simple(index));
       }
     } catch (Exception e) {
       throw new IllegalStateException("Unable to clear indexes", e);
@@ -121,10 +123,10 @@ public class BackendCleanup {
       throw new IllegalStateException("Fail to reset data", e);
     }
 
-    clearIndex(IssueIndexDefinition.INDEX_TYPE_ISSUE);
-    clearIndex(ViewIndexDefinition.INDEX_TYPE_VIEW);
-    clearIndex(ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES);
-    clearIndex(ComponentIndexDefinition.INDEX_TYPE_COMPONENT);
+    clearIndex(IssueIndexDefinition.DESCRIPTOR);
+    clearIndex(ViewIndexDefinition.DESCRIPTOR);
+    clearIndex(ProjectMeasuresIndexDefinition.DESCRIPTOR);
+    clearIndex(ComponentIndexDefinition.DESCRIPTOR);
   }
 
   private void truncateAnalysisTables(Connection connection) throws SQLException {
@@ -166,8 +168,8 @@ public class BackendCleanup {
   /**
    * Completely remove a index with all types
    */
-  public void clearIndex(IndexType indexType) {
-    BulkIndexer.delete(esClient, indexType, esClient.prepareSearch(indexType.getIndex()).setQuery(matchAllQuery()));
+  public void clearIndex(Index index) {
+    BulkIndexer.delete(esClient, IndexType.main(index, index.getName()), esClient.prepareSearch(index).setQuery(matchAllQuery()));
   }
 
   @FunctionalInterface
index 075090224228f7ced14937f48abe8092b5ec798f..1575ac315fba624dcfce62f06e081343f58cc288 100644 (file)
@@ -32,16 +32,20 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.es.IndexType.IndexMainType;
 import org.sonar.server.es.metadata.MetadataIndex;
 import org.sonar.server.es.metadata.MetadataIndexDefinition;
+import org.sonar.server.es.newindex.NewRegularIndex;
+import org.sonar.server.es.newindex.SettingsConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.IndexType.main;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 public class IndexCreatorTest {
 
-  private static final NewIndex.SettingsConfiguration SETTINGS_CONFIGURATION = newBuilder(new MapSettings().asConfig()).build();
+  private static final SettingsConfiguration SETTINGS_CONFIGURATION = newBuilder(new MapSettings().asConfig()).build();
   private static final String LOG_DB_VENDOR_CHANGED = "Delete Elasticsearch indices (DB vendor changed)";
   private static final String LOG_DB_SCHEMA_CHANGED = "Delete Elasticsearch indices (DB schema changed)";
 
@@ -80,15 +84,19 @@ public class IndexCreatorTest {
 
   @Test
   public void mark_all_non_existing_index_types_as_uninitialized() {
+    Index fakesIndex = Index.simple("fakes");
+    Index fakersIndex = Index.simple("fakers");
     startNewCreator(context -> {
-      NewIndex i = context.create("fakes", SETTINGS_CONFIGURATION);
-      i.createType("t1");
-      i.createType("t2");
+      context.create(fakesIndex, SETTINGS_CONFIGURATION)
+        .createTypeMapping(IndexType.main(fakesIndex, "fake"));
+      context.create(fakersIndex, SETTINGS_CONFIGURATION)
+        .createTypeMapping(IndexType.main(fakersIndex, "faker"));
     });
 
-    assertThat(metadataIndex.getHash("fakes")).isNotEmpty();
-    assertThat(metadataIndex.getInitialized(new IndexType("fakes", "t1"))).isFalse();
-    assertThat(metadataIndex.getInitialized(new IndexType("fakes", "t2"))).isFalse();
+    assertThat(metadataIndex.getHash(fakesIndex)).isNotEmpty();
+    assertThat(metadataIndex.getHash(fakersIndex)).isNotEmpty();
+    assertThat(metadataIndex.getInitialized(main(fakesIndex, "fake"))).isFalse();
+    assertThat(metadataIndex.getInitialized(main(fakersIndex, "faker"))).isFalse();
   }
 
   @Test
@@ -96,7 +104,7 @@ public class IndexCreatorTest {
     // v1
     startNewCreator(new FakeIndexDefinition());
 
-    IndexType fakeIndexType = new IndexType("fakes", "fake");
+    IndexMainType fakeIndexType = main(Index.simple("fakes"), "fake");
     String id = "1";
     es.client().prepareIndex(fakeIndexType).setId(id).setSource(new FakeDoc().getFields()).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
     assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isTrue();
@@ -135,7 +143,7 @@ public class IndexCreatorTest {
   public void do_not_recreate_index_on_unchanged_definition() {
     // v1
     startNewCreator(new FakeIndexDefinition());
-    IndexType fakeIndexType = new IndexType("fakes", "fake");
+    IndexMainType fakeIndexType = main(Index.simple("fakes"), "fake");
     String id = "1";
     es.client().prepareIndex(fakeIndexType).setId(id).setSource(new FakeDoc().getFields()).setRefreshPolicy(IMMEDIATE).get();
     assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isTrue();
@@ -225,25 +233,27 @@ public class IndexCreatorTest {
 
   private static class FakeIndexDefinition implements IndexDefinition {
 
-    private static final IndexType INDEX_TYPE = new IndexType("fakes", "fake");
+    private static final IndexMainType INDEX_TYPE = main(Index.simple("fakes"), "fake");
 
     @Override
     public void define(IndexDefinitionContext context) {
-      NewIndex index = context.create("fakes", SETTINGS_CONFIGURATION);
-      NewIndex.NewIndexType mapping = index.createType("fake");
-      mapping.keywordFieldBuilder("key").build();
-      mapping.createDateTimeField("updatedAt");
+      Index index = Index.simple("fakes");
+      NewRegularIndex newIndex = context.create(index, SETTINGS_CONFIGURATION);
+      newIndex.createTypeMapping(IndexType.main(index, "fake"))
+        .keywordFieldBuilder("key").build()
+        .createDateTimeField("updatedAt");
     }
   }
   private static class FakeIndexDefinitionV2 implements IndexDefinition {
 
     @Override
     public void define(IndexDefinitionContext context) {
-      NewIndex index = context.create("fakes", SETTINGS_CONFIGURATION);
-      NewIndex.NewIndexType mapping = index.createType("fake");
-      mapping.keywordFieldBuilder("key").build();
-      mapping.createDateTimeField("updatedAt");
-      mapping.createIntegerField("newField");
+      Index index = Index.simple("fakes");
+      NewRegularIndex newIndex = context.create(index, SETTINGS_CONFIGURATION);
+      newIndex.createTypeMapping(IndexType.main(index, "fake"))
+        .keywordFieldBuilder("key").build()
+        .createDateTimeField("updatedAt")
+        .createIntegerField("newField");
     }
   }
 }
index 129cfd003eef252e7df31572d4c035c27cd7fd98..b9b17c31ee274baa3359d9141eb088443cd30bb0 100644 (file)
@@ -26,13 +26,14 @@ import org.junit.Test;
 import org.mockito.Mockito;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.server.es.metadata.MetadataIndex;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.sonar.server.es.FakeIndexDefinition.INDEX_TYPE_FAKE;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
 
 public class IndexerStartupTaskTest {
 
@@ -46,31 +47,31 @@ public class IndexerStartupTaskTest {
 
   @Before
   public void setUp() throws Exception {
-    doReturn(ImmutableSet.of(INDEX_TYPE_FAKE)).when(indexer).getIndexTypes();
+    doReturn(ImmutableSet.of(TYPE_FAKE)).when(indexer).getIndexTypes();
   }
 
   @Test
   public void index_if_not_initialized() {
-    doReturn(false).when(metadataIndex).getInitialized(INDEX_TYPE_FAKE);
+    doReturn(false).when(metadataIndex).getInitialized(TYPE_FAKE);
 
     underTest.execute();
 
     verify(indexer).getIndexTypes();
-    verify(indexer).indexOnStartup(Mockito.eq(ImmutableSet.of(INDEX_TYPE_FAKE)));
+    verify(indexer).indexOnStartup(Mockito.eq(ImmutableSet.of(TYPE_FAKE)));
   }
 
   @Test
   public void set_initialized_after_indexation() {
-    doReturn(false).when(metadataIndex).getInitialized(INDEX_TYPE_FAKE);
+    doReturn(false).when(metadataIndex).getInitialized(TYPE_FAKE);
 
     underTest.execute();
 
-    verify(metadataIndex).setInitialized(eq(INDEX_TYPE_FAKE), eq(true));
+    verify(metadataIndex).setInitialized(eq(TYPE_FAKE), eq(true));
   }
 
   @Test
   public void do_not_index_if_already_initialized() {
-    doReturn(true).when(metadataIndex).getInitialized(INDEX_TYPE_FAKE);
+    doReturn(true).when(metadataIndex).getInitialized(TYPE_FAKE);
 
     underTest.execute();
 
@@ -81,7 +82,7 @@ public class IndexerStartupTaskTest {
   @Test
   public void do_not_index_if_indexes_are_disabled() {
     settings.setProperty("sonar.internal.es.disableIndexes", "true");
-    es.putDocuments(INDEX_TYPE_FAKE, new FakeDoc());
+    es.putDocuments(TYPE_FAKE, new FakeDoc());
 
     underTest.execute();
 
index bc84c56724a313acf3d87f06f561db9b69d53427..8d82b2c5bd722df8ee4d1cfc856eb2536c324c0a 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.server.es;
 
 import java.util.Iterator;
-import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
@@ -29,39 +28,39 @@ import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.server.platform.db.migration.es.MigrationEsClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
 public class MigrationEsClientImplTest {
   @Rule
   public LogTester logTester = new LogTester();
   @Rule
   public EsTester es = EsTester.createCustom(
-    new SimpleIndexDefinition("a"),
-    new SimpleIndexDefinition("b"),
-    new SimpleIndexDefinition("c"));
+    new SimpleIndexDefinition("as"),
+    new SimpleIndexDefinition("bs"),
+    new SimpleIndexDefinition("cs"));
 
   private MigrationEsClient underTest = new MigrationEsClientImpl(es.client());
 
   @Test
   public void delete_existing_index() {
-    underTest.deleteIndexes("a");
+    underTest.deleteIndexes("as");
 
     assertThat(loadExistingIndices())
-      .doesNotContain("a")
-      .contains("b", "c");
+      .doesNotContain("as")
+      .contains("bs", "cs");
     assertThat(logTester.logs(LoggerLevel.INFO))
-      .contains("Drop Elasticsearch index [a]");
+      .contains("Drop Elasticsearch index [as]");
   }
 
   @Test
   public void ignore_indices_that_do_not_exist() {
-    underTest.deleteIndexes("a", "xxx", "c");
+    underTest.deleteIndexes("as", "xxx", "cs");
 
     assertThat(loadExistingIndices())
-      .doesNotContain("a", "c")
-      .contains("b");
+      .doesNotContain("as", "cs")
+      .contains("bs");
     assertThat(logTester.logs(LoggerLevel.INFO))
-      .contains("Drop Elasticsearch index [a]", "Drop Elasticsearch index [c]")
+      .contains("Drop Elasticsearch index [as]", "Drop Elasticsearch index [cs]")
       .doesNotContain("Drop Elasticsearch index [xxx]");
   }
 
@@ -78,9 +77,11 @@ public class MigrationEsClientImplTest {
 
     @Override
     public void define(IndexDefinitionContext context) {
-      NewIndex index = context.create(indexName, newBuilder(new MapSettings().asConfig()).build());
-      index.getSettings().put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0);
-      index.getSettings().put("index.refresh_interval", "-1");
+      IndexType.IndexMainType mainType = IndexType.main(Index.simple(indexName), indexName.substring(1));
+      context.create(
+        mainType.getIndex(),
+        newBuilder(new MapSettings().asConfig()).build())
+        .createTypeMapping(mainType);
     }
   }
 }
index b0cdd01d7c46fd0af344feea070b1d5a58d81281..cad24375750a8a79733389a13f7d5985685a10a3 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.es.IndexType.IndexMainType;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.user.index.UserIndexer;
 
@@ -57,7 +58,7 @@ import static org.sonar.api.utils.log.LoggerLevel.TRACE;
 public class RecoveryIndexerTest {
 
   private static final long PAST = 1_000L;
-  private static final IndexType FOO_TYPE = new IndexType("foos", "foo");
+  private static final IndexMainType FOO_TYPE = IndexType.main(Index.simple("foos"), "foo");
 
   private TestSystem2 system2 = new TestSystem2().setNow(PAST);
   private MapSettings emptySettings = new MapSettings();
@@ -114,13 +115,12 @@ public class RecoveryIndexerTest {
 
   @Test
   public void successfully_recover_indexing_requests() {
-    IndexType type1 = new IndexType("foos", "foo");
-    EsQueueDto item1a = insertItem(type1, "f1");
-    EsQueueDto item1b = insertItem(type1, "f2");
-    IndexType type2 = new IndexType("bars", "bar");
+    EsQueueDto item1a = insertItem(FOO_TYPE, "f1");
+    EsQueueDto item1b = insertItem(FOO_TYPE, "f2");
+    IndexMainType type2 = IndexType.main(Index.simple("bars"), "bar");
     EsQueueDto item2 = insertItem(type2, "b1");
 
-    SuccessfulFakeIndexer indexer1 = new SuccessfulFakeIndexer(type1);
+    SuccessfulFakeIndexer indexer1 = new SuccessfulFakeIndexer(FOO_TYPE);
     SuccessfulFakeIndexer indexer2 = new SuccessfulFakeIndexer(type2);
     advanceInTime();
 
@@ -221,7 +221,7 @@ public class RecoveryIndexerTest {
   public void unsupported_types_are_kept_in_queue_for_manual_fix_operation() {
     insertItem(FOO_TYPE, "f1");
 
-    ResilientIndexer indexer = new SuccessfulFakeIndexer(new IndexType("bars", "bar"));
+    ResilientIndexer indexer = new SuccessfulFakeIndexer(IndexType.main(Index.simple("bars"), "bar"));
     advanceInTime();
 
     underTest = newRecoveryIndexer(indexer);
index 78dcca23b21634f476d2dfb3420ef4c19e5bc07f..108c89475a67895e9892792893bf3f45dc354679 100644 (file)
@@ -25,7 +25,7 @@ import org.junit.rules.ExpectedException;
 import org.mockito.Mockito;
 import org.sonar.db.DbClient;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
index 55c4aa462e124f7cc983357bd2d0caf83b00518b..b326fd398f773db01041d1ddbc1a4d7329ecc520 100644 (file)
@@ -61,7 +61,8 @@ public class WebIssueStorageTest {
 
   private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
 
-  private WebIssueStorage underTest = new WebIssueStorage(system2, dbClient, new DefaultRuleFinder(db.getDbClient(), defaultOrganizationProvider), mock(IssueIndexer.class));
+  private IssueIndexer issueIndexer = mock(IssueIndexer.class);
+  private WebIssueStorage underTest = new WebIssueStorage(system2, dbClient, new DefaultRuleFinder(db.getDbClient(), defaultOrganizationProvider), issueIndexer);
 
   @Test
   public void load_component_id_from_db() {
index cb5c4ef44cabe236a360124f33713b004bbbfcf9..184daed2564a8a8ca67b84d5d9598aeb251491af 100644 (file)
@@ -149,7 +149,7 @@ public class IssueIndexTest {
     // project2 can be seen by group2
     indexIssue(newDoc("I2", file2));
     authorizationIndexer.allowOnlyGroup(project2, group2);
-    // project3 can be seen by nobody
+    // project3 can be seen by nobody but root
     indexIssue(newDoc("I3", file3));
 
     userSessionRule.logIn().setGroups(group1);
@@ -167,6 +167,9 @@ public class IssueIndexTest {
 
     userSessionRule.logIn().setGroups(group1, group2);
     assertThatSearchReturnsEmpty(IssueQuery.builder().projectUuids(singletonList(project3.uuid())));
+
+    userSessionRule.setRoot();
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1", "I2", "I3");
   }
 
   @Test
@@ -198,6 +201,9 @@ public class IssueIndexTest {
     // another user
     userSessionRule.logIn(newUserDto());
     assertThatSearchReturnsEmpty(IssueQuery.builder());
+
+    userSessionRule.setRoot();
+    assertThatSearchReturnsOnly(IssueQuery.builder(), "I1", "I2", "I3");
   }
 
   @Test
index e6d4616cdc2e6bdab7d953f2d79454cbea71a16d..601b84b441dccb05895ce9b1c068d56c15169467 100644 (file)
@@ -68,7 +68,7 @@ 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.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
 
 @RunWith(DataProviderRunner.class)
@@ -1383,7 +1383,7 @@ public class ProjectMeasuresIndexTest {
 
   @Test
   public void search_statistics() {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES,
+    es.putDocuments(TYPE_PROJECT_MEASURES,
       newDoc("lines", 10, "coverage", 80)
         .setLanguages(Arrays.asList("java", "cs", "js"))
         .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 200, "cs", 250, "js", 50)),
@@ -1409,17 +1409,17 @@ public class ProjectMeasuresIndexTest {
   }
 
   private void index(ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    es.putDocuments(TYPE_PROJECT_MEASURES, docs);
     authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).allowAnyone()).collect(toList()));
   }
 
   private void indexForUser(UserDto user, ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    es.putDocuments(TYPE_PROJECT_MEASURES, docs);
     authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addUserId(user.getId())).collect(toList()));
   }
 
   private void indexForGroup(GroupDto group, ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    es.putDocuments(TYPE_PROJECT_MEASURES, docs);
     authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addGroupId(group.getId())).collect(toList()));
   }
 
index 918a889312bd27e5d9c7ea508a3c002ad55c2d03..344b598c5c7e75b128481b31c4f811e38dc2e115 100644 (file)
@@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 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.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.GT;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.LT;
 
@@ -287,7 +287,7 @@ public class ProjectMeasuresIndexTextSearchTest {
   }
 
   private void index(ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    es.putDocuments(TYPE_PROJECT_MEASURES, docs);
     authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).allowAnyone()).collect(toList()));
   }
 
index 157981454a41dbcf9210da017eff9c6c93d97760..309698e3c490ec1b3331ce0a7791d477c07ec152 100644 (file)
@@ -489,7 +489,7 @@ public class MemberUpdaterTest {
 
   private void assertUserIsNotMember(OrganizationDto organization, UserDto user) {
     db.organizations().assertUserIsNotMemberOfOrganization(organization, user);
-    SearchRequestBuilder request = es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+    SearchRequestBuilder request = es.client().prepareSearch(UserIndexDefinition.TYPE_USER)
       .setQuery(boolQuery()
         .must(termQuery(FIELD_ORGANIZATION_UUIDS, organization.getUuid()))
         .must(termQuery(FIELD_UUID, user.getUuid())));
index 84bd85739c7d00f5435c63e43b152164b81799d7..d6f3fbc51149003ed570c4769d12129089f502e1 100644 (file)
@@ -60,7 +60,6 @@ import org.sonar.server.permission.PermissionService;
 import org.sonar.server.permission.PermissionServiceImpl;
 import org.sonar.server.qualityprofile.BuiltInQProfileRepository;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
 import org.sonar.server.ws.TestRequest;
@@ -82,6 +81,7 @@ import static org.sonar.core.config.CorePropertyDefinitions.ORGANIZATIONS_ANYONE
 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_NAME;
 import static org.sonar.server.organization.ws.OrganizationsWsTestSupport.STRING_257_CHARS_LONG;
 import static org.sonar.server.organization.ws.OrganizationsWsTestSupport.STRING_65_CHARS_LONG;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ORGANIZATION_UUIDS;
 import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID;
 import static org.sonar.test.JsonAssert.assertJson;
@@ -236,7 +236,7 @@ public class CreateActionTest {
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "bar").get();
     assertThat(dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId())).isPresent();
-    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+    assertThat(es.client().prepareSearch(TYPE_USER)
       .setQuery(boolQuery()
         .must(termQuery(FIELD_ORGANIZATION_UUIDS, organization.getUuid()))
         .must(termQuery(FIELD_UUID, user.getUuid())))
index 8c5fd471ed2d44f1da556240e3f6d96d00c54e4a..0deceacf733443a1d0e7116c9da86bdbf4a7aa26 100644 (file)
@@ -26,8 +26,8 @@ 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;
+import static org.sonar.server.permission.index.FooIndexDefinition.DESCRIPTOR;
+import static org.sonar.server.permission.index.FooIndexDefinition.TYPE_AUTHORIZATION;
 
 public class FooIndex {
 
@@ -40,8 +40,8 @@ public class FooIndex {
   }
 
   public boolean hasAccessToProject(String projectUuid) {
-    SearchHits hits = esClient.prepareSearch(FOO_INDEX)
-      .setTypes(FOO_TYPE)
+    SearchHits hits = esClient.prepareSearch(DESCRIPTOR)
+      .setTypes(TYPE_AUTHORIZATION.getType())
       .setQuery(QueryBuilders.boolQuery()
         .must(QueryBuilders.termQuery(FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid))
         .filter(authorizationTypeSupport.createQueryFilter()))
index c34f215389837c1d2b6b6c468ab702017ffefb99..2dfe93c4c29aad4bd59927e7b64ee263ce212a62 100644 (file)
 package org.sonar.server.permission.index;
 
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
-import org.sonar.server.es.NewIndex;
+import org.sonar.server.es.newindex.NewAuthorizedIndex;
 
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
-import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
+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 Index DESCRIPTOR = Index.withRelations("foos");
   public static final String FOO_TYPE = "foo";
-  public static final IndexType INDEX_TYPE_FOO = new IndexType(FOO_INDEX, FOO_TYPE);
+  public static final IndexType.IndexMainType TYPE_AUTHORIZATION = IndexType.main(DESCRIPTOR, IndexAuthorizationConstants.TYPE_AUTHORIZATION);
+  public static final IndexType.IndexRelationType TYPE_FOO = IndexType.relation(TYPE_AUTHORIZATION, 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());
+    NewAuthorizedIndex newIndex = context.createWithAuthorization(
+      DESCRIPTOR,
+      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();
+    newIndex.createTypeMapping(TYPE_FOO)
+      .keywordFieldBuilder(FIELD_NAME).build()
+      .keywordFieldBuilder(FIELD_PROJECT_UUID).build();
   }
 }
index 8572418fd4453bda84fe3f712b365c5fd902de79..fd9e79f31bf32e1cb9fe529b28748b90d7b5b3e4 100644 (file)
  */
 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.BaseDoc;
 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;
+import static org.sonar.server.permission.index.FooIndexDefinition.TYPE_FOO;
 
 public class FooIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
-  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(INDEX_TYPE_FOO, p -> true);
+  private static final AuthorizationScope AUTHORIZATION_SCOPE = new AuthorizationScope(TYPE_FOO, p -> true);
 
   private final EsClient esClient;
 
@@ -59,15 +59,34 @@ public class FooIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
   }
 
   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))
+    FooDoc fooDoc = new FooDoc(projectUuid, name);
+    esClient.prepareIndex(TYPE_FOO)
+      .setId(fooDoc.getId())
+      .setRouting(fooDoc.getRouting().orElse(null))
+      .setSource(fooDoc.getFields())
       .get();
   }
 
+  private static final class FooDoc extends BaseDoc {
+    private final String projectUuid;
+    private final String name;
+
+    private FooDoc(String projectUuid, String name) {
+      super(TYPE_FOO);
+      this.projectUuid = projectUuid;
+      this.name = name;
+      setField(FooIndexDefinition.FIELD_PROJECT_UUID, projectUuid);
+      setField(FooIndexDefinition.FIELD_NAME, name);
+      setParent(AuthorizationDoc.idOf(projectUuid));
+    }
+
+    @Override
+    public String getId() {
+      return projectUuid + "_" + name;
+    }
+
+  }
+
   @Override
   public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
     throw new UnsupportedOperationException();
@@ -75,7 +94,7 @@ public class FooIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
 
   @Override
   public Set<IndexType> getIndexTypes() {
-    return ImmutableSet.of(INDEX_TYPE_FOO);
+    return ImmutableSet.of(TYPE_FOO);
   }
 
   @Override
index 37708c1da8ba9046bb16c5977f229c365dd5cce7..1dda0b4e89a4c05648796695f14283cb7364ace7 100644 (file)
@@ -33,6 +33,7 @@ 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.IndexType.IndexMainType;
 import org.sonar.server.es.IndexingResult;
 import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.tester.UserSessionRule;
@@ -47,7 +48,7 @@ import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE
 
 public class PermissionIndexerTest {
 
-  private static final IndexType INDEX_TYPE_FOO_AUTH = new IndexType(FooIndexDefinition.INDEX_TYPE_FOO.getIndex(), TYPE_AUTHORIZATION);
+  private static final IndexMainType INDEX_TYPE_FOO_AUTH = IndexType.main(FooIndexDefinition.DESCRIPTOR, TYPE_AUTHORIZATION);
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
@@ -336,8 +337,7 @@ public class PermissionIndexerTest {
   }
 
   private void assertThatAuthIndexHasSize(int expectedSize) {
-    IndexType authIndexType = underTest.getIndexTypes().iterator().next();
-    assertThat(es.countDocuments(authIndexType)).isEqualTo(expectedSize);
+    assertThat(es.countDocuments(FooIndexDefinition.TYPE_AUTHORIZATION)).isEqualTo(expectedSize);
   }
 
   private void indexOnStartup() {
index 808befb7bac7a998a89b1ad5510c0fb2f5c2e265..9a94ba36f93e700506f288b564f34196b34c7011 100644 (file)
@@ -61,14 +61,14 @@ public class WebAuthorizationTypeSupportTest {
         "          \"bool\" : {" +
         "            \"should\" : [{" +
         "              \"term\" : {" +
-        "                \"allowAnyone\" : {\"value\": true}" +
+        "                \"auth_allowAnyone\" : {\"value\": true}" +
         "              }" +
         "            }]" +
         "          }" +
         "        }]" +
         "      }" +
         "    }," +
-        "    \"parent_type\" : \"authorization\"" +
+        "    \"parent_type\" : \"auth\"" +
         "  }" +
         "}");
   }
@@ -88,12 +88,12 @@ public class WebAuthorizationTypeSupportTest {
         "            \"should\": [" +
         "              {" +
         "                \"term\": {" +
-        "                  \"allowAnyone\": {\"value\": true}" +
+        "                  \"auth_allowAnyone\": {\"value\": true}" +
         "                }" +
         "              }," +
         "              {" +
         "                \"term\": {" +
-        "                  \"userIds\": {\"value\": 1234}" +
+        "                  \"auth_userIds\": {\"value\": 1234}" +
         "                }" +
         "              }" +
         "            ]" +
@@ -101,7 +101,7 @@ public class WebAuthorizationTypeSupportTest {
         "        }]" +
         "      }" +
         "    }," +
-        "    \"parent_type\": \"authorization\"" +
+        "    \"parent_type\": \"auth\"" +
         "  }" +
         "}");
   }
@@ -123,22 +123,22 @@ public class WebAuthorizationTypeSupportTest {
         "            \"should\": [" +
         "              {" +
         "                \"term\": {" +
-        "                  \"allowAnyone\": {\"value\": true}" +
+        "                  \"auth_allowAnyone\": {\"value\": true}" +
         "                }" +
         "              }," +
         "              {" +
         "                \"term\": {" +
-        "                  \"userIds\": {\"value\": 1234}" +
+        "                  \"auth_userIds\": {\"value\": 1234}" +
         "                }" +
         "              }," +
         "              {" +
         "                \"term\": {" +
-        "                  \"groupIds\": {\"value\": 10}" +
+        "                  \"auth_groupIds\": {\"value\": 10}" +
         "                }" +
         "              }," +
         "              {" +
         "                \"term\": {" +
-        "                  \"groupIds\": {\"value\": 11}" +
+        "                  \"auth_groupIds\": {\"value\": 11}" +
         "                }" +
         "              }" +
         "            ]" +
@@ -146,7 +146,7 @@ public class WebAuthorizationTypeSupportTest {
         "        }]" +
         "      }" +
         "    }," +
-        "    \"parent_type\": \"authorization\"" +
+        "    \"parent_type\": \"auth\"" +
         "  }" +
         "}");
   }
index 90c92c9d56be501ba51eb3581f525e3e2cb3a5a7..abf5408ad55bd780e28b246952017bc2902079a3 100644 (file)
@@ -75,28 +75,28 @@ public class BackendCleanupTest {
 
   @Test
   public void clear_indexes() {
-    es.putDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE, IssueDocTesting.newDoc());
-    es.putDocuments(RuleIndexDefinition.INDEX_TYPE_RULE, newRuleDoc());
-    es.putDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT, newComponentDoc());
+    es.putDocuments(IssueIndexDefinition.TYPE_ISSUE, IssueDocTesting.newDoc());
+    es.putDocuments(RuleIndexDefinition.TYPE_RULE, newRuleDoc());
+    es.putDocuments(ComponentIndexDefinition.TYPE_COMPONENT, newComponentDoc());
 
     underTest.clearIndexes();
 
-    assertThat(es.countDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE)).isEqualTo(0);
-    assertThat(es.countDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT)).isEqualTo(0);
+    assertThat(es.countDocuments(IssueIndexDefinition.TYPE_ISSUE)).isEqualTo(0);
+    assertThat(es.countDocuments(ComponentIndexDefinition.TYPE_COMPONENT)).isEqualTo(0);
   }
 
   @Test
   public void clear_all() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
-    es.putDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE, IssueDocTesting.newDoc());
-    es.putDocuments(RuleIndexDefinition.INDEX_TYPE_RULE, newRuleDoc());
-    es.putDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT, newComponentDoc());
+    es.putDocuments(IssueIndexDefinition.TYPE_ISSUE, IssueDocTesting.newDoc());
+    es.putDocuments(RuleIndexDefinition.TYPE_RULE, newRuleDoc());
+    es.putDocuments(ComponentIndexDefinition.TYPE_COMPONENT, newComponentDoc());
 
     underTest.clearAll();
 
-    assertThat(es.countDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE)).isEqualTo(0);
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(0);
-    assertThat(es.countDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT)).isEqualTo(0);
+    assertThat(es.countDocuments(IssueIndexDefinition.TYPE_ISSUE)).isEqualTo(0);
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(0);
+    assertThat(es.countDocuments(ComponentIndexDefinition.TYPE_COMPONENT)).isEqualTo(0);
 
     assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(0);
     assertThat(dbTester.countRowsOfTable("snapshots")).isEqualTo(0);
@@ -107,28 +107,28 @@ public class BackendCleanupTest {
   @Test
   public void reset_data() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
-    es.putDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE, IssueDocTesting.newDoc());
-    es.putDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW, new ViewDoc().setUuid("CDEF").setProjects(newArrayList("DEFG")));
-    es.putDocuments(RuleIndexDefinition.INDEX_TYPE_RULE, newRuleDoc());
-    es.putDocuments(ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES, new ProjectMeasuresDoc()
+    es.putDocuments(IssueIndexDefinition.TYPE_ISSUE, IssueDocTesting.newDoc());
+    es.putDocuments(ViewIndexDefinition.TYPE_VIEW, new ViewDoc().setUuid("CDEF").setProjects(newArrayList("DEFG")));
+    es.putDocuments(RuleIndexDefinition.TYPE_RULE, newRuleDoc());
+    es.putDocuments(ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES, new ProjectMeasuresDoc()
       .setId("PROJECT")
       .setKey("Key")
       .setName("Name"));
-    es.putDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT, newComponentDoc());
+    es.putDocuments(ComponentIndexDefinition.TYPE_COMPONENT, newComponentDoc());
 
     underTest.resetData();
 
     assertThat(dbTester.countRowsOfTable("projects")).isZero();
     assertThat(dbTester.countRowsOfTable("snapshots")).isZero();
     assertThat(dbTester.countRowsOfTable("properties")).isZero();
-    assertThat(es.countDocuments(IssueIndexDefinition.INDEX_TYPE_ISSUE)).isZero();
-    assertThat(es.countDocuments(ViewIndexDefinition.INDEX_TYPE_VIEW)).isZero();
-    assertThat(es.countDocuments(ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES)).isZero();
-    assertThat(es.countDocuments(ComponentIndexDefinition.INDEX_TYPE_COMPONENT)).isZero();
+    assertThat(es.countDocuments(IssueIndexDefinition.TYPE_ISSUE)).isZero();
+    assertThat(es.countDocuments(ViewIndexDefinition.TYPE_VIEW)).isZero();
+    assertThat(es.countDocuments(ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES)).isZero();
+    assertThat(es.countDocuments(ComponentIndexDefinition.TYPE_COMPONENT)).isZero();
 
     // Rules should not be removed
     assertThat(dbTester.countRowsOfTable("rules")).isEqualTo(1);
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1);
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(1);
   }
 
   private static RuleDoc newRuleDoc() {
index c5283f379217c35131a4ecdb2aa69b7665c8768c..37a712ecc47cf2e94c44ff38663a9f0631fe5a41 100644 (file)
@@ -24,7 +24,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
index 0fe9304e70a8a5fe5fd9928adb12f8feacfef187..cd4d9ad0b453e8446aa79be2c9d9488036ed560e 100644 (file)
@@ -47,7 +47,7 @@ import static java.util.Optional.ofNullable;
 import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 import static org.sonar.test.JsonAssert.assertJson;
 
 public class SearchActionTest {
@@ -110,7 +110,7 @@ public class SearchActionTest {
   }
 
   private void index(ProjectMeasuresDoc... docs) {
-    es.putDocuments(INDEX_TYPE_PROJECT_MEASURES, docs);
+    es.putDocuments(TYPE_PROJECT_MEASURES, docs);
     authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).allowAnyone()).collect(toList()));
   }
 
index ab2da8a585e5bc3f9f2af897ee14ee9e136c2db6..281e15aab70ff802dc02ed81efeab012dc56ee76 100644 (file)
@@ -277,7 +277,7 @@ public class RegisterRulesTest {
       .containsOnly(READY);
 
     // verify index
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(numberOfRules);
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
     assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getIds())
       .isNotEmpty();
 
@@ -291,7 +291,7 @@ public class RegisterRulesTest {
       .containsOnly(REMOVED);
 
     // verify index (documents are still in the index, but all are removed)
-    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(numberOfRules);
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
     assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getIds())
       .isEmpty();
   }
@@ -315,7 +315,7 @@ public class RegisterRulesTest {
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, RULE_KEY1);
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, RULE_KEY2);
     RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, HOTSPOT_RULE_KEY);
-    assertThat(es.getIds(RuleIndexDefinition.INDEX_TYPE_RULE)).containsOnly(valueOf(rule1.getId()), valueOf(rule2.getId()), valueOf(hotspotRule.getId()));
+    assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getId()), valueOf(rule2.getId()), valueOf(hotspotRule.getId()));
 
     // user adds tags and sets markdown note
     rule1.setTags(newHashSet("usertag1", "usertag2"));
@@ -739,7 +739,7 @@ public class RegisterRulesTest {
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, RULE_KEY1);
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, RULE_KEY2);
     RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), defaultOrganization, HOTSPOT_RULE_KEY);
-    assertThat(es.getIds(RuleIndexDefinition.INDEX_TYPE_RULE)).containsOnly(valueOf(rule1.getId()), valueOf(rule2.getId()), valueOf(hotspotRule.getId()));
+    assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getId()), valueOf(rule2.getId()), valueOf(hotspotRule.getId()));
 
     assertThat(rule2.getStatus()).isEqualTo(READY);
 
@@ -774,7 +774,7 @@ public class RegisterRulesTest {
     execute(new BigRepository());
     assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
     assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
-    assertThat(es.getIds(RuleIndexDefinition.INDEX_TYPE_RULE)).hasSize(BigRepository.SIZE);
+    assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
   }
 
   @Test
index 315b5a03d2f5b2b8f13af1f6a766098e963b3720..450bf1b96d2c80d5f89e4c7d6d9e97cccc0bb4a2 100644 (file)
  */
 package org.sonar.server.search;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Map;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.sonar.server.es.BaseDoc;
 import org.sonar.server.es.EsUtils;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 
 public class BaseDocTest {
+  private final IndexType.IndexMainType someType = IndexType.main(Index.simple("bar"), "donut");
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
 
   @Test
   public void getField() {
@@ -38,21 +47,12 @@ public class BaseDocTest {
     fields.put("a_string", "foo");
     fields.put("a_int", 42);
     fields.put("a_null", null);
-    BaseDoc doc = new BaseDoc(fields) {
+    BaseDoc doc = new BaseDoc(someType, fields) {
       @Override
       public String getId() {
         return null;
       }
 
-      @Override
-      public String getRouting() {
-        return null;
-      }
-
-      @Override
-      public String getParent() {
-        return null;
-      }
     };
 
     assertThat((String) doc.getNullableField("a_string")).isEqualTo("foo");
@@ -63,21 +63,12 @@ public class BaseDocTest {
   @Test
   public void getField_fails_if_missing_field() {
     Map<String, Object> fields = Collections.emptyMap();
-    BaseDoc doc = new BaseDoc(fields) {
+    BaseDoc doc = new BaseDoc(someType, fields) {
       @Override
       public String getId() {
         return null;
       }
 
-      @Override
-      public String getRouting() {
-        return null;
-      }
-
-      @Override
-      public String getParent() {
-        return null;
-      }
     };
 
     try {
@@ -90,21 +81,12 @@ public class BaseDocTest {
 
   @Test
   public void getFieldAsDate() {
-    BaseDoc doc = new BaseDoc(Maps.newHashMap()) {
+    BaseDoc doc = new BaseDoc(someType, Maps.newHashMap()) {
       @Override
       public String getId() {
         return null;
       }
 
-      @Override
-      public String getRouting() {
-        return null;
-      }
-
-      @Override
-      public String getParent() {
-        return null;
-      }
     };
     Date now = new Date();
     doc.setField("javaDate", now);
@@ -116,21 +98,12 @@ public class BaseDocTest {
 
   @Test
   public void getNullableFieldAsDate() {
-    BaseDoc doc = new BaseDoc(Maps.newHashMap()) {
+    BaseDoc doc = new BaseDoc(someType, Maps.newHashMap()) {
       @Override
       public String getId() {
         return null;
       }
 
-      @Override
-      public String getRouting() {
-        return null;
-      }
-
-      @Override
-      public String getParent() {
-        return null;
-      }
     };
     Date now = new Date();
     doc.setField("javaDate", now);
@@ -142,4 +115,45 @@ public class BaseDocTest {
     doc.setField("noValue", null);
     assertThat(doc.getNullableFieldAsDate("noValue")).isNull();
   }
+
+  @Test
+  public void getFields_fails_with_ISE_if_setParent_has_not_been_called_on_IndexRelationType() {
+    IndexType.IndexRelationType relationType = IndexType.relation(IndexType.main(Index.withRelations("foo"), "bar"), "donut");
+    BaseDoc doc = new BaseDoc(relationType) {
+
+      @Override
+      public String getId() {
+        throw new UnsupportedOperationException("getId not implemented");
+      }
+
+    };
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("parent must be set on a doc associated to a IndexRelationType (see BaseDoc#setParent(String))");
+
+    doc.getFields();
+  }
+
+  @Test
+  public void getFields_contains_join_field_and_indexType_field_when_setParent_has_been_called_on_IndexRelationType() {
+    Index index = Index.withRelations("foo");
+    IndexType.IndexRelationType relationType = IndexType.relation(IndexType.main(index, "bar"), "donut");
+    BaseDoc doc = new BaseDoc(relationType) {
+      {
+        setParent("miam");
+      }
+
+      @Override
+      public String getId() {
+        throw new UnsupportedOperationException("getId not implemented");
+      }
+
+    };
+
+    Map<String, Object> fields = doc.getFields();
+
+    assertThat((Map) fields.get(index.getJoinField()))
+      .isEqualTo(ImmutableMap.of("name", relationType.getName(), "parent", "miam"));
+    assertThat(fields.get("indexType")).isEqualTo(relationType.getName());
+  }
 }
index 697defe8b1c8e0ec54a4ef83d04a7f325b173509..56bf5052cc757743442efb5eda0a055cd9062454 100644 (file)
@@ -121,7 +121,7 @@ public class UserUpdaterCreateTest {
       .isEqualTo(dto.getUpdatedAt());
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").getId()).isEqualTo(dto.getId());
-    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER);
+    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER);
     assertThat(indexUsers).hasSize(1);
     assertThat(indexUsers.get(0).getSource())
       .contains(
@@ -351,7 +351,7 @@ public class UserUpdaterCreateTest {
       .build(), u -> {
       }, otherUser);
 
-    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(created.getUuid(), otherUser.getUuid());
+    assertThat(es.getIds(UserIndexDefinition.TYPE_USER)).containsExactlyInAnyOrder(created.getUuid(), otherUser.getUuid());
   }
 
   @Test
index 9b03952c851452e6bd0b3bcb9df82c833bcdf6a7..dd76ec4f82410ae0805c121e3ae8ea680da84c94 100644 (file)
@@ -106,7 +106,7 @@ public class UserUpdaterUpdateTest {
     assertThat(updatedUser.getCreatedAt()).isEqualTo(user.getCreatedAt());
     assertThat(updatedUser.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
 
-    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER);
+    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER);
     assertThat(indexUsers).hasSize(1);
     assertThat(indexUsers.get(0).getSource())
       .contains(
@@ -228,7 +228,7 @@ public class UserUpdaterUpdateTest {
       .setLogin("new_login"), u -> {
       });
 
-    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER);
+    List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER);
     assertThat(indexUsers).hasSize(1);
     assertThat(indexUsers.get(0).getSource())
       .contains(entry("login", "new_login"));
@@ -473,7 +473,7 @@ public class UserUpdaterUpdateTest {
       .setScmAccounts(asList("ma2")), u -> {
       }, otherUser);
 
-    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(user.getUuid(), otherUser.getUuid());
+    assertThat(es.getIds(UserIndexDefinition.TYPE_USER)).containsExactlyInAnyOrder(user.getUuid(), otherUser.getUuid());
   }
 
   @Test
index 0a6f6f5fb8007d99445527a6923dd104a7b9896e..e01fb412b8ce33d117ad77603543325b284cc0cb 100644 (file)
@@ -117,7 +117,7 @@ public class CreateActionTest {
       .containsOnly("john", "John", "john@email.com", singletonList("jn"), true);
 
     // exists in index
-    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+    assertThat(es.client().prepareSearch(UserIndexDefinition.TYPE_USER)
       .setQuery(boolQuery()
         .must(termQuery(FIELD_LOGIN, "john"))
         .must(termQuery(FIELD_NAME, "John"))
index 73e4b95806112e7d0ef1f396ab47427371d33361..54aac993829a6d24c7254fb9ab97958f23aacbf1 100644 (file)
@@ -103,7 +103,7 @@ public class DeactivateActionTest {
     deactivate(user.getLogin());
 
     verifyThatUserIsDeactivated(user.getLogin());
-    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+    assertThat(es.client().prepareSearch(UserIndexDefinition.TYPE_USER)
       .setQuery(boolQuery()
         .must(termQuery(FIELD_UUID, user.getUuid()))
         .must(termQuery(FIELD_ACTIVE, "false")))