package com.constellio.model.services.schemas;
import static com.constellio.model.services.schemas.builders.CommonMetadataBuilder.ALL_REMOVED_AUTHS;
import static com.constellio.model.services.schemas.builders.CommonMetadataBuilder.ATTACHED_ANCESTORS;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.data.utils.BatchBuilderIterator;
import com.constellio.data.utils.ImpossibleRuntimeException;
import com.constellio.model.entities.Taxonomy;
import com.constellio.model.entities.calculators.dependencies.Dependency;
import com.constellio.model.entities.calculators.dependencies.ReferenceDependency;
import com.constellio.model.entities.calculators.dependencies.SpecialDependencies;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.Transaction;
import com.constellio.model.entities.schemas.Metadata;
import com.constellio.model.entities.schemas.MetadataSchemaType;
import com.constellio.model.entities.schemas.MetadataSchemaTypes;
import com.constellio.model.entities.schemas.ModificationImpact;
import com.constellio.model.entities.schemas.Schemas;
import com.constellio.model.entities.schemas.entries.CalculatedDataEntry;
import com.constellio.model.entities.schemas.entries.CopiedDataEntry;
import com.constellio.model.entities.schemas.entries.DataEntryType;
import com.constellio.model.services.records.RecordServices;
import com.constellio.model.services.schemas.builders.CommonMetadataBuilder;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition;
//AFTER : Move in com.constellio.model.services.records.
public class ModificationImpactCalculator {
private static final Logger LOGGER = LoggerFactory.getLogger(ModificationImpactCalculator.class);
SearchServices searchServices;
RecordServices recordServices;
List<Taxonomy> taxonomies;
MetadataSchemaTypes metadataSchemaTypes;
SchemaUtils schemaUtils;
public ModificationImpactCalculator(MetadataSchemaTypes metadataSchemaTypes, List<Taxonomy> taxonomies,
SearchServices searchServices, RecordServices recordServices) {
this(metadataSchemaTypes, taxonomies, searchServices, recordServices, new SchemaUtils());
}
public ModificationImpactCalculator(MetadataSchemaTypes metadataSchemaTypes, List<Taxonomy> taxonomies,
SearchServices searchServices, RecordServices recordServices, SchemaUtils schemaUtils) {
this.taxonomies = taxonomies;
this.searchServices = searchServices;
this.recordServices = recordServices;
this.metadataSchemaTypes = metadataSchemaTypes;
this.schemaUtils = schemaUtils;
}
public List<ModificationImpact> findTransactionImpact(Transaction transaction,
boolean executedAfterTransaction) {
List<ModificationImpact> recordsModificationImpacts = new ArrayList<>();
List<RecordsModification> recordsModifications = new RecordsModificationBuilder(recordServices)
.build(transaction, metadataSchemaTypes);
List<String> transactionRecordIds = executedAfterTransaction ? null : transaction.getRecordIds();
for (RecordsModification recordsModification : recordsModifications) {
recordsModificationImpacts
.addAll(findImpactOfARecordsModification(recordsModification, transactionRecordIds, transaction.getTitle()));
}
return recordsModificationImpacts;
}
List<ModificationImpact> findImpactOfARecordsModification(RecordsModification recordsModification,
List<String> transactionRecordIds, String transactionTitle) {
List<ModificationImpact> impacts = new ArrayList<>();
for (MetadataSchemaType type : metadataSchemaTypes.getSchemaTypes()) {
impacts.addAll(findImpactsOfARecordsModificationInSchemaType(type, recordsModification, transactionRecordIds,
transactionTitle));
}
return impacts;
}
List<ModificationImpact> findImpactsOfARecordsModificationInSchemaType(MetadataSchemaType schemaType,
RecordsModification recordsModification, List<String> transactionRecordIds, String transactionTitle) {
List<ModificationImpact> recordsModificationImpactsInType = new ArrayList<>();
MetadataList references = new MetadataList();
List<Metadata> reindexedMetadatas = new ArrayList<>();
findPotentialMetadataToReindexAndTheirReferencesToAModifiedMetadata(schemaType, recordsModification,
references, reindexedMetadatas);
return findRealImpactsOfPotentialMetadataToReindex(schemaType, recordsModification, transactionRecordIds,
recordsModificationImpactsInType, references, reindexedMetadatas, transactionTitle);
}
// private List<ModificationImpact> findRealImpactsOfPotentialMetadataToReindex(MetadataSchemaType schemaType,
// RecordsModification recordsModification, List<String> transactionRecordIds,
// List<ModificationImpact> recordsModificationImpactsInType, List<Metadata> references,
// List<Metadata> reindexedMetadatas) {
// if (!references.isEmpty()) {
// Iterator<List<Record>> batchIterator = splitModifiedRecordsInBatchOf1000(recordsModification);
// while (batchIterator.hasNext()) {
//
// LogicalSearchCondition facetsMainCondition = from(schemaType).whereAny(references).isNotNull();
// if (transactionRecordIds != null) {
// facetsMainCondition = facetsMainCondition.andWhere(Schemas.IDENTIFIER).isNotIn(transactionRecordIds);
// }
//
// LogicalSearchQuery query = new LogicalSearchQuery(facetsMainCondition);
// List<Record> records = batchIterator.next();
// List<String> queries = new ArrayList<>();
// for (Record record : records) {
// LogicalSearchCondition facetCondition = from(schemaType).whereAny(references).isEqualTo(record.getId());
// queries.add(facetCondition.getSolrQuery());
// }
// query.addQueryFacets("batch", queries);
// query.setNumberOfRows(0);
//
// SPEQueryResponse response = searchServices.query(query);
//
// List<String> recordIdsWithModificationImpacts = new ArrayList<>();
// for(int i = 0 ; i < queries.size() ; i++) {
// Record record = records.get(i);
// String facetQuery = queries.get(i);
// if (response.getQueryFacetCount(facetQuery) > 0) {
// recordIdsWithModificationImpacts.add(record.getId());
// }
// }
//
//
// LogicalSearchCondition condition = getLogicalSearchConditionFor(schemaType, batchIterator.next(),
// transactionRecordIds, references);
// if (searchServices.hasResults(condition)) {
// recordsModificationImpactsInType.add(new ModificationImpact(reindexedMetadatas, condition));
// }
// }
// }
//
// return recordsModificationImpactsInType;
// }
LogicalSearchCondition getLogicalSearchConditionFor(MetadataSchemaType schemaType,
List<Record> modifiedRecordsBatch, List<String> transactionRecordIds, List<Metadata> references) {
LogicalSearchCondition condition = from(schemaType).whereAny(references).isIn(modifiedRecordsBatch);
if (transactionRecordIds != null) {
condition = condition.andWhere(Schemas.IDENTIFIER).isNotIn(transactionRecordIds);
}
return condition;
}
private List<ModificationImpact> findRealImpactsOfPotentialMetadataToReindex(MetadataSchemaType schemaType,
RecordsModification recordsModification, List<String> transactionRecordIds,
List<ModificationImpact> recordsModificationImpactsInType, List<Metadata> references,
List<Metadata> reindexedMetadatas, String transactionTitle) {
if (!references.isEmpty()) {
Iterator<List<Record>> batchIterator = splitModifiedRecordsInBatchOf1000(recordsModification);
while (batchIterator.hasNext()) {
LogicalSearchCondition condition = getLogicalSearchConditionFor(schemaType, batchIterator.next(),
transactionRecordIds, references);
int recordsCount = (int) searchServices.getResultsCount(condition);
if (recordsCount > 0) {
recordsModificationImpactsInType.add(new ModificationImpact(
schemaType, reindexedMetadatas, condition, recordsCount, transactionTitle));
}
}
}
return recordsModificationImpactsInType;
}
void findPotentialMetadataToReindexAndTheirReferencesToAModifiedMetadata(MetadataSchemaType schemaType,
RecordsModification recordsModification, MetadataList references, List<Metadata> reindexedMetadatas) {
List<Metadata> automaticMetadatas = schemaType.getAutomaticMetadatas();
List<Metadata> modifiedMetadatas = recordsModification.getModifiedMetadatas();
for (Metadata automaticMetadata : automaticMetadatas) {
List<Metadata> referenceMetadatasLinkingToModifiedMetadatas = getReferenceMetadatasLinkingToModifiedMetadatas(
automaticMetadata, modifiedMetadatas);
if (!referenceMetadatasLinkingToModifiedMetadatas.isEmpty()) {
reindexedMetadatas.add(automaticMetadata);
references.addAll(referenceMetadatasLinkingToModifiedMetadatas);
}
}
}
List<Metadata> getReferenceMetadatasLinkingToModifiedMetadatas(Metadata automaticMetadata,
List<Metadata> modifiedMetadatas) {
MetadataList returnedReferences = new MetadataList();
for (Metadata modifiedMetadata : modifiedMetadatas) {
returnedReferences.addAll(getReferencesToMetadata(automaticMetadata, modifiedMetadata));
}
return returnedReferences;
}
List<Metadata> getReferencesToMetadata(Metadata automaticMetadata, Metadata modifiedMetadata) {
List<Metadata> referencesToMetadata;
if (automaticMetadata.getDataEntry().getType() == DataEntryType.COPIED) {
if (isCopiedMetadataReferencingTheGivenModifiedMetadata(automaticMetadata, modifiedMetadata)) {
referencesToMetadata = Arrays.asList(getReferenceMetadataUsedByCopiedMetadata(automaticMetadata));
} else {
referencesToMetadata = Collections.emptyList();
}
} else if (automaticMetadata.getDataEntry().getType() == DataEntryType.CALCULATED) {
referencesToMetadata = getReferenceMetadatasUsedByTheGivenCalculatedMetadataToObtainValuesOfTheModifiedMetadata(
automaticMetadata, modifiedMetadata);
} else if (automaticMetadata.getDataEntry().getType() == DataEntryType.AGGREGATED) {
referencesToMetadata = Collections.emptyList();
} else {
throw new ImpossibleRuntimeException("Unsupported type : " + automaticMetadata.getDataEntry().getType());
}
return referencesToMetadata;
}
private boolean isCopiedMetadataReferencingTheGivenModifiedMetadata(Metadata automaticMetadata,
Metadata modifiedMetadata) {
CopiedDataEntry dataEntry = (CopiedDataEntry) automaticMetadata.getDataEntry();
String modifiedMetadataCode = modifiedMetadata.getCode();
String metadataCopiedToAutomaticMetadataCode = dataEntry.getCopiedMetadata();
return modifiedMetadataCode.equals(metadataCopiedToAutomaticMetadataCode);
}
private Metadata getReferenceMetadataUsedByCopiedMetadata(Metadata copiedMetadata) {
CopiedDataEntry copiedDataEntry = (CopiedDataEntry) copiedMetadata.getDataEntry();
return metadataSchemaTypes.getMetadata(copiedDataEntry.getReferenceMetadata());
}
private List<Metadata> getReferenceMetadatasUsedByTheGivenCalculatedMetadataToObtainValuesOfTheModifiedMetadata(
Metadata automaticMetadata, Metadata modifiedMetadata) {
List<Metadata> referencesToMetadata = new ArrayList<>();
CalculatedDataEntry dataEntry = (CalculatedDataEntry) automaticMetadata.getDataEntry();
for (Dependency dependency : dataEntry.getCalculator().getDependencies()) {
referencesToMetadata.addAll(
addReferenceMetadatasUsedByTheGivenDependencyToObtainValuesOfTheModifiedMetadata(automaticMetadata,
modifiedMetadata, dependency));
}
return referencesToMetadata;
}
private List<Metadata> addReferenceMetadatasUsedByTheGivenDependencyToObtainValuesOfTheModifiedMetadata(
Metadata automaticMetadata,
Metadata modifiedMetadata, Dependency dependency) {
if (dependency instanceof ReferenceDependency) {
return getReferenceMetadatasUsedByTheGivenReferenceDependencyToObtainValuesOfTheModifiedMetadata(
automaticMetadata, modifiedMetadata, (ReferenceDependency) dependency);
} else if (dependency == SpecialDependencies.HIERARCHY
&& modifiedMetadataHasPotentialHierarchyImpactOnAutomaticMetadata(automaticMetadata, modifiedMetadata)) {
return getReferenceMetadatasUsedByTheGivenHierarchyDependencyToObtainValuesOfTheModifiedMetadata(
automaticMetadata, modifiedMetadata);
} else {
return Collections.emptyList();
}
}
private boolean modifiedMetadataHasPotentialHierarchyImpactOnAutomaticMetadata(Metadata automaticMeta,
Metadata modifiedMeta) {
return modifiedMeta.isLocalCode(CommonMetadataBuilder.PATH)
|| (modifiedMeta.isLocalCode(ATTACHED_ANCESTORS) && automaticMeta.isLocalCode(ATTACHED_ANCESTORS))
// || (modifiedMeta.isLocalCode(REMOVED_AUTHORIZATIONS) && automaticMeta.isLocalCode(ALL_REMOVED_AUTHS))
// || (modifiedMeta.isLocalCode(DETACHED_AUTHORIZATIONS) && automaticMeta.isLocalCode(ALL_REMOVED_AUTHS))
|| (modifiedMeta.isLocalCode(ALL_REMOVED_AUTHS) && automaticMeta.isLocalCode(ALL_REMOVED_AUTHS));
}
private List<Metadata> getReferenceMetadatasUsedByTheGivenReferenceDependencyToObtainValuesOfTheModifiedMetadata(
Metadata automaticMetadata, Metadata modifiedMetadata,
ReferenceDependency<?> referenceDependency) {
List<Metadata> referencesToMetadata = new ArrayList<>();
if (isDependencyReferencingAnySchemaMetadataWithCode(referenceDependency, modifiedMetadata.getLocalCode())) {
Metadata reference = getDependencyReferenceMetadata(automaticMetadata, referenceDependency);
String dependencyCode = schemaUtils.getDependencyCode(referenceDependency, reference);
if (dependencyCode.equals(modifiedMetadata.getCode())) {
referencesToMetadata.add(reference);
}
}
return referencesToMetadata;
}
private List<Metadata> getReferenceMetadatasUsedByTheGivenHierarchyDependencyToObtainValuesOfTheModifiedMetadata(
Metadata automaticMetadata, Metadata modifiedMetadata) {
MetadataList referencesToMetadata = new MetadataList();
String modifiedMetadataSchemaTypeCode = schemaUtils
.getSchemaTypeCode(schemaUtils.getSchemaCode(modifiedMetadata));
MetadataSchemaType automaticMetadataSchema = metadataSchemaTypes
.getSchemaType(schemaUtils.getSchemaTypeCode(automaticMetadata));
referencesToMetadata.addAll(automaticMetadataSchema.getTaxonomySchemasMetadataWithChildOfRelationship(taxonomies));
referencesToMetadata.addAll(automaticMetadataSchema.getAllReferencesToTaxonomySchemas(taxonomies));
referencesToMetadata.addAll(automaticMetadataSchema.getAllParentReferences());
return referencesToMetadata.onlyReferencesToType(modifiedMetadataSchemaTypeCode);
}
private Metadata getDependencyReferenceMetadata(Metadata automaticMetadata,
ReferenceDependency<?> referenceDependency) {
String referenceCode = schemaUtils.getReferenceCode(automaticMetadata, referenceDependency);
return metadataSchemaTypes.getMetadata(referenceCode);
}
boolean isDependencyReferencingAnySchemaMetadataWithCode(ReferenceDependency<?> referenceDependency,
String metadataCode) {
String metadataLocalCode = new SchemaUtils().toLocalMetadataCode(metadataCode);
return referenceDependency.getDependentMetadataCode().contains(metadataLocalCode);
}
private Iterator<List<Record>> splitModifiedRecordsInBatchOf1000(RecordsModification recordsModification) {
return new BatchBuilderIterator<>(recordsModification.getRecords().iterator(), 100);
}
}