diff --git a/repository/src/main/java/org/alfresco/repo/download/ZipDownloadExporter.java b/repository/src/main/java/org/alfresco/repo/download/ZipDownloadExporter.java
index ccf5c93f34..5e999d39fc 100644
--- a/repository/src/main/java/org/alfresco/repo/download/ZipDownloadExporter.java
+++ b/repository/src/main/java/org/alfresco/repo/download/ZipDownloadExporter.java
@@ -1,329 +1,344 @@
-/*
- * #%L
- * Alfresco Repository
- * %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
- * %%
- * This file is part of the Alfresco software.
- * If the software was purchased under a paid Alfresco license, the terms of
- * the paid license agreement will prevail. Otherwise, the software is
- * provided under the following open source license terms:
- *
- * Alfresco is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Alfresco is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see .
- * #L%
- */
-package org.alfresco.repo.download;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.attribute.FileTime;
-import java.util.Date;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.LinkedList;
-
-import org.alfresco.model.ContentModel;
-import org.alfresco.repo.transaction.RetryingTransactionHelper;
-import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
-import org.alfresco.service.cmr.coci.CheckOutCheckInService;
-import org.alfresco.service.cmr.dictionary.DictionaryService;
-import org.alfresco.service.cmr.download.DownloadStatus;
-import org.alfresco.service.cmr.download.DownloadStatus.Status;
-import org.alfresco.service.cmr.repository.ContentData;
-import org.alfresco.service.cmr.repository.NodeRef;
-import org.alfresco.service.cmr.repository.NodeService;
-import org.alfresco.service.cmr.view.ExporterContext;
-import org.alfresco.service.cmr.view.ExporterException;
-import org.alfresco.service.namespace.QName;
-import org.alfresco.util.Pair;
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
-import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.UnicodeExtraFieldPolicy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Handler for exporting node content to a ZIP file
- *
- * @author Alex Miller
- */
-public class ZipDownloadExporter extends BaseExporter
-{
- private static Logger log = LoggerFactory.getLogger(ZipDownloadExporter.class);
-
- private static final String PATH_SEPARATOR = "/";
-
- protected ZipArchiveOutputStream zipStream;
-
- private NodeRef downloadNodeRef;
- private int sequenceNumber = 1;
- private long total;
- private long done;
- private long totalFileCount;
- private long filesAddedCount;
-
- private RetryingTransactionHelper transactionHelper;
- private DownloadStorage downloadStorage;
- private DictionaryService dictionaryService;
- private DownloadStatusUpdateService updateService;
-
- private Deque> path = new LinkedList>();
- private String currentName;
-
- private OutputStream outputStream;
- private Date zipTimestampCreated;
- private Date zipTimestampModified;
-
- /**
- * Construct
- *
- * @param zipFile File
- * @param checkOutCheckInService CheckOutCheckInService
- * @param nodeService NodeService
- * @param transactionHelper RetryingTransactionHelper
- * @param updateService DownloadStatusUpdateService
- * @param downloadStorage DownloadStorage
- * @param dictionaryService DictionaryService
- * @param downloadNodeRef NodeRef
- * @param total long
- * @param totalFileCount long
- */
- public ZipDownloadExporter(File zipFile, CheckOutCheckInService checkOutCheckInService, NodeService nodeService, RetryingTransactionHelper transactionHelper, DownloadStatusUpdateService updateService, DownloadStorage downloadStorage, DictionaryService dictionaryService, NodeRef downloadNodeRef, long total, long totalFileCount)
- {
- super(checkOutCheckInService, nodeService);
- try
- {
- this.outputStream = new FileOutputStream(zipFile);
- this.updateService = updateService;
- this.transactionHelper = transactionHelper;
- this.downloadStorage = downloadStorage;
- this.dictionaryService = dictionaryService;
-
- this.downloadNodeRef = downloadNodeRef;
- this.total = total;
- this.totalFileCount = totalFileCount;
- }
- catch (FileNotFoundException e)
- {
- throw new ExporterException("Failed to create zip file", e);
- }
- }
-
- @Override
- public void start(final ExporterContext context)
- {
- zipStream = new ZipArchiveOutputStream(outputStream);
- // NOTE: This encoding allows us to workaround bug...
- // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807
- zipStream.setEncoding("UTF-8");
- zipStream.setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy.ALWAYS);
- zipStream.setUseLanguageEncodingFlag(true);
- zipStream.setFallbackToUTF8(true);
- }
-
- @Override
- public void startNode(NodeRef nodeRef)
- {
- this.currentName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
- this.zipTimestampCreated = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED);
- this.zipTimestampModified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
- path.push(new Pair(currentName, nodeRef));
- if (dictionaryService.isSubClass(nodeService.getType(nodeRef), ContentModel.TYPE_FOLDER))
- {
- String path = getPath() + PATH_SEPARATOR;
- ZipArchiveEntry archiveEntry = new ZipArchiveEntry(path);
- try
- {
- archiveEntry.setTime(zipTimestampCreated.getTime());
- archiveEntry.setCreationTime(FileTime.fromMillis(zipTimestampCreated.getTime()));
- archiveEntry.setLastModifiedTime(FileTime.fromMillis(zipTimestampModified.getTime()));
- zipStream.putArchiveEntry(archiveEntry);
- zipStream.closeArchiveEntry();
- }
- catch (IOException e)
- {
- throw new ExporterException("Unexpected IOException adding folder entry", e);
- }
- }
- }
-
- @Override
- public void contentImpl(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index)
- {
- // if the content stream to output is empty, then just return content descriptor as is
- if (content == null)
- {
- return;
- }
-
- try
- {
- if (log.isDebugEnabled())
- {
- log.debug("Archiving content for nodeRef: "+nodeRef+" with contentURL: "+contentData.getContentUrl());
- }
- // ALF-2016
- ZipArchiveEntry zipEntry=new ZipArchiveEntry(getPath());
- zipEntry.setTime(zipTimestampCreated.getTime());
- zipEntry.setCreationTime(FileTime.fromMillis(zipTimestampCreated.getTime()));
- zipEntry.setLastModifiedTime(FileTime.fromMillis(zipTimestampModified.getTime()));
- zipStream.putArchiveEntry(zipEntry);
-
- // copy export stream to zip
- copyStream(zipStream, content);
-
- zipStream.closeArchiveEntry();
- filesAddedCount = filesAddedCount + 1;
- }
- catch (IOException e)
- {
- throw new ExporterException("Failed to zip export stream", e);
- }
- }
-
- @Override
- public void endNode(NodeRef nodeRef)
- {
- path.pop();
- }
-
- @Override
- public void end()
- {
- try
- {
- zipStream.close();
- }
- catch (IOException error)
- {
- throw new ExporterException("Unexpected error closing zip stream!", error);
- }
- }
-
- private String getPath()
- {
- if (path.size() < 1)
- {
- throw new IllegalStateException("No elements in path!");
- }
-
- Iterator> iter = path.descendingIterator();
- StringBuilder pathBuilder = new StringBuilder();
-
- while (iter.hasNext())
- {
- Pair element = iter.next();
-
- pathBuilder.append(element.getFirst());
- if (iter.hasNext())
- {
- pathBuilder.append(PATH_SEPARATOR);
- }
- }
-
- return pathBuilder.toString();
- }
-
- /**
- * Copy input stream to output stream
- *
- * @param output output stream
- * @param in input stream
- * @throws IOException
- */
- private void copyStream(OutputStream output, InputStream in)
- throws IOException
- {
- byte[] buffer = new byte[2048 * 10];
- int read = in.read(buffer, 0, 2048 *10);
- int i = 0;
- while (read != -1)
- {
- output.write(buffer, 0, read);
- done = done + read;
-
- // ALF-16289 - only update the status every 10MB
- if (i++%500 == 0)
- {
- updateStatus();
- checkCancelled();
- }
-
- read = in.read(buffer, 0, 2048 *10);
- }
- }
-
- private void checkCancelled()
- {
- boolean downloadCancelled = transactionHelper.doInTransaction(new RetryingTransactionCallback()
- {
- @Override
- public Boolean execute() throws Throwable
- {
- return downloadStorage.isCancelled(downloadNodeRef);
- }
- }, true, true);
-
- if ( downloadCancelled == true)
- {
- log.debug("Download cancelled");
- throw new DownloadCancelledException();
- }
- }
-
- private void updateStatus()
- {
- transactionHelper.doInTransaction(new RetryingTransactionCallback