org.alfresco.repo.download.ZipDownloadExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.download.ZipDownloadExporter.java

Source

/*
 * #%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 <http://www.gnu.org/licenses/>.
 * #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.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.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 DownloadStatusUpdateService updateService;

    private Deque<Pair<String, NodeRef>> path = new LinkedList<Pair<String, NodeRef>>();
    private String currentName;

    private OutputStream outputStream;

    /**
     * Construct
     *
     * @param zipFile File
     * @param checkOutCheckInService CheckOutCheckInService
     * @param nodeService NodeService
     * @param transactionHelper RetryingTransactionHelper
     * @param updateService DownloadStatusUpdateService
     * @param downloadStorage DownloadStorage
     * @param downloadNodeRef NodeRef
     * @param total long
     * @param totalFileCount long
     */
    public ZipDownloadExporter(File zipFile, CheckOutCheckInService checkOutCheckInService, NodeService nodeService,
            RetryingTransactionHelper transactionHelper, DownloadStatusUpdateService updateService,
            DownloadStorage downloadStorage, 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.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);
        path.push(new Pair<String, NodeRef>(currentName, nodeRef));
        if (ContentModel.TYPE_FOLDER.equals(nodeService.getType(nodeRef))) {
            String path = getPath() + PATH_SEPARATOR;
            ZipArchiveEntry archiveEntry = new ZipArchiveEntry(path);
            try {
                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 {
            // ALF-2016
            ZipArchiveEntry zipEntry = new ZipArchiveEntry(getPath());
            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<Pair<String, NodeRef>> iter = path.descendingIterator();
        StringBuilder pathBuilder = new StringBuilder();

        while (iter.hasNext()) {
            Pair<String, NodeRef> 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<Boolean>() {
            @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<Object>() {
            @Override
            public Object execute() throws Throwable {
                DownloadStatus status = new DownloadStatus(Status.IN_PROGRESS, done, total, filesAddedCount,
                        totalFileCount);

                updateService.update(downloadNodeRef, status, getNextSequenceNumber());
                return null;
            }
        }, false, true);
    }

    public int getNextSequenceNumber() {
        return sequenceNumber++;
    }

    public long getDone() {
        return done;
    }

    public long getTotal() {
        return total;
    }

    public long getFilesAdded() {
        return filesAddedCount;
    }

    public long getTotalFiles() {
        return totalFileCount;
    }
}