package com.constellio.model.services.search.zipContents; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasInExceptEvents; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.constellio.data.io.services.facades.IOServices; import com.constellio.data.io.services.zip.ZipService; import com.constellio.data.io.services.zip.ZipServiceException; 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.schemas.DataStoreField; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchema; import com.constellio.model.entities.schemas.MetadataValueType; import com.constellio.model.entities.schemas.Schemas; 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.schemas.MetadataList; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.schemas.SchemaUtils; 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 ZipContentsService { private static Logger LOGGER = Logger.getLogger(ZipContentsService.class); private static final String TMP_CONTENTS_FOLDER = "ZipContentsService-zipContentsOfRecords"; private static final String CONTENTS_FOLDER = "ZipContentsService-getContents"; ModelLayerFactory modelLayerFactory; ZipService zipService; RecordServices recordServices; SearchServices searchServices; ContentManager contentManager; private IOServices ioServices; private MetadataSchemasManager metadataSchemaManager; private String collection; ZipContentsService() { } public ZipContentsService(ModelLayerFactory modelLayerFactory, String collection) { this.modelLayerFactory = modelLayerFactory; this.zipService = modelLayerFactory.getIOServicesFactory().newZipService(); this.recordServices = modelLayerFactory.newRecordServices(); contentManager = modelLayerFactory.getContentManager(); this.collection = collection; metadataSchemaManager = modelLayerFactory.getMetadataSchemasManager(); this.ioServices = modelLayerFactory.getDataLayerFactory().getIOServicesFactory().newIOServices(); this.searchServices = modelLayerFactory.newSearchServices(); } public void zipContentsOfRecords(List<String> selectedRecordIds, File destinationFile) throws IOException { File newTempFolder = null; try { newTempFolder = ioServices.newTemporaryFile(TMP_CONTENTS_FOLDER); List<File> filesToZip = getContents(newTempFolder, selectedRecordIds); if (filesToZip.size() != 0) { zipService.zip(destinationFile, filesToZip); } else { throw new NoContentToZipRuntimeException(); } } catch (ZipServiceException e) { throw new RuntimeException(e); } finally { ioServices.deleteQuietly(newTempFolder); } } List<File> getContents(File newTempFolder, List<String> recordIds) throws IOException { List<File> returnFiles = new ArrayList<>(); recordIds = removeRedundantRecords(recordIds); List<RecordToZipNode> recordsToZipTrees = new ArrayList<>(); for (String recordId : recordIds) { Record record = recordServices.getDocumentById(recordId); List<RelatedContent> allRecordContents = getRecordContents(record); if (!allRecordContents.isEmpty()) { RecordToZipNode tree = generateTreeOfContents(record, allRecordContents); giveUniqueNamesInHierarchy(tree); recordsToZipTrees.add(tree); } } giveUniqueNameToRootNodes(recordsToZipTrees); for (RecordToZipNode tree : recordsToZipTrees) { saveTree(tree, newTempFolder); } String[] listOfFilesToZip = newTempFolder.list(); if (listOfFilesToZip == null || listOfFilesToZip.length == 0) { return returnFiles; } for (String filePath : listOfFilesToZip) { returnFiles.add(new File(newTempFolder.getPath() + File.separator + filePath)); } return returnFiles; } private void saveTree(RecordToZipNode node, File parentFolder) throws IOException { File nodeFile; if (node.isCanHaveChildren()) { nodeFile = new File(parentFolder, node.getUniqueNameInHierarchy()); } else { nodeFile = parentFolder; } for (NodeContent nodeContent : node.getContents()) { ContentVersion currentContentVersion = nodeContent.getContentCurrentVersion(); String contentTitle = nodeContent.getUniqueName(); InputStream contentInputStream = contentManager .getContentInputStream(currentContentVersion.getHash(), CONTENTS_FOLDER); File currentFile = new File(nodeFile, contentTitle); FileUtils.copyInputStreamToFile(contentInputStream, currentFile); ioServices.closeQuietly(contentInputStream); } for (RecordToZipNode child : node.getChildren()) { saveTree(child, nodeFile); } } private void giveUniqueNameToRootNodes(List<RecordToZipNode> recordsToZipTrees) { Set<String> topRedundantNames = getRedundantNamesFromRoots(recordsToZipTrees); for (RecordToZipNode node : recordsToZipTrees) { if (node.isCanHaveChildren()) { if (topRedundantNames.contains(node.getUniqueNameInHierarchy())) { node.setUniqueNameInHierarchy(true); } } else { String nodeRecordId = node.getRecordId(); for (NodeContent nodeContent : node.getContents()) { if (topRedundantNames.contains(nodeContent.getContentName())) { nodeContent.rename(nodeRecordId); } } } } } private Set<String> getRedundantNamesFromRoots(List<RecordToZipNode> recordsToZipTrees) { Set<String> rootNamesWithoutRedundancy = new HashSet<>(); List<String> allRootNames = new ArrayList<>(); for (RecordToZipNode node : recordsToZipTrees) { if (node.isCanHaveChildren()) { rootNamesWithoutRedundancy.add(node.getRecordName()); allRootNames.add(node.getRecordName()); } else { for (NodeContent nodeContent : node.getContents()) { rootNamesWithoutRedundancy.add(nodeContent.getContentName()); allRootNames.add(nodeContent.getContentName()); } } } return new HashSet<>(CollectionUtils.subtract(allRootNames, rootNamesWithoutRedundancy)); } private void giveUniqueNamesInHierarchy(RecordToZipNode node) { giveUniqueNameToContents(node); Set<String> childrenToRename = node.getRedundantChildrenNames(); for (RecordToZipNode child : node.getChildren()) { if (childrenToRename.contains(child.getRecordName())) { child.setUniqueNameInHierarchy(true); } else { child.setUniqueNameInHierarchy(false); } giveUniqueNamesInHierarchy(child); } } private void giveUniqueNameToContents(RecordToZipNode node) { Set<String> contentsToRename = node.getRedundantContentsNames(); Map<String, Integer> contentsNamesAndTheirCount = new HashMap<>(); for (NodeContent content : node.getContents()) { String currentName = content.getContentName(); if (contentsToRename.contains(currentName)) { Integer currentNameFoundCount = contentsNamesAndTheirCount.get(currentName); if (currentNameFoundCount == null) { currentNameFoundCount = 0; contentsNamesAndTheirCount.put(currentName, 1); } else { contentsNamesAndTheirCount.put(currentName, currentNameFoundCount + 1); } content.rename(currentNameFoundCount.toString()); } } } private RecordToZipNode generateTreeOfContents(Record record, List<RelatedContent> allRecordContents) { RecordToZipNode returnNode = newRecordToZipNode(record); String recordPrincipalPath = record.get(Schemas.PRINCIPAL_PATH); if (recordPrincipalPath == null) { putAllContentsInRootNode(returnNode, allRecordContents); } else { for (RelatedContent relatedContent : allRecordContents) { String relativePathInTree = getRelativePathInRecord(relatedContent.getContainerPrincipalPath(), recordPrincipalPath, record.getId()); putContentInAdequateNode(returnNode, relatedContent, relativePathInTree); } } return returnNode; } private void putAllContentsInRootNode(RecordToZipNode rootNode, List<RelatedContent> allRecordContents) { for (RelatedContent content : allRecordContents) { rootNode.addContent(content); } } //TODO test me private void putContentInAdequateNode(RecordToZipNode tree, RelatedContent relatedContent, String relativePathInTree) { String[] recordsIds = StringUtils.split(relativePathInTree, "/"); RecordToZipNode currentNode = tree; for (String recordId : recordsIds) { if (!currentNode.getRecordId().equals(recordId)) { currentNode = getOrCreateChild(currentNode, recordId); } } currentNode.addContent(relatedContent); } private RecordToZipNode getOrCreateChild(RecordToZipNode node, String childRecordId) { for (RecordToZipNode child : node.getChildren()) { if (child.getRecordId().equals(childRecordId)) { return child; } } Record childRecord = recordServices.getDocumentById(childRecordId); RecordToZipNode childNode = newRecordToZipNode(childRecord); childNode.setParent(node); node.addChild(childNode); return childNode; } private RecordToZipNode newRecordToZipNode(Record record) { String recordId = record.getId(); String recordName = record.get(Schemas.TITLE); boolean canHaveChildren = canHaveChildren( metadataSchemaManager.getSchemaTypes(collection).getSchema(record.getSchemaCode())); return new RecordToZipNode(recordId, recordName, canHaveChildren); } private List<String> removeRedundantRecords(List<String> recordIds) { RecordDescriptionList recordDescriptionList = getRecordsDescriptionList(recordIds); recordDescriptionList.removeSubRecords(); return recordDescriptionList.getRecordsIds(); } private RecordDescriptionList getRecordsDescriptionList(List<String> recordIds) { RecordDescriptionList returnList = new RecordDescriptionList(); List<Record> allRecords = recordServices.getRecordsById(collection, recordIds); for (Record record : allRecords) { String recordPrincipalPath = record.get(Schemas.PRINCIPAL_PATH); returnList.add(new RecordDescription(record.getId(), recordPrincipalPath)); } return returnList; } private List<RelatedContent> getRecordContents(Record record) { String recordPrincipalPath = record.get(Schemas.PRINCIPAL_PATH); if (StringUtils.isBlank(recordPrincipalPath)) { return getRecordDirectContents(record); } List<RelatedContent> returnList = new ArrayList<>(); List<Record> recordsWithContent = getRecordsHavingContentFromHierarchy(record); for (Record recordInHierarchy : recordsWithContent) { returnList.addAll(getRecordDirectContents(recordInHierarchy)); } return returnList; } private List<Record> getRecordsHavingContentFromHierarchy(Record record) { String recordPrincipalPath = record.get(Schemas.PRINCIPAL_PATH); List<DataStoreField> contentDataStoreFields = new ArrayList<>(); for (Metadata metadata : metadataSchemaManager.getSchemaTypes(collection).getAllContentMetadatas()) { contentDataStoreFields.add(metadata); } if (contentDataStoreFields.isEmpty()) { return new ArrayList<>(); } LogicalSearchCondition recordContentQuery = fromAllSchemasInExceptEvents(collection).where(Schemas.PRINCIPAL_PATH) .isContainingText(recordPrincipalPath).andWhereAny(contentDataStoreFields).isNotNull(); return searchServices.search(new LogicalSearchQuery(recordContentQuery)); } String getRelativePathInRecord(String subRecordPrincipalPath, String recordPrincipalPath, String parentId) { if (!subRecordPrincipalPath.contains(recordPrincipalPath) || subRecordPrincipalPath.equals(recordPrincipalPath)) { return parentId; } else { return parentId + StringUtils.removeStart(subRecordPrincipalPath, recordPrincipalPath); } } public boolean canHaveChildren(MetadataSchema schema) { String schemaType = new SchemaUtils().getSchemaTypeCode(schema.getCode()); return !metadataSchemaManager.getSchemaTypes(schema.getCollection()).getAllMetadatas() .onlyParentReferenceToSchemaType(schemaType).isEmpty(); } List<RelatedContent> getRecordDirectContents(Record record) { List<RelatedContent> returnList = new ArrayList<>(); MetadataSchema schema = metadataSchemaManager.getSchemaTypes(collection) .getSchema(record.getSchemaCode()); String containerRecordId, containerRecordPrincipalPath; if (canHaveChildren(schema)) { containerRecordId = record.getId(); containerRecordPrincipalPath = record.get(Schemas.PRINCIPAL_PATH); } else { containerRecordId = record.getParentId(); Record parentRecord = recordServices.getDocumentById(containerRecordId); containerRecordPrincipalPath = parentRecord.get(Schemas.PRINCIPAL_PATH); } for (Metadata metadata : new MetadataList(schema.getMetadatas()).onlyWithType(MetadataValueType.CONTENT)) { if (metadata.isMultivalue()) { List<Content> metadataContents = record.getList(metadata); for (Content metadataContent : metadataContents) { returnList.add(new RelatedContent(metadataContent, containerRecordPrincipalPath, containerRecordId)); } } else { Content metadataContent = record.get(metadata); if (metadataContent != null) { returnList.add(new RelatedContent(metadataContent, containerRecordPrincipalPath, containerRecordId)); } } } return returnList; } public static class NoContentToZipRuntimeException extends RuntimeException { } private class RecordDescription { String id; String principalPath; public RecordDescription(String id, String recordPrincipalPath) { this.id = id; this.principalPath = recordPrincipalPath; } public String getPrincipalPath() { return principalPath; } public String getId() { return id; } } private class RecordDescriptionList { List<RecordDescription> recordDescriptions = new ArrayList<>(); public void add(RecordDescription recordDescription) { this.recordDescriptions.add(recordDescription); } public void removeSubRecords() { CollectionUtils.filter(this.recordDescriptions, new Predicate() { @Override public boolean evaluate(Object object) { RecordDescription elementToEvaluate = (RecordDescription) object; if (elementToEvaluate.getPrincipalPath() == null) { return true; } for (RecordDescription recordDescription : recordDescriptions) { if (!elementToEvaluate.getId().equals(recordDescription.getId()) && elementToEvaluate.getPrincipalPath().contains(recordDescription.getPrincipalPath())) { return false; } } return true; } }); } public List<RecordDescription> getRecordDescriptionList() { return new ArrayList<>(recordDescriptions); } public List<String> getRecordsIds() { List<String> recordIds = new ArrayList<>(); for (RecordDescription recordDescription : this.recordDescriptions) { recordIds.add(recordDescription.getId()); } return recordIds; } } }