package com.constellio.app.services.records; import static com.constellio.model.entities.schemas.MetadataValueType.REFERENCE; import static com.constellio.model.entities.schemas.entries.DataEntryType.MANUAL; import static com.constellio.model.entities.security.global.AuthorizationDeleteRequest.authorizationDeleteRequest; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasIn; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.app.api.extensions.params.CollectionSystemCheckParams; import com.constellio.app.api.extensions.params.TryRepairAutomaticValueParams; import com.constellio.app.services.factories.AppLayerFactory; import com.constellio.app.services.records.SystemCheckManagerRuntimeException.SystemCheckManagerRuntimeException_AlreadyRunning; import com.constellio.data.dao.managers.StatefulService; import com.constellio.data.utils.TimeProvider; import com.constellio.model.entities.Language; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.Transaction; import com.constellio.model.entities.records.wrappers.SolrAuthorizationDetails; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchemaType; import com.constellio.model.entities.schemas.MetadataValueType; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.services.collections.CollectionsListManager; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.records.SchemasRecordsServices; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.schemas.MetadataSchemasManagerException.OptimisticLocking; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.schemas.builders.MetadataBuilder; import com.constellio.model.services.schemas.builders.MetadataSchemaBuilder; import com.constellio.model.services.schemas.builders.MetadataSchemaTypeBuilder; import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; import com.constellio.model.services.security.AuthorizationsServices; import com.constellio.model.services.users.UserServices; public class SystemCheckManager implements StatefulService { private static final Logger LOGGER = LoggerFactory.getLogger(SystemCheckManager.class); private boolean systemCheckResultsRunning; private SystemCheckResults lastSystemCheckResults; private AppLayerFactory appLayerFactory; private ModelLayerFactory modelLayerFactory; private CollectionsListManager collectionsListManager; private MetadataSchemasManager schemasManager; private SearchServices searchServices; private UserServices userServices; private RecordServices recordServices; private AuthorizationsServices authServices; static final String CHECKED_REFERENCES_METRIC = "core.checkedReferences"; static final String BROKEN_REFERENCES_METRIC = "core.brokenReferences"; static final String BROKEN_AUTHS_METRIC = "core.brokenAuths"; public SystemCheckManager(AppLayerFactory appLayerFactory) { this.appLayerFactory = appLayerFactory; this.modelLayerFactory = appLayerFactory.getModelLayerFactory(); this.searchServices = modelLayerFactory.newSearchServices(); this.collectionsListManager = modelLayerFactory.getCollectionsListManager(); this.schemasManager = modelLayerFactory.getMetadataSchemasManager(); this.userServices = modelLayerFactory.newUserServices(); this.recordServices = modelLayerFactory.newRecordServices(); this.authServices = modelLayerFactory.newAuthorizationsServices(); } public synchronized void startSystemCheck(final boolean repair) { lastSystemCheckResults = new SystemCheckResults(TimeProvider.getLocalDateTime()); if (systemCheckResultsRunning) { throw new SystemCheckManagerRuntimeException_AlreadyRunning(); } systemCheckResultsRunning = true; new Thread() { @Override public void run() { try { runSystemCheck(repair); } finally { systemCheckResultsRunning = false; } } }.start(); } public SystemCheckResults getLastSystemCheckResults() { return lastSystemCheckResults; } public boolean isSystemCheckResultsRunning() { return systemCheckResultsRunning; } SystemCheckResults runSystemCheck(boolean repair) { lastSystemCheckResults = new SystemCheckResults(TimeProvider.getLocalDateTime()); Map<String, String> ids = findIdsAndTypes(); Language language = Language.withCode(appLayerFactory.getModelLayerFactory().getConfiguration().getMainDataLanguage()); SystemCheckResultsBuilder builder = new SystemCheckResultsBuilder(language, appLayerFactory, lastSystemCheckResults); Set<String> allRecordIds = new HashSet<>(); Set<String> allActiveRecordIds = new HashSet<>(); Map<String, String> allAuthsIdsWithTarget = getAllAuthsIds(); for (String collection : collectionsListManager.getCollections()) { for (MetadataSchemaType type : schemasManager.getSchemaTypes(collection).getSchemaTypes()) { List<Metadata> references = type.getAllMetadatas().onlyWithType(REFERENCE); LogicalSearchQuery query = new LogicalSearchQuery(from(type).returnAll()); Iterator<Record> allRecords = searchServices.recordsIterator(query, 10000); while (allRecords.hasNext()) { Record record = allRecords.next(); allRecordIds.add(record.getId()); if (record.isActive()) { allActiveRecordIds.add(record.getId()); } boolean recordsRepaired = findBrokenLinksInRecord(ids, references, record, repair, builder); try { recordServices.validateRecord(record); } catch (RecordServicesException.ValidationException e) { builder.addNewValidationError(e); } catch (Exception e) { e.printStackTrace(); //TODO } if (recordsRepaired) { try { Transaction transaction = new Transaction(); record.markAsModified(Schemas.TITLE); transaction.getRecordUpdateOptions().setFullRewrite(true); transaction.getRecordUpdateOptions().setUpdateModificationInfos(false); transaction.add(record); if (transaction.getModifiedRecords().size() >= 1) { recordServices.execute(transaction); lastSystemCheckResults.repairedRecords.add(record.getId()); } } catch (Exception e) { e.printStackTrace(); } } } } } for (Map.Entry<String, String> entry : allAuthsIdsWithTarget.entrySet()) { if (!allRecordIds.contains(entry.getValue())) { builder.incrementMetric(BROKEN_AUTHS_METRIC); if (repair) { Record record = recordServices.getDocumentById(entry.getKey()); authServices.execute(authorizationDeleteRequest(entry.getKey(), record.getCollection())); } } } for (String collection : collectionsListManager.getCollectionsExcludingSystem()) { CollectionSystemCheckParams params = new CollectionSystemCheckParams(collection, builder, repair); appLayerFactory.getExtensions().forCollection(collection).checkCollection(params); } for (String collection : collectionsListManager.getCollectionsExcludingSystem()) { MetadataSchemaTypesBuilder typesBuilder = modelLayerFactory.getMetadataSchemasManager().modify(collection); for (MetadataSchemaTypeBuilder schemaType : typesBuilder.getTypes()) { for (MetadataSchemaBuilder schema : schemaType.getAllSchemas()) { for (MetadataBuilder metadata : schema.getMetadatas()) { if (metadata.getDefaultValue() != null && metadata.getType() == MetadataValueType.REFERENCE) { if (metadata.isMultivalue()) { List<String> values = new ArrayList<>((List) metadata.getDefaultValue()); Iterator<String> valuesIterator = values.iterator(); while (valuesIterator.hasNext()) { builder.incrementMetric(CHECKED_REFERENCES_METRIC); String id = valuesIterator.next(); if (!allActiveRecordIds.contains(id)) { valuesIterator.remove(); builder.addBrokenLinkFromMetadataDefaultValue(metadata.getCode(), id); } } metadata.setDefaultValue(values); } else { builder.incrementMetric(CHECKED_REFERENCES_METRIC); String id = (String) metadata.getDefaultValue(); if (!allActiveRecordIds.contains(id)) { builder.addBrokenLinkFromMetadataDefaultValue(metadata.getCode(), id); metadata.setDefaultValue(null); } } } } } } if (repair) { try { modelLayerFactory.getMetadataSchemasManager().saveUpdateSchemaTypes(typesBuilder); } catch (OptimisticLocking e) { e.printStackTrace(); } } } return getLastSystemCheckResults(); } private Map<String, String> getAllAuthsIds() { Map<String, String> authsIds = new HashMap<>(); for (String collection : collectionsListManager.getCollectionsExcludingSystem()) { SchemasRecordsServices schemas = new SchemasRecordsServices(collection, modelLayerFactory); Iterator<Record> authsIterator = searchServices.recordsIterator(new LogicalSearchQuery( from(schemas.authorizationDetails.schemaType()).returnAll()), 10000); while (authsIterator.hasNext()) { SolrAuthorizationDetails auth = schemas.wrapSolrAuthorizationDetails(authsIterator.next()); authsIds.put(auth.getId(), auth.getTarget()); } } return authsIds; } private boolean findBrokenLinksInRecord(Map<String, String> ids, List<Metadata> references, Record record, boolean repair, SystemCheckResultsBuilder builder) { boolean recordRepaired = false; for (Metadata reference : references) { if (reference.isMultivalue()) { List<String> values = record.getList(reference); List<String> modifiedValues = new ArrayList<>(); for (String value : values) { builder.incrementMetric(CHECKED_REFERENCES_METRIC); if (!ids.containsKey(value)) { builder.addBrokenLink(record.getId(), value, reference); } else { modifiedValues.add(value); } } if (repair && reference.getDataEntry().getType() == MANUAL && values.size() != modifiedValues.size()) { record.set(reference, modifiedValues); recordRepaired = true; } if (repair && reference.getDataEntry().getType() != MANUAL && values.size() != modifiedValues.size()) { List<String> valuesToRemove = new ArrayList<>(values); valuesToRemove.removeAll(modifiedValues); recordRepaired = appLayerFactory.getExtensions().forCollectionOf(record).tryRepairAutomaticValue( new TryRepairAutomaticValueParams(record, reference, values, valuesToRemove)); } } else { String value = record.get(reference); if (value != null) { builder.incrementMetric(CHECKED_REFERENCES_METRIC); if (!ids.containsKey(value)) { builder.addBrokenLink(record.getId(), value, reference); if (repair && reference.getDataEntry().getType() == MANUAL) { String modifiedValue = null; // if (reference.isSameLocalCodeIn(Schemas.CREATED_BY.getLocalCode(), // Schemas.MODIFIED_BY.getLocalCode(), Folder.FORM_CREATED_BY, Folder.FORM_MODIFIED_BY)) { // modifiedValue = userServices.getUserInCollection(User.ADMIN, record.getCollection()).getId(); // } record.set(reference, modifiedValue); recordRepaired = true; } } } } } return recordRepaired; } private Map<String, String> findIdsAndTypes() { Map<String, String> idsAndTypes = new HashMap<>(); for (String collection : collectionsListManager.getCollections()) { LogicalSearchQuery query = new LogicalSearchQuery(fromAllSchemasIn(collection).returnAll()); Iterator<Record> allRecords = searchServices.recordsIterator(query, 10000); while (allRecords.hasNext()) { Record record = allRecords.next(); String schemaType = new SchemaUtils().getSchemaTypeCode(record.getSchemaCode()); idsAndTypes.put(record.getId(), schemaType); } } return idsAndTypes; } @Override public void initialize() { } @Override public void close() { } }