com.mirth.connect.util.ArchiveUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.util.ArchiveUtils.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

public class ArchiveUtils {
    /**
     * The buffer size to use when copying files into an archive
     */
    private final static int BUFFER_SIZE = 1048576;

    private static Logger logger = Logger.getLogger(ArchiveUtils.class);

    /**
     * Create an archive file from files/subfolders in a given source folder.
     * 
     * @param sourceFolder
     *            Subfolders and files inside this folder will be copied into the archive
     * @param destinationFile
     *            The destination archive file
     * @param archiver
     *            The archiver format, see
     *            org.apache.commons.compress.archivers.ArchiveStreamFactory
     * @throws CompressException
     */
    public static void createArchive(File sourceFolder, File destinationFile, String archiver)
            throws CompressException {
        createArchive(sourceFolder, destinationFile, archiver, null);
    }

    /**
     * Create an archive file from files/sub-folders in a given source folder.
     * 
     * @param sourceFolder
     *            Sub-folders and files inside this folder will be copied into the archive
     * @param destinationFile
     *            The destination archive file
     * @param archiver
     *            The archiver format, see
     *            org.apache.commons.compress.archivers.ArchiveStreamFactory
     * @param compressor
     *            The compressor format, see
     *            org.apache.commons.compress.compressors.CompressorStreamFactory
     * @throws CompressException
     */
    public static void createArchive(File sourceFolder, File destinationFile, String archiver, String compressor)
            throws CompressException {
        if (!sourceFolder.isDirectory() || !sourceFolder.exists()) {
            throw new CompressException("Invalid source folder: " + sourceFolder.getAbsolutePath());
        }

        logger.debug("Creating archive \"" + destinationFile.getAbsolutePath() + "\" from folder \""
                + sourceFolder.getAbsolutePath() + "\"");
        OutputStream outputStream = null;
        ArchiveOutputStream archiveOutputStream = null;

        try {
            /*
             * The commons-compress documentation recommends constructing a ZipArchiveOutputStream
             * with the archive file when using the ZIP archive format. See
             * http://commons.apache.org/proper/commons-compress/zip.html
             */
            if (archiver.equals(ArchiveStreamFactory.ZIP) && compressor == null) {
                archiveOutputStream = new ZipArchiveOutputStream(destinationFile);
            } else {
                // if not using ZIP format, use the archiver/compressor stream factories to initialize the archive output stream
                outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile));

                if (compressor != null) {
                    outputStream = new CompressorStreamFactory().createCompressorOutputStream(compressor,
                            outputStream);
                }

                archiveOutputStream = new ArchiveStreamFactory().createArchiveOutputStream(archiver, outputStream);
            }

