package com.constellio.app.modules.rm.services.decommissioning; import com.constellio.app.modules.rm.RMConfigs; import com.constellio.app.modules.rm.model.enums.DecommissioningListType; import com.constellio.app.modules.rm.model.enums.DecommissioningType; import com.constellio.app.modules.rm.model.enums.DisposalType; import com.constellio.app.modules.rm.model.enums.FolderStatus; import com.constellio.app.modules.rm.services.RMSchemasRecordsServices; import com.constellio.app.modules.rm.services.logging.DecommissioningLoggingService; import com.constellio.app.modules.rm.wrappers.ContainerRecord; import com.constellio.app.modules.rm.wrappers.DecommissioningList; import com.constellio.app.modules.rm.wrappers.Document; import com.constellio.app.modules.rm.wrappers.Folder; import com.constellio.app.modules.rm.wrappers.structures.DecomListContainerDetail; import com.constellio.app.modules.rm.wrappers.structures.DecomListFolderDetail; import com.constellio.app.services.factories.AppLayerFactory; import com.constellio.data.io.services.facades.FileService; import com.constellio.data.utils.ImpossibleRuntimeException; import com.constellio.model.entities.records.Content; import com.constellio.model.entities.records.ContentVersion; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.Transaction; import com.constellio.model.entities.records.wrappers.RecordWrapper; import com.constellio.model.entities.records.wrappers.User; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.services.contents.ContentConversionManager; import com.constellio.model.services.contents.ContentImpl; import com.constellio.model.services.contents.ContentManager; 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.search.SearchServices; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; import org.joda.time.LocalDate; import java.util.ArrayList; import java.util.List; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; public abstract class Decommissioner { protected final DecommissioningService decommissioningService; AppLayerFactory appLayerFactory; ModelLayerFactory modelLayerFactory; protected final RMSchemasRecordsServices rm; protected final RMConfigs configs; protected final SearchServices searchServices; protected final ContentManager contentManager; protected final DecommissioningLoggingService loggingServices; protected DecommissioningList decommissioningList; private ContentConversionManager conversionManager; private Transaction transaction; private List<Record> recordsToDelete; private LocalDate processingDate; private User user; public static Decommissioner forList(DecommissioningList decommissioningList, DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { switch (decommissioningList.getDecommissioningListType()) { case FOLDERS_TO_CLOSE: return new ClosingDecommissioner(decommissioningService, appLayerFactory); case FOLDERS_TO_TRANSFER: case DOCUMENTS_TO_TRANSFER: return new TransferringDecommissioner(decommissioningService, appLayerFactory); case FOLDERS_TO_DEPOSIT: case DOCUMENTS_TO_DEPOSIT: return decommissioningService.isSortable(decommissioningList) ? new SortingDecommissioner(decommissioningService, true, appLayerFactory) : new DepositingDecommissioner(decommissioningService, appLayerFactory); case FOLDERS_TO_DESTROY: case DOCUMENTS_TO_DESTROY: return decommissioningService.isSortable(decommissioningList) ? new SortingDecommissioner(decommissioningService, false, appLayerFactory) : new DestroyingDecommissioner(decommissioningService, appLayerFactory); } throw new ImpossibleRuntimeException("Unknown decommissioning type: " + decommissioningList.getDecommissioningListType()); } protected Decommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { this.decommissioningService = decommissioningService; this.appLayerFactory = appLayerFactory; this.modelLayerFactory = appLayerFactory.getModelLayerFactory(); rm = decommissioningService.getRMSchemasRecordServices(); configs = decommissioningService.getRMConfigs(); searchServices = modelLayerFactory.newSearchServices(); contentManager = modelLayerFactory.getContentManager(); loggingServices = new DecommissioningLoggingService(modelLayerFactory); } public void process(DecommissioningList decommissioningList, User user, LocalDate processingDate) { prepare(decommissioningList, user, processingDate); validate(); saveCertificates(decommissioningList); try (ContentConversionManager manager = new ContentConversionManager(modelLayerFactory)) { conversionManager = manager; if (decommissioningList.getDecommissioningListType().isFolderList()) { processFolders(); processContainers(); } else { processDocuments(); } } markProcessed(); execute(true); } public void approve(DecommissioningList decommissioningList, User user, LocalDate processingDate) { prepare(decommissioningList, user, processingDate); approveFolders(); markApproved(); execute(false); } protected void approveFolders() { for (DecomListFolderDetail detail : decommissioningList.getFolderDetails()) { if (detail.isFolderExcluded()) { continue; } Folder folder = rm.getFolder(detail.getFolderId()); add(folder.setPermissionStatus(getPermissionStatusFor(folder, detail))); } } protected abstract FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail); private void markApproved() { add(decommissioningList.setApprovalDate(processingDate).setApprovalUser(user)); } protected void removeManualArchivisticStatus(Folder folder) { folder.setManualArchivisticStatus(null); } protected LocalDate getProcessingDate() { return processingDate; } protected void add(RecordWrapper record) { transaction.addUpdate(record.getWrappedRecord()); } protected void delete(RecordWrapper record) { recordsToDelete.add(record.getWrappedRecord()); } private void validate() { if (!decommissioningService.isProcessable(decommissioningList, user)) { // TODO: Proper exception throw new RuntimeException("The decommissioning list cannot be processed"); } } private void prepare(DecommissioningList decommissioningList, User user, LocalDate processingDate) { this.decommissioningList = decommissioningList; this.processingDate = processingDate; this.user = user; transaction = new Transaction(); recordsToDelete = new ArrayList<>(); } private void saveCertificates(DecommissioningList decommissioningList) { if (!decommissioningList.getDecommissioningListType().isDestroyal()) { return; } FileService fileService = modelLayerFactory.getIOServicesFactory() .newFileService(); DecomCertificateService service = new DecomCertificateService(rm, searchServices, contentManager, fileService, user, decommissioningList, appLayerFactory); service.computeContents(); Content documentsContent = service.getDocumentsContent(); Content foldersContent = service.getFoldersContent(); decommissioningList.setDocumentsReportContent(documentsContent); decommissioningList.setFoldersReportContent(foldersContent); } private void processFolders() { DecommissioningListType decommissioningListType = decommissioningList.getDecommissioningListType(); for (DecomListFolderDetail detail : decommissioningList.getFolderDetails()) { if (detail.isFolderExcluded()) { continue; } Folder folder = rm.getFolder(detail.getFolderId()); preprocessFolder(folder, detail, decommissioningListType); processFolder(folder, detail); add(folder); } } protected abstract void processDocuments(); protected void preprocessFolder(Folder folder, DecomListFolderDetail detail, DecommissioningListType decommissioningListType) { if (folder.getCloseDateEntered() == null && DecommissioningListType.FOLDERS_TO_CLOSE.equals(decommissioningListType)) { folder.setCloseDateEntered(processingDate); } if (detail.getContainerRecordId() != null) { folder.setContainer(detail.getContainerRecordId()); } if (detail.getFolderLinearSize() != null) { folder.setLinearSize(detail.getFolderLinearSize()); } } protected abstract void processFolder(Folder folder, DecomListFolderDetail detail); protected void markFolderTransferred(Folder folder) { folder.setActualTransferDate(processingDate); } protected void markDocumentTransferred(Document document) { document.setActualTransferDateEntered(processingDate); } protected void markFolderDeposited(Folder folder) { folder.setActualDepositDate(processingDate); } protected void markDocumentDeposited(Document document) { document.setActualDepositDateEntered(processingDate); } protected void markFolderDestroyed(Folder folder) { folder.setActualDestructionDate(processingDate); } protected void markDocumentDestroyed(Document document) { document.setActualDestructionDateEntered(processingDate); } protected void removeFolderFromContainer(Folder folder) { String containerId = folder.getContainer(); if (containerId == null) { return; } folder.setContainer((String) null); for (DecomListContainerDetail detail : decommissioningList.getContainerDetails()) { if (detail.getContainerRecordId().equals(containerId)) { detail.setFull(false); } } } protected void cleanupDocumentsIn(Folder folder, boolean purgeMinorVersions, boolean createPDFa) { if (!purgeMinorVersions && !createPDFa) { return; } cleanupDocuments(getAllDocumentsInFolder(folder), purgeMinorVersions, createPDFa, null); } protected void cleanupDocuments( List<Document> documents, boolean purgeMinorVersions, boolean createPDFa, DocumentUpdater updater) { for (Document document : documents) { Content content = document.getContent(); if (content != null) { if (purgeMinorVersions) { content = purgeMinorVersions(content); } if (createPDFa && content != null) { content = createPDFa(content); loggingServices.logPdfAGeneration(document, user); } } if (updater != null) { updater.update(document); } add(document.setContent(content)); } } protected void destroyDocumentsIn(Folder folder) { destroyDocuments(getAllDocumentsInFolder(folder), null); } protected void destroyDocuments(List<Document> documents, DocumentUpdater updater) { for (Document document : documents) { if (document.getContent() != null) { destroyContent(document.getContent()); } if (updater != null) { updater.update(document); } add(document.setContent(null)); if (configs.deleteDocumentRecordsWithDestruction()) { delete(document); } } } public Content purgeMinorVersions(Content content) { List<ContentVersion> history = new ArrayList<>(); for (ContentVersion version : new ArrayList<>(content.getHistoryVersions())) { if (version.isMajor()) { history.add(version); } else { contentManager.silentlyMarkForDeletionIfNotReferenced(version.getHash()); } } ContentVersion current = content.getCurrentVersion(); if (!content.getCurrentVersion().isMajor() && configs.purgeCurrentVersionIfMinor()) { contentManager.silentlyMarkForDeletionIfNotReferenced(current.getHash()); if (history.isEmpty()) { return null; } int lastIndex = history.size() - 1; current = history.get(lastIndex); history.remove(lastIndex); } return ContentImpl.create(content.getId(), current, history); } private Content createPDFa(Content content) { return conversionManager.replaceContentByPDFA(content); } private void destroyContent(Content content) { for (ContentVersion version : new ArrayList<>(content.getHistoryVersions())) { contentManager.markForDeletionIfNotReferenced(version.getHash()); } contentManager.markForDeletionIfNotReferenced(content.getCurrentVersion().getHash()); } protected List<Document> getListDocuments() { LogicalSearchQuery query = new LogicalSearchQuery(from(rm.documentSchemaType()) .where(Schemas.IDENTIFIER).isIn(decommissioningList.getDocuments())); return rm.wrapDocuments(searchServices.search(query)); } private List<Document> getAllDocumentsInFolder(Folder folder) { LogicalSearchQuery query = new LogicalSearchQuery(from(rm.documentSchemaType()) .where(rm.documentFolder()).isEqualTo(folder)); return rm.wrapDocuments(searchServices.search(query)); } private void processContainers() { List<String> containerIdUsed = new ArrayList<>(); for (DecomListFolderDetail detail : decommissioningList.getFolderDetails()) { if (detail.isFolderExcluded()) { continue; } containerIdUsed.add(detail.getContainerRecordId()); } List<DecomListContainerDetail> containerDetails = decommissioningList.getContainerDetails(); for (DecomListContainerDetail detail : containerDetails) { if(containerIdUsed.contains(detail.getContainerRecordId())) { processContainer(rm.getContainerRecord(detail.getContainerRecordId()), detail); } else { decommissioningList.removeContainerDetail(detail.getContainerRecordId()); } } add(decommissioningList); } protected void updateContainer(ContainerRecord container, DecomListContainerDetail detail) { container.setDecommissioningType(getDecommissioningTypeForContainer()); if (detail.isFull()) { if (container.getCompletionDate() == null) { container.setCompletionDate(processingDate); } } else { container.setCompletionDate(null); } container.setFull(detail.isFull()); add(container); } protected abstract void processContainer(ContainerRecord container, DecomListContainerDetail detail); protected boolean isContainerEmpty(ContainerRecord container, List<String> destroyedFolders) { LogicalSearchCondition condition = from(rm.folder.schemaType()).where(rm.folder.container()).isEqualTo(container); if (!destroyedFolders.isEmpty()) { condition = condition.andWhere(Schemas.IDENTIFIER).isNotIn(destroyedFolders); } return searchServices.getResultsCount(condition) == 0; } protected DecommissioningType getDecommissioningTypeForContainer() { return decommissioningList.getDecommissioningListType().getDecommissioningType(); } private void markProcessed() { add(decommissioningList.setProcessingDate(processingDate).setProcessingUser(user)); } private void execute(boolean logging) { if (logging) { loggingServices.logDecommissioning(decommissioningList, user); } RecordServices recordServices = modelLayerFactory.newRecordServices(); if (!transaction.getRecordUpdateOptions().getTransactionRecordsReindexation().isReindexAll()) { transaction.removeUnchangedRecords(); } try { recordServices.execute(transaction); for (Record record : recordsToDelete) { recordServices.logicallyDelete(record, user); } contentManager.deleteUnreferencedContents(); } catch (RecordServicesException e) { // TODO: Proper exception throw new RuntimeException(e); } } public interface DocumentUpdater { void update(Document document); } } class ClosingDecommissioner extends Decommissioner { protected ClosingDecommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); } @Override protected FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail) { return folder.getPermissionStatus(); } @Override protected void processFolder(Folder folder, DecomListFolderDetail detail) { // No need to do anything here } @Override protected void processContainer(ContainerRecord container, DecomListContainerDetail detail) { // No need to do anything here } @Override protected void processDocuments() { // No need to do anything here } @Override protected void approveFolders() { // No need to do anything here } } class TransferringDecommissioner extends Decommissioner { protected TransferringDecommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); } @Override protected FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail) { return FolderStatus.SEMI_ACTIVE; } @Override protected void processFolder(Folder folder, DecomListFolderDetail detail) { removeManualArchivisticStatus(folder); markFolderTransferred(folder); processDocumentsIn(folder); } @Override protected void processContainer(ContainerRecord container, DecomListContainerDetail detail) { container.setRealTransferDate(getProcessingDate()); updateContainer(container, detail); } @Override protected void processDocuments() { cleanupDocuments(getListDocuments(), configs.purgeMinorVersionsOnTransfer(), configs.createPDFaOnTransfer(), new DocumentUpdater() { @Override public void update(Document document) { markDocumentTransferred(document); } }); } private void processDocumentsIn(Folder folder) { cleanupDocumentsIn(folder, configs.purgeMinorVersionsOnTransfer(), configs.createPDFaOnTransfer()); } } abstract class DeactivatingDecommissioner extends Decommissioner { protected List<String> destroyedFolders; protected DeactivatingDecommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); destroyedFolders = new ArrayList<>(); } protected void processDepositedFolder(Folder folder, DecomListFolderDetail detail) { markFolderDeposited(folder); processDocumentsInDeposited(folder); } protected void processDepositedContainer(ContainerRecord container, DecomListContainerDetail detail) { container.setRealDepositDate(getProcessingDate()); updateContainer(container, detail); } protected void processDeletedFolder(Folder folder, DecomListFolderDetail detail) { removeFolderFromContainer(folder); markFolderDestroyed(folder); //TODO // try { // modelLayerFactory.newRecordServices().update(folder); // } catch (RecordServicesException e) { // e.printStackTrace(); // } //add(folder); if (configs.deleteFolderRecordsWithDestruction()) { delete(folder); } destroyedFolders.add(folder.getId()); processDocumentsInDeleted(folder); } protected void processDeletedContainer(ContainerRecord container, DecomListContainerDetail detail) { if (isContainerEmpty(container, destroyedFolders)) { if (configs.isContainerRecyclingAllowed()) { add(decommissioningService.prepareToRecycle(container)); } else { delete(container); } } else { container.setFull(detail.isFull()); add(container); } } private void processDocumentsInDeposited(Folder folder) { cleanupDocumentsIn(folder, shouldPurgeMinorVersions(), shouldCreatePDFa()); } private void processDocumentsInDeleted(Folder folder) { destroyDocumentsIn(folder); } protected boolean shouldPurgeMinorVersions() { return decommissioningList.isFromActive() ? configs.purgeMinorVersionsOnTransfer() : configs.purgeMinorVersionsOnDeposit(); } protected boolean shouldCreatePDFa() { return decommissioningList.isFromActive() ? configs.createPDFaOnTransfer() : configs.createPDFaOnDeposit(); } } class DepositingDecommissioner extends DeactivatingDecommissioner { protected DepositingDecommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); } @Override protected FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail) { return FolderStatus.INACTIVE_DEPOSITED; } @Override protected void processFolder(Folder folder, DecomListFolderDetail detail) { removeManualArchivisticStatus(folder); processDepositedFolder(folder, detail); } @Override protected void processContainer(ContainerRecord container, DecomListContainerDetail detail) { processDepositedContainer(container, detail); } @Override protected void processDocuments() { cleanupDocuments(getListDocuments(), shouldPurgeMinorVersions(), shouldCreatePDFa(), new DocumentUpdater() { @Override public void update(Document document) { markDocumentDeposited(document); } }); } } class DestroyingDecommissioner extends DeactivatingDecommissioner { protected DestroyingDecommissioner(DecommissioningService decommissioningService, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); } @Override protected FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail) { return FolderStatus.INACTIVE_DESTROYED; } @Override protected void processFolder(Folder folder, DecomListFolderDetail detail) { removeManualArchivisticStatus(folder); processDeletedFolder(folder, detail); } @Override protected void processContainer(ContainerRecord container, DecomListContainerDetail detail) { processDeletedContainer(container, detail); } @Override protected void processDocuments() { destroyDocuments(getListDocuments(), new DocumentUpdater() { @Override public void update(Document document) { markDocumentDestroyed(document); } }); } } class SortingDecommissioner extends DeactivatingDecommissioner { private final boolean depositByDefault; protected SortingDecommissioner(DecommissioningService decommissioningService, boolean depositByDefault, AppLayerFactory appLayerFactory) { super(decommissioningService, appLayerFactory); this.depositByDefault = depositByDefault; } @Override protected FolderStatus getPermissionStatusFor(Folder folder, DecomListFolderDetail detail) { return shouldDeposit(folder, detail) ? FolderStatus.INACTIVE_DEPOSITED : FolderStatus.INACTIVE_DESTROYED; } @Override protected void processFolder(Folder folder, DecomListFolderDetail detail) { removeManualArchivisticStatus(folder); if (shouldDeposit(folder, detail)) { processDepositedFolder(folder, detail); } else { processDeletedFolder(folder, detail); } } @Override protected void processContainer(ContainerRecord container, DecomListContainerDetail detail) { if (isContainerEmpty(container, destroyedFolders)) { delete(container); } else { processDepositedContainer(container, detail); } } @Override protected void processDocuments() { // This is never called on documents for the time being } @Override protected DecommissioningType getDecommissioningTypeForContainer() { return DecommissioningType.DEPOSIT; } private boolean shouldDeposit(Folder folder, DecomListFolderDetail detail) { if (folder.getInactiveDisposalType() == DisposalType.SORT) { return depositByDefault != detail.isReversedSort(); } else { return depositByDefault; } } }