All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.exporter.schema.IndexSchemaValidator Maven / Gradle / Ivy

There is a newer version: 8.7.0-alpha2
Show newest version
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.exporter.schema;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.exporter.exceptions.IndexSchemaValidationException;
import io.camunda.webapps.schema.descriptors.IndexDescriptor;
import io.camunda.webapps.schema.descriptors.IndexTemplateDescriptor;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link IndexSchemaValidator} validates existing indices mappings against index/index template
 * mappings defined.
 *
 * 

Mappings are valid if * *

    *
  • The existing indices corresponding to an {@link IndexDescriptor} or {@link * IndexTemplateDescriptor} has the same mappings as provided by the descriptor *
  • The mapping provided by the descriptor has new fields compared to the existing indices * corresponding to an {@link IndexDescriptor} or {@link IndexTemplateDescriptor}. *
  • The mapping provided by the descriptor has removed some fields compared to the existing * indices corresponding to an {@link IndexDescriptor} or {@link IndexTemplateDescriptor}. *
* *

Mappings are invalid if * *

    *
  • The mapping provided by the descriptor has same fields with different types compared to * the existing indices corresponding to an {@link IndexDescriptor} or {@link * IndexTemplateDescriptor}. This indicates that the existing indices cannot be updated to new * mappings. If the index is set to allow dynamic mapping, then this case is ignored and the * mapping will be considered as valid. *
  • If multiple indices corresponding to the {@link IndexDescriptor} or {@link * IndexTemplateDescriptor} has different mappings and the differences are not the same. In * this case, it is not clear how to update multiple indices for the same descriptor to the * provided mapping. *
*/ public class IndexSchemaValidator { private static final Logger LOGGER = LoggerFactory.getLogger(IndexSchemaValidator.class); private static final ObjectMapper MAPPER = new ObjectMapper(); /** * Validates existing indices mappings against index/index template mappings defined. * * @param mappings is a map of all the mappings to compare. * @param indexDescriptors is the set of all index descriptors representing desired schema states. * @return new mapping properties to add to schemas, so they align with the descriptors. * @throws IndexSchemaValidationException if the existing indices cannot be updated with the given * mappings. */ public Map> validateIndexMappings( final Map mappings, final Collection indexDescriptors) throws IndexSchemaValidationException { final Map> newFields = new HashMap<>(); for (final IndexDescriptor indexDescriptor : indexDescriptors) { final Map indexMappingsGroup = filterIndexMappings(mappings, indexDescriptor); // we don't check indices that were not yet created if (!indexMappingsGroup.isEmpty()) { final IndexMappingDifference difference = getIndexMappingDifference(indexDescriptor, indexMappingsGroup); validateDifferenceAndCollectNewFields(indexDescriptor, difference, newFields); } } return newFields; } private void validateDifferenceAndCollectNewFields( final IndexDescriptor indexDescriptor, final IndexMappingDifference difference, final Map> newFields) { if (difference != null && !difference.equal()) { LOGGER.debug( "Index fields differ from expected. Index name: {}. Difference: {}.", indexDescriptor.getIndexName(), difference); if (!difference.entriesDiffering().isEmpty()) { // This call will throw an exception unless the index is dynamic, in which case // field differences will be ignored. In the case of a dynamic index, we still want // to collect any new fields, so we should continue to the next checks instead of making // this part of the if/else block failIfIndexNotDynamic(difference, indexDescriptor); } if (!difference.entriesOnlyOnRight().isEmpty()) { LOGGER.info( "Index '{}': Field deletion is requested, will be ignored. Fields: {}", indexDescriptor.getIndexName(), difference.entriesOnlyOnRight()); } else if (!difference.entriesOnlyOnLeft().isEmpty()) { // Collect the new fields newFields.put(indexDescriptor, difference.entriesOnlyOnLeft()); } } else { LOGGER.debug("Index fields are up to date for Index '{}'.", indexDescriptor.getIndexName()); } } private IndexMappingDifference getIndexMappingDifference( final IndexDescriptor indexDescriptor, final Map indexMappingsGroup) { final IndexMapping indexMappingMustBe = IndexMapping.from(indexDescriptor, MAPPER); final var differences = indexMappingsGroup.values().stream() .map(mapping -> IndexMappingDifference.of(indexMappingMustBe, mapping)) .filter(difference -> !difference.equal()) .distinct() .toList(); if (differences.isEmpty()) { return null; } if (differences.size() > 1) { throw new IndexSchemaValidationException( String.format( "Ambiguous schema update. Multiple indices for mapping '%s' has different fields. Differences: '%s'", indexDescriptor.getIndexName(), differences)); } return differences.getFirst(); } /** * Given a {@link Map} of all index mappings, only return those which match the * indexDescriptor. * *

Mappings can be retrieved using {@link SearchEngineClient#getMappings} * * @param indexMappings represents mappings that will be checked * @param indexDescriptor represents the desired state of indices/index templates * @return a filtered map of all indexMappings matching the descriptor */ private Map filterIndexMappings( final Map indexMappings, final IndexDescriptor indexDescriptor) { if (indexDescriptor instanceof IndexTemplateDescriptor) { return indexMappings.entrySet().stream() .filter( e -> e.getKey().equals(((IndexTemplateDescriptor) indexDescriptor).getTemplateName())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } else { return indexMappings.entrySet().stream() .filter(e -> e.getKey().matches(indexDescriptor.getAllVersionsIndexNameRegexPattern())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } } private void failIfIndexNotDynamic( final IndexMappingDifference difference, final IndexDescriptor indexDescriptor) { if (difference.isLeftDynamic() || difference.isRightDynamic()) { LOGGER.debug( "Index '{}' is dynamic, ignoring changes found: {}", indexDescriptor.getIndexName(), difference.entriesDiffering()); } else { final String errorMsg = String.format( "Index name: %s. Not supported index changes are introduced. Data migration is required. Changes found: %s", indexDescriptor.getIndexName(), difference.entriesDiffering()); LOGGER.error(errorMsg); throw new IndexSchemaValidationException(errorMsg); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy