package com.constellio.model.services.batch.manager; import static com.constellio.model.entities.schemas.Schemas.IDENTIFIER; import static com.constellio.model.entities.schemas.Schemas.MARKED_FOR_REINDEXING; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasIn; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromEveryTypesOfEveryCollection; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.apache.solr.common.params.ModifiableSolrParams; import org.jdom2.Document; import org.joda.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.data.dao.managers.StatefulService; import com.constellio.data.dao.managers.config.ConfigManager; import com.constellio.data.dao.managers.config.ConfigManagerException; import com.constellio.data.dao.managers.config.ConfigManagerException.OptimisticLockingConfiguration; import com.constellio.data.dao.managers.config.DocumentAlteration; import com.constellio.data.dao.managers.config.events.ConfigUpdatedEventListener; import com.constellio.data.dao.managers.config.values.XMLConfiguration; import com.constellio.data.dao.services.bigVault.solr.SolrUtils; import com.constellio.model.entities.batchprocess.BatchProcess; import com.constellio.model.entities.batchprocess.BatchProcessAction; import com.constellio.model.entities.batchprocess.BatchProcessStatus; import com.constellio.model.services.background.RecordsReindexingBackgroundAction; import com.constellio.model.services.batch.xml.detail.BatchProcessReader; import com.constellio.model.services.batch.xml.list.BatchProcessListReader; import com.constellio.model.services.batch.xml.list.BatchProcessListWriter; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; public class BatchProcessesManager implements StatefulService, ConfigUpdatedEventListener { static final String BATCH_PROCESS_LIST_PATH = "/batchProcesses/list.xml"; private static final Logger LOGGER = LoggerFactory.getLogger(BatchProcessesManager.class); private final ConfigManager configManager; private final SearchServices searchServices; private final List<BatchProcessesListUpdatedEventListener> listeners = new ArrayList<>(); private final ModelLayerFactory modelLayerFactory; public BatchProcessesManager(ModelLayerFactory modelLayerFactory) { super(); this.searchServices = modelLayerFactory.newSearchServices(); this.configManager = modelLayerFactory.getDataLayerFactory().getConfigManager(); this.modelLayerFactory = modelLayerFactory; } @Override public void initialize() { if (!configManager.exist(BATCH_PROCESS_LIST_PATH)) { saveEmptyProcessListXMLDocument(); } configManager.registerListener(BATCH_PROCESS_LIST_PATH, this); markAllStandbyAsPending(); deleteFinishedWithoutErrors(); } void deleteFinishedWithoutErrors() { List<String> batchProcessIds = new ArrayList<>(); for (BatchProcess batchProcess : getFinishedBatchProcesses()) { if (batchProcess.getErrors() == 0) { batchProcessIds.add(batchProcess.getId()); } } delete(batchProcessIds); } private void delete(List<String> batchProcessIds) { configManager.updateXML(BATCH_PROCESS_LIST_PATH, newDeleteBatchProcessesAlteration(batchProcessIds)); for (String batchProcessId : batchProcessIds) { configManager.delete("/batchProcesses/" + batchProcessId + ".xml"); } } public BatchProcess addPendingBatchProcess(LogicalSearchCondition condition, BatchProcessAction action, String title) { BatchProcess batchProcess = addBatchProcessInStandby(condition, action, title); markAsPending(batchProcess); return get(batchProcess.getId()); } public BatchProcess addPendingBatchProcess(LogicalSearchQuery query, BatchProcessAction action, String username, String title) { BatchProcess batchProcess = addBatchProcessInStandby(query, action, username, title); markAsPending(batchProcess); return get(batchProcess.getId()); } public BatchProcess addPendingBatchProcess(List<String> records, BatchProcessAction action, String username, String title, String collection) { BatchProcess batchProcess = addBatchProcessInStandby(records, action, username, title, collection); markAsPending(batchProcess); return get(batchProcess.getId()); } public BatchProcess addPendingBatchProcess(LogicalSearchQuery query, BatchProcessAction action, String title) { BatchProcess batchProcess = addBatchProcessInStandby(query, action, title); markAsPending(batchProcess); return get(batchProcess.getId()); } public BatchProcess addBatchProcessInStandby(LogicalSearchCondition condition, BatchProcessAction action, String title) { LogicalSearchQuery query = new LogicalSearchQuery(condition); String collection = condition.getCollection(); String id = newBatchProcessId(); return addBatchProcessInStandby(query, action, title); } public BatchProcess addBatchProcessInStandby(LogicalSearchQuery logicalQuery, BatchProcessAction action, String title) { String collection = logicalQuery.getCondition().getCollection(); String id = newBatchProcessId(); ModifiableSolrParams params = searchServices.addSolrModifiableParams(logicalQuery); String solrQuery = SolrUtils.toSingleQueryString(params); LocalDateTime requestDateTime = getCurrentTime(); long recordsCount = searchServices.getResultsCount(logicalQuery); updateBatchProcesses( newAddBatchProcessDocumentAlteration(id, solrQuery, collection, requestDateTime, (int) recordsCount, action, null, title)); return newBatchProcessListReader(getProcessListXMLDocument()).read(id); } public BatchProcess addBatchProcessInStandby(LogicalSearchQuery logicalQuery, BatchProcessAction action, String username, String title) { String collection = logicalQuery.getCondition().getCollection(); String id = newBatchProcessId(); ModifiableSolrParams params = searchServices.addSolrModifiableParams(logicalQuery); String solrQuery = SolrUtils.toSingleQueryString(params); LocalDateTime requestDateTime = getCurrentTime(); long recordsCount = searchServices.getResultsCount(logicalQuery); updateBatchProcesses( newAddBatchProcessDocumentAlteration(id, solrQuery, collection, requestDateTime, (int) recordsCount, action, username, title)); return newBatchProcessListReader(getProcessListXMLDocument()).read(id); } public BatchProcess addBatchProcessInStandby(List<String> records, BatchProcessAction action, String username, String title, String collection) { String id = newBatchProcessId(); LocalDateTime requestDateTime = getCurrentTime(); long recordsCount = records.size(); updateBatchProcesses( newAddBatchProcessDocumentAlteration(id, records, collection, requestDateTime, (int) recordsCount, action, username, title)); return newBatchProcessListReader(getProcessListXMLDocument()).read(id); } public void markAsPending(List<BatchProcess> batchProcesses) { for (BatchProcess batchProcess : batchProcesses) { markAsPending(batchProcess); } } public void markAsPending(BatchProcess batchProcess) { updateBatchProcesses(markBatchProcessAsPendingDocumentAlteration(batchProcess.getId())); } public void cancelStandByBatchProcesses(List<BatchProcess> batchProcesses) { for (BatchProcess batchProcess : batchProcesses) { cancelStandByBatchProcess(batchProcess); } } public void cancelStandByBatchProcess(BatchProcess batchProcess) { updateBatchProcesses(cancelStandByBatchProcessDocumentAlteration(batchProcess.getId())); } public void markAllStandbyAsPending() { updateBatchProcesses(markAllBatchProcessAsPendingDocumentAlteration()); } private void updateBatchProcesses(DocumentAlteration documentAlteration) { configManager.updateXML(BATCH_PROCESS_LIST_PATH, documentAlteration); } private BatchProcess withQueryIfBatchProcessFromPreviousFramework(BatchProcess batchProcess) { if (batchProcess != null && batchProcess.getQuery() == null && batchProcess.getRecords() == null) { List<String> records = getRecords(batchProcess); LogicalSearchCondition condition = fromAllSchemasIn(batchProcess.getCollection()).where(IDENTIFIER).isIn(records); ModifiableSolrParams params = searchServices.addSolrModifiableParams(new LogicalSearchQuery(condition)); String query = SolrUtils.toSingleQueryString(params); batchProcess = batchProcess.withQuery(query); } return batchProcess; } public BatchProcess get(String id) { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); return withQueryIfBatchProcessFromPreviousFramework(reader.read(id)); } public BatchProcess getCurrentBatchProcess() { try { return getCurrentBatchProcessWithPossibleOptimisticLocking(); } catch (ConfigManagerException.OptimisticLockingConfiguration e) { LOGGER.info("Optimistic locking while getting current batch process, retrying..."); return getCurrentBatchProcess(); } } BatchProcess getCurrentBatchProcessWithPossibleOptimisticLocking() throws ConfigManagerException.OptimisticLockingConfiguration { XMLConfiguration xmlConfiguration = getProcessListXMLConfiguration(); Document processList = getProcessListXMLDocument(); BatchProcessListReader reader = newBatchProcessListReader(processList); BatchProcess batchProcess = reader.readCurrent(); List<BatchProcess> pendingBatchProcesses = reader.readPendingBatchProcesses(); if (batchProcess == BatchProcessListReader.NO_CURRENT_BATCH_PROCESS && !pendingBatchProcesses.isEmpty()) { startNextBatchProcess(xmlConfiguration, processList); batchProcess = getCurrentBatchProcess(); } return withQueryIfBatchProcessFromPreviousFramework(batchProcess); } LocalDateTime getCurrentTime() { return new LocalDateTime(); } XMLConfiguration getExistingXMLConfiguration(String id) { return configManager.getXML("/batchProcesses/" + id + ".xml"); } public List<BatchProcess> getFinishedBatchProcesses() { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); return reader.readFinishedBatchProcesses(); } public List<BatchProcess> getPendingBatchProcesses() { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); return reader.readPendingBatchProcesses(); } public List<BatchProcess> getStandbyBatchProcesses() { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); return reader.readStandbyBatchProcesses(); } public int getAllBatchProcessesCount() { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); return reader.getAllBatchProcessesCount(); } XMLConfiguration getProcessListXMLConfiguration() { XMLConfiguration config = configManager.getXML(BATCH_PROCESS_LIST_PATH); if (config == null) { saveEmptyProcessListXMLDocument(); config = getProcessListXMLConfiguration(); } return config; } Document getProcessListXMLDocument() { return getProcessListXMLConfiguration().getDocument(); } public List<String> getRecords(BatchProcess batchProcess) { XMLConfiguration batchProcessConfiguration = getExistingXMLConfiguration(batchProcess.getId()); Document document = batchProcessConfiguration.getDocument(); return newBatchProcessReader(document).getRecords(); } DocumentAlteration newDeleteBatchProcessesAlteration(final List<String> ids) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document).deleteBatchProcessesAlteration(ids); } }; } DocumentAlteration newAddBatchProcessDocumentAlteration(final String id, final String query, final String collection, final LocalDateTime requestDateTime, final int recordsCount, final BatchProcessAction action) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document).addBatchProcess(id, query, collection, requestDateTime, recordsCount, action); } }; } DocumentAlteration newAddBatchProcessDocumentAlteration(final String id, final String query, final String collection, final LocalDateTime requestDateTime, final int recordsCount, final BatchProcessAction action, final String username, final String title) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document) .addBatchProcess(id, query, collection, requestDateTime, recordsCount, action, username, title); } }; } DocumentAlteration newAddBatchProcessDocumentAlteration(final String id, final List<String> records, final String collection, final LocalDateTime requestDateTime, final int recordsCount, final BatchProcessAction action, final String username, final String title) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document) .addBatchProcess(id, records, collection, requestDateTime, recordsCount, action, username, title); } }; } DocumentAlteration markAllBatchProcessAsPendingDocumentAlteration() { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document).markAllBatchProcessAsPending(); } }; } DocumentAlteration markBatchProcessAsPendingDocumentAlteration(final String id) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document).markBatchProcessAsPending(id); } }; } DocumentAlteration cancelStandByBatchProcessDocumentAlteration(final String id) { return new DocumentAlteration() { @Override public void alter(Document document) { newBatchProcessListWriter(document).cancelStandByBatchProcess(id); } }; } String newBatchProcessId() { return UUID.randomUUID().toString(); } BatchProcessListReader newBatchProcessListReader(Document document) { return new BatchProcessListReader(document); } BatchProcessListWriter newBatchProcessListWriter(Document document) { return new BatchProcessListWriter(document); } BatchProcessReader newBatchProcessReader(Document document) { return new BatchProcessReader(document); } Document newDocument() { return new Document(); } void saveEmptyProcessListXMLDocument() { Document document = new Document(); BatchProcessListWriter writer = newBatchProcessListWriter(document); writer.createEmptyProcessList(); configManager.add(BATCH_PROCESS_LIST_PATH, document); } private void startNextBatchProcess(XMLConfiguration xmlConfiguration, Document processList) throws OptimisticLockingConfiguration { BatchProcessListWriter writer = newBatchProcessListWriter(processList); writer.startNextBatchProcess(getCurrentTime()); configManager.update(BATCH_PROCESS_LIST_PATH, xmlConfiguration.getHash(), processList); } public void registerBatchProcessesListUpdatedEvent(BatchProcessesListUpdatedEventListener listener) { listeners.add(listener); } @Override public void onConfigUpdated(String configPath) { for (BatchProcessesListUpdatedEventListener listener : listeners) { listener.onBatchProcessesListUpdated(); } } public List<BatchProcess> getAllNonFinishedBatchProcesses() { BatchProcessListReader reader = newBatchProcessListReader(getProcessListXMLDocument()); List<BatchProcess> batchProcesses = new ArrayList<>(); if (reader.readCurrent() != null) { batchProcesses.add(reader.readCurrent()); } batchProcesses.addAll(reader.readPendingBatchProcesses()); batchProcesses.addAll(reader.readStandbyBatchProcesses()); return batchProcesses; } @Override public void close() { } public void waitUntilAllFinished() { for (int i = 0; i < 10; i++) { for (BatchProcess batchProcess : getAllNonFinishedBatchProcesses()) { waitUntilFinished(batchProcess); } RecordsReindexingBackgroundAction recordsReindexingBackgroundAction = modelLayerFactory .getModelLayerBackgroundThreadsManager().getRecordsReindexingBackgroundAction(); if (recordsReindexingBackgroundAction != null) { while (searchServices.hasResults(fromEveryTypesOfEveryCollection().where(MARKED_FOR_REINDEXING).isTrue())) { recordsReindexingBackgroundAction.run(); } } } } public void waitUntilFinished(BatchProcess batchProcess) { while (get(batchProcess.getId()).getStatus() != BatchProcessStatus.FINISHED) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public void markAsFinished(final BatchProcess batchProcess, final int errorsCount) { configManager.updateXML(BATCH_PROCESS_LIST_PATH, new DocumentAlteration() { @Override public void alter(Document document) { new BatchProcessListWriter(document).markBatchProcessAsFinished(batchProcess, errorsCount); } }); } }