            createFolderArchive(sourceFolder, archiveOutputStream,
                    sourceFolder.getAbsolutePath() + IOUtils.DIR_SEPARATOR);
        } catch (Exception e) {
            throw new CompressException(e);
        } finally {
            IOUtils.closeQuietly(archiveOutputStream);
            IOUtils.closeQuietly(outputStream);
            logger.debug("Finished creating archive \"" + destinationFile.getAbsolutePath() + "\"");
        }
    }

    /**
     * Recursively copies folders/files into the given ArchiveOutputStream.
     */
    private static void createFolderArchive(File folder, ArchiveOutputStream archiveOutputStream, String rootFolder)
            throws CompressException {
        byte[] buffer = new byte[BUFFER_SIZE];

        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                createFolderArchive(file, archiveOutputStream, rootFolder);
            } else {
                try {
                    // extract/remove the rootFolder from the file's absolute path before adding it to the archive
                    String entryName = file.getAbsolutePath();

                    if (entryName.substring(0, rootFolder.length()).equals(rootFolder)) {
                        entryName = entryName.substring(rootFolder.length());
                    }

                    archiveOutputStream.putArchiveEntry(archiveOutputStream.createArchiveEntry(file, entryName));
                    InputStream inputStream = new FileInputStream(file);

                    logger.debug("Adding \"" + entryName + "\" to archive");

                    try {
                        IOUtils.copyLarge(inputStream, archiveOutputStream, buffer);
                    } finally {
                        IOUtils.closeQuietly(inputStream);
                        archiveOutputStream.closeArchiveEntry();
                    }
                } catch (Exception e) {
                    throw new CompressException(e);
                }
            }
        }
    }

    /**
     * Extracts folders/files from an archive into the destinationFolder. Supports reading any type
     * of archive file supported by Apache's commons-compress.
     * 
     * @param archiveFile
     *            A compressed or uncompressed archive file
     * @param destinationFolder
     *            Destination folder, will be created if it doesn't exist.
     * @throws CompressException
     */
    public static void extractArchive(File archiveFile, File destinationFolder) throws CompressException {
        /**
         * Since we don't know what type of archive file we've received, try treating it as a zip
         * file first and extract using our zip-optimized extract method. If an exception occurs,
         * fall back to the generic method.
         */
        try {
            extractZipArchive(archiveFile, destinationFolder);
        } catch (CompressException e) {
            extractGenericArchive(archiveFile, destinationFolder);
        }
    }

    /**
     * Extracts an archive using generic stream factories provided by commons-compress.
     */
    private static void extractGenericArchive(File archiveFile, File destinationFolder) throws CompressException {
        try {
            InputStream inputStream = new BufferedInputStream(FileUtils.openInputStream(archiveFile));

            try {
                inputStream = new CompressorStreamFactory().createCompressorInputStream(inputStream);
            } catch (CompressorException e) {
                // a compressor was not recognized in the stream, in this case we leave the inputStream as-is
            }

            ArchiveInputStream archiveInputStream = new ArchiveStreamFactory()
                    .createArchiveInputStream(inputStream);
            ArchiveEntry entry;
            int inputOffset = 0;
            byte[] buffer = new byte[BUFFER_SIZE];

            try {
                while (null != (entry = archiveInputStream.getNextEntry())) {
                    File outputFile = new File(
                            destinationFolder.getAbsolutePath() + IOUtils.DIR_SEPARATOR + entry.getName());

                    if (entry.isDirectory()) {
                        FileUtils.forceMkdir(outputFile);
                    } else {
                        FileOutputStream outputStream = null;

                        try {
                            outputStream = FileUtils.openOutputStream(outputFile);
                            int bytesRead;
                            int outputOffset = 0;

                            while ((bytesRead = archiveInputStream.read(buffer, inputOffset, BUFFER_SIZE)) > 0) {
                                outputStream.write(buffer, outputOffset, bytesRead);
                                inputOffset += bytesRead;
                                outputOffset += bytesRead;
                            }
                        } finally {
                            IOUtils.closeQuietly(outputStream);
                        }
                    }
                }
            } finally {
                IOUtils.closeQuietly(archiveInputStream);
            }
        } catch (Exception e) {
            throw new CompressException(e);
        }
    }

    /**
     * Extracts folders/files from a zip archive using zip-optimized code from commons-compress.
     */
    private static void extractZipArchive(File archiveFile, File destinationFolder) throws CompressException {
        ZipFile zipFile = null;

        try {
            zipFile = new ZipFile(archiveFile);
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            ZipArchiveEntry entry = null;
            byte[] buffer = new byte[BUFFER_SIZE];

            for (; entries.hasMoreElements(); entry = entries.nextElement()) {
                File outputFile = new File(
                        destinationFolder.getAbsolutePath() + IOUtils.DIR_SEPARATOR + entry.getName());

                if (entry.isDirectory()) {
                    FileUtils.forceMkdir(outputFile);
                } else {
                    InputStream inputStream = zipFile.getInputStream(entry);
                    OutputStream outputStream = FileUtils.openOutputStream(outputFile);

                    try {
                        IOUtils.copyLarge(inputStream, outputStream, buffer);
                    } finally {
                        IOUtils.closeQuietly(inputStream);
                        IOUtils.closeQuietly(outputStream);
                    }
                }
            }
        } catch (Exception e) {
            throw new CompressException(e);
        } finally {
            ZipFile.closeQuietly(zipFile);
        }
    }

    public static class CompressException extends Exception {
        public CompressException(String message) {
            super(message);
        }

        public CompressException(Throwable cause) {
            super(cause);
        }
    }
}