/* * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Laurent Doguin */ package org.nuxeo.ecm.webapp.clipboard; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipOutputStream; import org.nuxeo.common.utils.StringUtils; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.LifeCycleConstants; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.impl.blob.FileBlob; import org.nuxeo.runtime.api.Framework; public class DocumentListZipExporter { public static final String ZIP_ENTRY_ENCODING_PROPERTY = "zip.entry.encoding"; public static enum ZIP_ENTRY_ENCODING_OPTIONS { ascii } private static final int BUFFER = 2048; private static final String SUMMARY_FILENAME = "INDEX.txt"; public Blob exportWorklistAsZip(List<DocumentModel> documents, CoreSession documentManager, boolean exportAllBlobs) throws IOException { StringBuilder blobList = new StringBuilder(); FileBlob blob = new FileBlob("zip"); try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(blob.getFile()))) { out.setMethod(ZipOutputStream.DEFLATED); out.setLevel(9); byte[] data = new byte[BUFFER]; for (DocumentModel doc : documents) { // first check if DM is attached to the core if (doc.getSessionId() == null) { // refetch the doc from the core doc = documentManager.getDocument(doc.getRef()); } // NXP-2334 : skip deleted docs if (LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { continue; } BlobHolder bh = doc.getAdapter(BlobHolder.class); if (doc.isFolder() && !isEmptyFolder(doc, documentManager)) { addFolderToZip("", out, doc, data, documentManager, blobList, exportAllBlobs); } else if (bh != null) { addBlobHolderToZip("", out, doc, data, blobList, bh, exportAllBlobs); } } if (blobList.length() > 1) { addSummaryToZip(out, data, blobList); } } catch (IOException cause) { blob.getFile().delete(); throw cause; } return blob; } private void addFolderToZip(String path, ZipOutputStream out, DocumentModel doc, byte[] data, CoreSession documentManager, StringBuilder blobList, boolean exportAllBlobs) throws IOException { String title = doc.getTitle(); List<DocumentModel> docList = documentManager.getChildren(doc.getRef()); for (DocumentModel docChild : docList) { // NXP-2334 : skip deleted docs if (LifeCycleConstants.DELETED_STATE.equals(docChild.getCurrentLifeCycleState())) { continue; } BlobHolder bh = docChild.getAdapter(BlobHolder.class); String newPath = null; if (path.length() == 0) { newPath = title; } else { newPath = path + "/" + title; } if (docChild.isFolder() && !isEmptyFolder(docChild, documentManager)) { addFolderToZip(newPath, out, docChild, data, documentManager, blobList, exportAllBlobs); } else if (bh != null) { addBlobHolderToZip(newPath, out, docChild, data, blobList, bh, exportAllBlobs); } } } private boolean isEmptyFolder(DocumentModel doc, CoreSession documentManager) { List<DocumentModel> docList = documentManager.getChildren(doc.getRef()); for (DocumentModel docChild : docList) { // If there is a blob or a folder, it is not empty. if (docChild.getAdapter(BlobHolder.class) != null || docChild.isFolder()) { return false; } } return true; } /** * Writes a summary file and puts it in the archive. */ private void addSummaryToZip(ZipOutputStream out, byte[] data, StringBuilder sb) throws IOException { Blob content = Blobs.createBlob(sb.toString()); BufferedInputStream buffi = new BufferedInputStream(content.getStream(), BUFFER); ZipEntry entry = new ZipEntry(SUMMARY_FILENAME); out.putNextEntry(entry); int count = buffi.read(data, 0, BUFFER); while (count != -1) { out.write(data, 0, count); count = buffi.read(data, 0, BUFFER); } out.closeEntry(); buffi.close(); } private void addBlobHolderToZip(String path, ZipOutputStream out, DocumentModel doc, byte[] data, StringBuilder blobList, BlobHolder bh, boolean exportAllBlobs) throws IOException { List<Blob> blobs = new ArrayList<Blob>(); if (exportAllBlobs) { if (bh.getBlobs() != null) { blobs = bh.getBlobs(); } } else { Blob mainBlob = bh.getBlob(); if (mainBlob != null) { blobs.add(mainBlob); } } if (blobs.size() > 0) { // add document info SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); if (path.length() > 0) { blobList.append(path).append('/'); } blobList.append(doc.getTitle()).append(" "); blobList.append(doc.getType()).append(" "); Calendar c = (Calendar) doc.getPropertyValue("dc:modified"); if (c != null) { blobList.append(format.format(c.getTime())); } blobList.append("\n"); } for (Blob content : blobs) { String fileName = content.getFilename(); if (fileName == null) { // use a default value fileName = "file.bin"; } BufferedInputStream buffi = new BufferedInputStream(content.getStream(), BUFFER); // Workaround to deal with duplicate file names. int tryCount = 0; String entryPath = null; String entryName = null; while (true) { try { ZipEntry entry = null; if (tryCount == 0) { entryName = fileName; } else { entryName = formatFileName(fileName, "(" + tryCount + ")"); } if (path.length() == 0) { entryPath = entryName; } else { entryPath = path + "/" + entryName; } entryPath = escapeEntryPath(entryPath); entry = new ZipEntry(entryPath); out.putNextEntry(entry); break; } catch (ZipException e) { tryCount++; } } blobList.append(" - ").append(entryName).append("\n"); int count = buffi.read(data, 0, BUFFER); while (count != -1) { out.write(data, 0, count); count = buffi.read(data, 0, BUFFER); } out.closeEntry(); buffi.close(); } } private String formatFileName(String filename, String count) { StringBuilder sb = new StringBuilder(); CharSequence name = filename.subSequence(0, filename.lastIndexOf(".")); CharSequence extension = filename.subSequence(filename.lastIndexOf("."), filename.length()); sb.append(name).append(count).append(extension); return sb.toString(); } protected String escapeEntryPath(String path) { String zipEntryEncoding = Framework.getProperty(ZIP_ENTRY_ENCODING_PROPERTY); if (zipEntryEncoding != null && zipEntryEncoding.equals(ZIP_ENTRY_ENCODING_OPTIONS.ascii.toString())) { return StringUtils.toAscii(path, true); } return path; } }