Skip to content
This repository has been archived by the owner on Mar 31, 2022. It is now read-only.

Commit

Permalink
Support configuration of index settings #23
Browse files Browse the repository at this point in the history
  • Loading branch information
Gavrilov-Ivan committed Jul 20, 2021
1 parent c6e4a8f commit 05dd841
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 18 deletions.
14 changes: 12 additions & 2 deletions search/src/main/java/io/jmix/search/index/IndexConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.jmix.search.index;

import io.jmix.search.index.mapping.IndexMappingConfiguration;
import org.elasticsearch.common.settings.Settings;

import java.util.Set;

Expand All @@ -35,13 +36,14 @@ public class IndexConfiguration {

protected final IndexMappingConfiguration mapping;

//todo settings
protected final Settings settings;

public IndexConfiguration(String entityName, Class<?> entityClass, String indexName, IndexMappingConfiguration mapping, Set<Class<?>> affectedEntityClasses) {
public IndexConfiguration(String entityName, Class<?> entityClass, String indexName, IndexMappingConfiguration mapping, Settings settings, Set<Class<?>> affectedEntityClasses) {
this.entityName = entityName;
this.entityClass = entityClass;
this.indexName = indexName;
this.mapping = mapping;
this.settings = settings;
this.affectedEntityClasses = affectedEntityClasses;
}

Expand Down Expand Up @@ -81,6 +83,14 @@ public IndexMappingConfiguration getMapping() {
return mapping;
}

/**
* Gets settings of this index
* @return settings
*/
public Settings getSettings() {
return settings;
}

/**
* Gets java classes of all entities presented in indexed properties. Transitive entities are included too.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.search.index;

import org.elasticsearch.common.settings.Settings;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
* Allows to configure Elasticsearch index settings.
* <p>
* Settings can be configured for all search indexes ({@link IndexSettingsConfigurationContext#commonSettingsBuilder()})
* or for index related to specific entity ({@link IndexSettingsConfigurationContext#entitySettingsBuilder(Class)}).
*/
public class IndexSettingsConfigurationContext {

protected Settings.Builder commonSettingsBuilder;
protected Map<Class<?>, Settings.Builder> specificSettingsBuilders;

public IndexSettingsConfigurationContext() {
commonSettingsBuilder = Settings.builder();
specificSettingsBuilders = new HashMap<>();
}

/**
* Provides builder to set settings for all search indexes.
* <p>
* Use builder's 'put' methods to set settings values.
*
* @return ES index settings builder
*/
public Settings.Builder commonSettingsBuilder() {
return commonSettingsBuilder;
}

/**
* Provides builder to set settings for index related to provided entity.
* Value explicitly set for specific index overrides common value.
* <p>
* Use builder's 'put' methods to set settings values.
*
* @param entityClass entity class
* @return ES index settings builder
*/
public Settings.Builder entitySettingsBuilder(Class<?> entityClass) {
return specificSettingsBuilders.computeIfAbsent(entityClass, key -> Settings.builder());
}

/**
* Creates final settings for index related to provided entity as a combination of common and explicit settings.
*
* @param entityClass entity class
* @return ES index settings
*/
public Settings buildEffectiveSettingsForEntity(Class<?> entityClass) {
Settings commonSettings = buildCommonSettings();
Settings entitySettings = buildSpecificSettingsForEntity(entityClass);
return Settings.builder()
.put(commonSettings)
.put(entitySettings)
.build();
}

protected Settings buildCommonSettings() {
return commonSettingsBuilder.build();
}

protected Settings buildSpecificSettingsForEntity(Class<?> entityClass) {
return Optional.ofNullable(specificSettingsBuilders.get(entityClass)).orElse(Settings.builder()).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.search.index;

/**
* Base interface for configurers of Elasticsearch index settings.
* <p>
* Create Spring Bean that implements this interface.
* Index settings can be configured inside {@link IndexSettingsConfigurer#configure(IndexSettingsConfigurationContext)}.
* <p>See {@link IndexSettingsConfigurationContext}.
*
* <p>Example:
* <p>Settings of all search indexes will have common values "index.max_result_window"=15000 and "index.mapping.total_fields.limit"=1500
* but settings of index related to entity class 'DemoEntity' will have common value "index.max_result_window"=15000 and explicit value "index.mapping.total_fields.limit"=2000.
* <p>Configurer:<pre>{@code
* @Component("demo_IndexSettingsConfigurer")
* public class DemoIndexSettingsConfigurer implements IndexSettingsConfigurer {
*
* @Override
* public void configure(@Nonnull IndexSettingsConfigurationContext context) {
* context.commonSettingsBuilder()
* .put("index.max_result_window", 15000)
* .put("index.mapping.total_fields.limit", 1500);
*
* context.entitySettingsBuilder(DemoEntity.class)
* .put("index.mapping.total_fields.limit", 2000);
* }
* }
* }
* </pre>
*/
public interface IndexSettingsConfigurer {

void configure(IndexSettingsConfigurationContext context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -71,6 +72,7 @@ public boolean createIndex(IndexConfiguration indexConfiguration) {
throw new RuntimeException("Unable to create index '" + indexConfiguration.getIndexName() + "': Failed to parse index definition", e);
}
request.mapping(mappingBody, XContentType.JSON);
request.settings(indexConfiguration.getSettings());
log.info("Create index '{}' with mapping {}", indexConfiguration.getIndexName(), mappingBody);
CreateIndexResponse response;
try {
Expand Down Expand Up @@ -285,16 +287,37 @@ protected IndexSynchronizationStatus handleMissingIndex(IndexConfiguration index
protected boolean isIndexActual(IndexConfiguration indexConfiguration) {
Preconditions.checkNotNullArgument(indexConfiguration);

GetIndexResponse index = getIndex(indexConfiguration.getIndexName());
Map<String, MappingMetadata> mappings = index.getMappings();
MappingMetadata mappingMetadata = mappings.get(indexConfiguration.getIndexName());
Map<String, Object> currentMapping = mappingMetadata.getSourceAsMap();
log.debug("Current mapping of index '{}': {}", indexConfiguration.getIndexName(), currentMapping);
GetIndexResponse indexResponse = getIndex(indexConfiguration.getIndexName());
boolean indexMappingActual = isIndexMappingActual(indexConfiguration, indexResponse);
boolean indexSettingsActual = isIndexSettingsActual(indexConfiguration, indexResponse);

Map<String, Object> actualMapping = objectMapper.convertValue(indexConfiguration.getMapping(), new TypeReference<Map<String, Object>>() {
});
log.debug("Actual mapping of index '{}': {}", indexConfiguration.getIndexName(), actualMapping);
return indexMappingActual && indexSettingsActual;
}

protected boolean isIndexMappingActual(IndexConfiguration indexConfiguration, GetIndexResponse indexResponse) {
Map<String, MappingMetadata> mappings = indexResponse.getMappings();
MappingMetadata indexMappingMetadata = mappings.get(indexConfiguration.getIndexName());
Map<String, Object> currentMapping = indexMappingMetadata.getSourceAsMap();
Map<String, Object> actualMapping = objectMapper.convertValue(
indexConfiguration.getMapping(),
new TypeReference<Map<String, Object>>() {
}
);
log.debug("Mappings of index '{}':\nCurrent: {}\nActual: {}",
indexConfiguration.getIndexName(), currentMapping, actualMapping);
return actualMapping.equals(currentMapping);
}

protected boolean isIndexSettingsActual(IndexConfiguration indexConfiguration, GetIndexResponse indexResponse) {
Map<String, Settings> settings = indexResponse.getSettings();
Settings currentSettings = settings.get(indexConfiguration.getIndexName());
Settings actualSettings = indexConfiguration.getSettings();
long unmatchedSettings = actualSettings.keySet().stream().filter(key -> {
String actualValue = actualSettings.get(key);
String currentValue = currentSettings.get(key);
return !actualValue.equals(currentValue);
}).count();

return unmatchedSettings == 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import io.jmix.core.metamodel.model.MetaProperty;
import io.jmix.core.metamodel.model.MetaPropertyPath;
import io.jmix.search.SearchProperties;
import io.jmix.search.index.IndexSettingsConfigurer;
import io.jmix.search.index.IndexSettingsConfigurationContext;
import io.jmix.search.index.IndexConfiguration;
import io.jmix.search.index.annotation.JmixEntitySearchIndex;
import io.jmix.search.index.mapping.DisplayedNameDescriptor;
Expand All @@ -35,6 +37,7 @@
import io.jmix.search.utils.PropertyTools;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -79,6 +82,8 @@ public class AnnotatedIndexDefinitionProcessor {
protected PropertyValueExtractorProvider propertyValueExtractorProvider;
@Autowired
protected SearchProperties searchProperties;
@Autowired
protected List<IndexSettingsConfigurer> indexSettingsConfigurers;

/**
* Processes index definition interface marked with {@link JmixEntitySearchIndex} annotation
Expand All @@ -93,20 +98,21 @@ public IndexConfiguration createIndexConfiguration(String className) {
Class<?> indexDefClass = resolveClass(className);

JmixEntitySearchIndex indexAnnotation = indexDefClass.getAnnotation(JmixEntitySearchIndex.class);
Class<?> entityJavaClass = indexAnnotation.entity();
MetaClass entityMetaClass = metadata.findClass(entityJavaClass);
if (entityMetaClass == null) {
throw new RuntimeException("MetaClass for '" + entityJavaClass + "' not found");
Class<?> entityClass = indexAnnotation.entity();
MetaClass metaClass = metadata.findClass(entityClass);
if (metaClass == null) {
throw new RuntimeException("MetaClass for '" + entityClass + "' not found");
}
String indexName = createIndexName(indexAnnotation, entityMetaClass);
log.debug("Index name for entity {}: {}", entityMetaClass, indexName);
String indexName = createIndexName(indexAnnotation, metaClass);
log.debug("Index name for entity {}: {}", metaClass, indexName);

IndexMappingConfiguration indexMappingConfiguration = createIndexMappingConfig(entityMetaClass, indexDefClass);
IndexMappingConfiguration indexMappingConfiguration = createIndexMappingConfig(metaClass, indexDefClass);
Set<Class<?>> affectedEntityClasses = getAffectedEntityClasses(indexMappingConfiguration);

log.debug("Definition class {}. Affected entity classes = {}", className, affectedEntityClasses);

return new IndexConfiguration(entityMetaClass.getName(), entityJavaClass, indexName, indexMappingConfiguration, affectedEntityClasses);
Settings settings = configureIndexSettings(entityClass);
return new IndexConfiguration(metaClass.getName(), entityClass, indexName, indexMappingConfiguration, settings, affectedEntityClasses);
}

protected Class<?> resolveClass(String className) {
Expand Down Expand Up @@ -248,6 +254,12 @@ protected Set<Class<?>> getAffectedEntityClasses(IndexMappingConfiguration index
return affectedClasses;
}

protected Settings configureIndexSettings(Class<?> entityClass) {
IndexSettingsConfigurationContext context = new IndexSettingsConfigurationContext();
indexSettingsConfigurers.forEach(configurer -> configurer.configure(context));
return context.buildEffectiveSettingsForEntity(entityClass);
}

protected Map<String, MappingFieldDescriptor> processMappingDefinition(MetaClass metaClass, MappingDefinition mappingDefinition) {
return mappingDefinition.getElements().stream()
.map(item -> processMappingDefinitionElement(metaClass, item))
Expand Down

0 comments on commit 05dd841

Please sign in to comment.