package com.constellio.data.io.services.zip;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.IOUtils;
import com.constellio.data.io.services.facades.IOServices;
import com.constellio.data.io.services.zip.ZipServiceException.CannotAddFileToZipException;
import com.constellio.data.io.services.zip.ZipServiceException.CannotCreateZipOutputStreamException;
import com.constellio.data.io.services.zip.ZipServiceException.FileToZipNotFound;
import com.constellio.data.io.services.zip.ZipServiceException.ZipFileCannotBeParsed;
import com.constellio.data.io.services.zip.ZipServiceException.ZipFileInvalidExtension;
import com.constellio.data.io.services.zip.ZipServiceException.ZippedFilesInDifferentParentFolder;
public class ZipService {
private static final String WRITE_CONTENT_TO_ZIP_FILE = "ZipService-WriteContentToZipFile";
private static final String UNZIP_FILE_ENTRY_TO_FILE = "ZipService-UnzipFileEntryToFile";
private static final String ZIP_FILE_STREAM = "ZipService-ZipFileStream";
private static final String[] VALID_ZIP_EXTENSIONS = { ".zip", ".war", ".jar" };
private final IOServices ioServices;
public ZipService(IOServices ioServices) {
this.ioServices = ioServices;
}
public void zip(File zipFile, List<File> zippedFiles)
throws ZipServiceException {
validateZipFileExtension(zipFile);
validateFilesInSameFolder(zippedFiles);
String parent = zippedFiles.get(0).getParentFile().getAbsolutePath();
ZipOutputStream zipOutputStream;
try {
zipOutputStream = newZipOutputStream(zipFile);
} catch (FileNotFoundException e) {
throw new ZipServiceException.ZipFileNotFound(zipFile, e);
}
try {
addFilestoZip(zippedFiles, zipOutputStream, parent);
} catch (CannotCreateZipOutputStreamException e) {
throw new CannotCreateZipOutputStreamException(zipFile, e);
} catch (IOException e) {
throw new ZipServiceRuntimeException(e);
} finally {
ioServices.closeQuietly(zipOutputStream);
}
}
private ZipOutputStream newZipOutputStream(File zipFile)
throws FileNotFoundException {
ZipOutputStream zipOutputStream;
OutputStream zipFileOutputStream = ioServices.newFileOutputStream(new File(zipFile.getPath()), WRITE_CONTENT_TO_ZIP_FILE);
zipOutputStream = new ZipOutputStream(zipFileOutputStream);
return zipOutputStream;
}
public void unzip(File zipFile, File zipFileContentDestinationDir)
throws ZipServiceException {
prevalidateZipFile(zipFile);
ZipFile zipFileObject = toZipFileObject(zipFile);
unzip(zipFile, zipFileObject, zipFileContentDestinationDir);
try {
zipFileObject.close();
} catch (IOException e) {
// This exception is thrown if the zip api is not used correctly
throw new ZipServiceRuntimeException.CannotUnzip(zipFile.getPath(), zipFileContentDestinationDir.getPath(), e);
}
}
public int size(File zipFile)
throws ZipServiceException {
ZipFile zippedFile = toZipFileObject(zipFile);
try {
return zippedFile.size();
} finally {
ioServices.closeQuietly(zippedFile);
}
}
public boolean contains(File zipFile, String filePath)
throws ZipServiceException {
ZipFile zippedFile = toZipFileObject(zipFile);
try {
return zippedFile.getEntry(getRelativePath(filePath, zipFile.getParent())) != null;
} finally {
ioServices.closeQuietly(zippedFile);
}
}
private void unzip(File zipFile, ZipFile zipFileObject, File zipFileContentDestinationDir)
throws ZipServiceException {
Enumeration<? extends ZipEntry> zipEntryEnum = zipFileObject.entries();
while (zipEntryEnum.hasMoreElements()) {
ZipEntry zipFileEntry = zipEntryEnum.nextElement();
try {
unzip(zipFileObject, zipFileEntry, zipFileContentDestinationDir);
} catch (IOException e) {
throw new ZipServiceException.ZipFileCorrupted(zipFile, zipFileEntry.getName(), e);
}
}
}
private void unzip(ZipFile zipFile, ZipEntry entry, File zipFileContentDestinationDir)
throws IOException {
if (entry.isDirectory()) {
new File(zipFileContentDestinationDir, entry.getName()).mkdirs();
} else {
File zipFileItemDestination = new File(zipFileContentDestinationDir, entry.getName());
zipFileItemDestination.getParentFile().mkdirs();
InputStream is = null;
OutputStream os = null;
try {
is = zipFile.getInputStream(entry);
os = ioServices.newFileOutputStream(zipFileItemDestination, UNZIP_FILE_ENTRY_TO_FILE);
IOUtils.copy(is, os);
} finally {
ioServices.closeQuietly(is);
ioServices.closeQuietly(os);
}
}
}
private ZipFile toZipFileObject(File zipFile)
throws ZipFileCannotBeParsed {
try {
return new ZipFile(zipFile);
} catch (ZipException e) {
throw new ZipServiceException.ZipFileCannotBeParsed(zipFile, e);
} catch (IOException e) {
throw new ZipServiceException.ZipFileCannotBeParsed(zipFile, e);
}
}
private void prevalidateZipFile(File zipFile)
throws ZipServiceException {
if (!zipFile.exists()) {
throw new ZipServiceException.ZipFileNotFound(zipFile, null);
}
if (zipFile.length() == 0) {
throw new ZipServiceException.ZipFileHasNoContent(zipFile);
}
}
private void validateZipFileExtension(File zipFile)
throws ZipFileInvalidExtension {
String fileName = zipFile.getName();
String fileExtension = null;
if (fileName.contains(".")) {
fileExtension = fileName.substring(fileName.lastIndexOf("."));
}
if (fileExtension == null || !Arrays.asList(VALID_ZIP_EXTENSIONS).contains(fileExtension)) {
throw new ZipServiceException.ZipFileInvalidExtension(fileExtension);
}
}
private void validateFilesInSameFolder(List<File> zippedFiles)
throws ZippedFilesInDifferentParentFolder, FileToZipNotFound {
File commonParentFile = null;
for (File zippedFile : zippedFiles) {
if (!zippedFile.exists()) {
throw new ZipServiceException.FileToZipNotFound(zippedFile);
}
if (commonParentFile == null) {
commonParentFile = zippedFile.getParentFile();
} else if (!commonParentFile.getAbsolutePath().equals(zippedFile.getParentFile().getAbsolutePath())) {
throw new ZipServiceException.ZippedFilesInDifferentParentFolder(commonParentFile, zippedFile.getParentFile());
}
}
}
private void addFilestoZip(List<File> zippedFiles, ZipOutputStream zipOutputStream, String parent)
throws IOException, ZipServiceException {
for (File zippedFile : zippedFiles) {
addFileToZip(zipOutputStream, parent, zippedFile);
}
}
private void addFileToZip(ZipOutputStream zipOutputStream, String parent, File zippedFile)
throws IOException, ZipServiceException {
try {
if (zippedFile.isDirectory()) {
File[] children = zippedFile.listFiles();
if(children.length == 0){
createEmptyDirectoryInZip(zippedFile, zipOutputStream, parent);
}else{
addFilestoZip(Arrays.asList(children), zipOutputStream, parent);
}
} else {
copyInputStreamToOutputStream(zippedFile, zipOutputStream, parent);
}
} catch (CannotAddFileToZipException e) {
throw new CannotAddFileToZipException(zippedFile, e);
}
}
protected void createEmptyDirectoryInZip(File zippedFile, ZipOutputStream zipOutputStream, String parent)
throws ZipServiceException, IOException {
prevalidateFile(zippedFile);
try {
zipOutputStream.putNextEntry(new ZipEntry(getRelativePath(zippedFile.getAbsolutePath(), parent) + "/"));
} catch (IOException e) {
throw new ZipServiceException.CannotAddFileToZipException(zippedFile, e);
} finally {
zipOutputStream.closeEntry();
}
}
private void copyInputStreamToOutputStream(File zippedFile, ZipOutputStream zipOutputStream, String parent)
throws IOException, ZipServiceException {
prevalidateFile(zippedFile);
InputStream fileInputStream = null;
try {
fileInputStream = ioServices.newFileInputStream(zippedFile, ZIP_FILE_STREAM);
zipOutputStream.putNextEntry(new ZipEntry(getRelativePath(zippedFile.getAbsolutePath(), parent)));
ioServices.copy(fileInputStream, zipOutputStream);
} catch (IOException e) {
throw new ZipServiceException.CannotAddFileToZipException(zippedFile, e);
} finally {
zipOutputStream.closeEntry();
ioServices.closeQuietly(fileInputStream);
}
}
private void prevalidateFile(File file)
throws ZipServiceException {
if (!file.exists()) {
throw new ZipServiceException.FileToZipNotFound(file);
}
}
private String getRelativePath(String filePath, String startPath) {
return filePath.replace(startPath + File.separator, "");
}
}