
org.nuiton.topia.templates.sql.order.ReplicationOrderBuilderWithEntryPoint Maven / Gradle / Ivy
package org.nuiton.topia.templates.sql.order;
/*-
* #%L
* Toolkit :: Templates
* %%
* Copyright (C) 2017 - 2024 Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataEntity;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataEntityPath;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataLink;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataModel;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataModelPaths;
import org.nuiton.topia.service.sql.metadata.TopiaMetadataReverseAssociation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;
/**
* To compute replication order for a set of entities coming from a single entry point.
*
* Created on 24/09/2020.
*
* @author Tony Chemit - [email protected]
* @since 1.27
*/
public class ReplicationOrderBuilderWithEntryPoint extends ReplicationOrderBuilder {
private static final Logger log = LogManager.getLogger(ReplicationOrderBuilderWithEntryPoint.class);
/**
* Paths for each entity in this model.
*/
private final TopiaMetadataModelPaths paths;
public static List build(TopiaMetadataModel metadataModel, TopiaMetadataModelPaths paths, TopiaMetadataEntity entryPoint) {
Set set = new LinkedHashSet<>();
set.add(Objects.requireNonNull(entryPoint));
set.addAll(Objects.requireNonNull(paths).keySet());
return new ReplicationOrderBuilderWithEntryPoint(Objects.requireNonNull(metadataModel), Objects.requireNonNull(paths), set).build();
}
protected ReplicationOrderBuilderWithEntryPoint(TopiaMetadataModel metadataModel, TopiaMetadataModelPaths paths, Set set) {
super(metadataModel, set);
this.paths = Objects.requireNonNull(paths);
}
@Override
public List build() {
Set entities = getEntities();
List result = new ArrayList<>(entities.size());
ArrayListMultimap pathsMapping = computeReducedPathsMapping(paths, entities);
ArrayListMultimap dependenciesMapping = computeDependencies(entities);
ArrayListMultimap pathsCount = computePathsCount(pathsMapping);
int maxDepth = 0;
OptionalInt max = pathsCount.keySet().stream().mapToInt(MutableInt::intValue).max();
if (max.isPresent()) {
maxDepth = max.getAsInt();
}
log.info(String.format("Found %d max depth to process", maxDepth));
// get optimized order from paths
for (int i = 0; i <= maxDepth; i++) {
List entitiesForThisRound = pathsCount.get(new MutableInt(i));
if (entitiesForThisRound != null) {
addRound(i, entitiesForThisRound, pathsMapping, result);
}
}
// fix order for some entities with skipped relation for paths (to avoid cycle)
int fixRound = 0;
while (fixDependenciesOrder(fixRound, dependenciesMapping, result) > 0) {
fixRound++;
if (fixRound == 100000) {
log.error(String.format("Abord after %d rounds, :(((", fixRound));
}
}
return Collections.unmodifiableList(result);
}
private int fixDependenciesOrder(int round, ArrayListMultimap dependenciesMapping, List result) {
log.debug(String.format("Next fix round %d", round));
int fixCount = 0;
for (Map.Entry> entry : dependenciesMapping.asMap().entrySet()) {
TopiaMetadataEntity entity = entry.getKey();
Collection dependencies = entry.getValue();
if (dependencies != null) {
int entityIndex = result.indexOf(entity);
for (TopiaMetadataEntity dependency : dependencies) {
int dependencyIndex = result.indexOf(dependency);
if (dependencyIndex > entityIndex) {
// place dependency just before entity
log.info(String.format("Place dependency %s (%d) before his owner %s (%d)", dependency.getFullyQualifiedName(), dependencyIndex, entity.getFullyQualifiedName(), entityIndex));
result.remove(dependency);
result.add(entityIndex, dependency);
entityIndex++;
fixCount++;
}
}
}
}
if (fixCount > 0) {
log.info(String.format("For round %d, found %d fixes to apply on dependencies order", round, fixCount));
}
return fixCount;
}
private void addRound(int roundIndex, List entitiesForThisRound, ArrayListMultimap pathsMapping, List result) {
for (TopiaMetadataEntity entity : TopiaMetadataEntity.sortByFqn(entitiesForThisRound)) {
List paths = pathsMapping.get(entity).stream().filter(p -> p.getLinksSize() == roundIndex).collect(Collectors.toList());
log.info(String.format("[%d] Adding entity %s", roundIndex, entity.getFullyQualifiedName()));
for (TopiaMetadataEntityPath path : paths.stream().sorted(Comparator.comparing(p -> p.getEnd().getFullyQualifiedName(), String::compareTo)).collect(Collectors.toList())) {
log.info(String.format("[%d] → Adding path %s → %s", roundIndex, path.getStart(), path.getEnd()));
for (TopiaMetadataLink link : path) {
log.info(String.format("[%d] → Adding link %s", roundIndex, link));
TopiaMetadataEntity owner = link.getOwner();
TopiaMetadataEntity target = link.getTarget();
int indexOfOwner = result.indexOf(owner);
int indexOfTarget = result.indexOf(target);
if (indexOfOwner == -1) {
result.add(owner);
indexOfOwner = result.indexOf(owner);
}
if (link instanceof TopiaMetadataReverseAssociation) {
// must set target before owner
if (indexOfTarget > -1) {
if (indexOfTarget < indexOfOwner) {
// already in good position
continue;
}
// remove target
result.remove(target);
}
// insert it just before owner
result.add(indexOfOwner, target);
} else {
if (indexOfTarget == -1) {
// add target just at the end
result.add(target);
}
}
}
}
}
}
}