/****************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
****************************************************************************/
package net.sourceforge.cruisecontrol.distributed.core;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import net.sourceforge.cruisecontrol.Builder;
import org.apache.log4j.Logger;
import net.sourceforge.cruisecontrol.util.Util;
public final class ZipUtil {
private static final Logger LOG = Logger.getLogger(ZipUtil.class);
private ZipUtil() { }
public static void zipFolderContents(final String outFilename, final String folderToZip) {
validateParams(outFilename, folderToZip);
BufferedOutputStream bos = null;
ZipOutputStream zipOut = null;
String errMsgsFromFinallyBlock = "";
Exception exceptionFromFinally = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(outFilename));
zipOut = new ZipOutputStream(bos);
final File folder = new File(folderToZip);
String message = "Zipping files from: " + folderToZip + " to: " + outFilename;
LOG.info(message);
zipFiles(folder, folder, zipOut);
message = "Finished zipping files";
LOG.info(message);
} catch (FileNotFoundException fnfe) {
final String message = "File not found while zipping files to: " + outFilename;
LOG.error(message, fnfe);
throw new RuntimeException(message, fnfe);
} finally {
try {
if (zipOut != null) {
zipOut.close();
}
} catch (ZipException ze) {
final File file = new File(outFilename);
if ((file.length() == 0) && (file.exists())) {
final String message = "Empty zip file created: " + outFilename;
LOG.debug(message);
try {
// this is required in order to close the file stream if zip is empty
bos.close();
} catch (IOException e) {
LOG.error(e);
}
// delete the empty zip file
if (!file.delete()) {
final String msg = "Error deleting empty zip file: " + file.getAbsolutePath();
// DO NOT throw exceptions from inside a finally block - bad things happen!
//throw new RuntimeException(msg);
LOG.error(msg);
errMsgsFromFinallyBlock += msg + "\n";
}
final String message2 = "Deleted empty zip file: " + outFilename;
LOG.debug(message2);
}
} catch (IOException ioe) {
final String message = "Error occured while closing zip file: " + outFilename;
LOG.error(message, ioe);
// DO NOT throw exceptions from inside a finally block - bad things happen!
//throw new RuntimeException(message, ioe);
errMsgsFromFinallyBlock += message + "\n";
exceptionFromFinally = ioe;
}
}
if (!"".equals(errMsgsFromFinallyBlock)) {
throw new RuntimeException(errMsgsFromFinallyBlock, exceptionFromFinally);
}
}
private static void zipFiles(final File rootDir, final File folderToZip, final ZipOutputStream zipOutputStream) {
final byte[] buf = new byte[1024];
final String relativePath = folderToZip.toString().substring(rootDir.toString().length());
FileInputStream in;
final File[] files = folderToZip.listFiles();
for (final File file : files) {
final String filename = file.getName();
if (file.isDirectory()) {
final String dirName = relativePath + File.separator + filename;
LOG.debug("adding dir [" + dirName + "]");
zipFiles(rootDir, file, zipOutputStream);
} else {
String filePath = relativePath + File.separator + filename;
if (filePath.charAt(0) == File.separatorChar) {
filePath = filePath.substring(1);
}
LOG.debug("adding file [" + filePath + "]");
try {
in = new FileInputStream(new File(folderToZip, filename));
try {
zipOutputStream.putNextEntry(new ZipEntry(filePath.replace(File.separatorChar, '/')));
int len;
while ((len = in.read(buf)) > 0) {
zipOutputStream.write(buf, 0, len);
}
zipOutputStream.closeEntry();
} finally {
in.close();
}
} catch (IOException ioe) {
final String message = "Error occured while zipping file " + filePath;
LOG.error(message, ioe);
throw new RuntimeException(message, ioe);
}
}
}
}
private static void validateParams(final String outFilename, final String folderToZip) {
if (outFilename == null) {
final String message = "Missing output zip file name";
LOG.error(message);
throw new IllegalArgumentException(message);
}
if (new File(outFilename).isDirectory()) {
final String message = "Output file already exists as directory";
LOG.error(message);
throw new IllegalArgumentException(message);
}
if (folderToZip == null) {
final String message = "Missing folder to zip";
LOG.error(message);
throw new IllegalArgumentException(message);
}
if (!(new File(folderToZip).isDirectory())) {
final String message = "Target folder to zip does not exist or is not a directory";
LOG.error(message);
throw new IllegalArgumentException(message);
}
}
/**
* @param zipFilePath the file to unzip
* @param toDirName the directory into which to put unziped contents
* @throws IOException if errors occur accessing files
*/
public static void unzipFileToLocation(final String zipFilePath, final String toDirName) throws IOException {
final ZipFile zipFile;
final Enumeration enumr;
boolean isEmptyFile = false;
try {
zipFile = new ZipFile(zipFilePath);
if (zipFile.size() == 0) {
isEmptyFile = true;
} else {
final String infoMessage = "Unzipping file: " + zipFilePath;
LOG.info(infoMessage);
enumr = zipFile.entries();
while (enumr.hasMoreElements()) {
final ZipEntry target = (ZipEntry) enumr.nextElement();
final String message = "Exploding: " + target.getName();
LOG.debug(message);
saveItem(zipFile, toDirName, target);
}
zipFile.close();
}
} catch (FileNotFoundException fnfe) {
final String message = "Could not find zip file" + zipFilePath;
LOG.error(message, fnfe);
throw new RuntimeException(message, fnfe);
} catch (ZipException ze) {
final String message = "Zip error occured while unzipping file " + zipFilePath;
LOG.error(message, ze);
throw new RuntimeException(message, ze);
} catch (IOException ioe) {
final String message = "Error occured while unzipping file " + zipFilePath;
LOG.error(message, ioe);
throw new RuntimeException(message, ioe);
}
if (isEmptyFile) {
final String message = "Zip file has no entries: " + zipFilePath;
LOG.warn(message);
throw new IOException(message);
}
final String infoMessage = "Unzip complete";
LOG.info(infoMessage);
}
private static void saveItem(final ZipFile zipFile, final String rootDirName, final ZipEntry entry)
throws IOException {
final InputStream is;
BufferedInputStream inStream = null;
final FileOutputStream outStream;
BufferedOutputStream bufferedOutStream = null;
try {
final File file = new File(rootDirName, entry.getName());
if (entry.isDirectory()) {
Util.doMkDirs(file);
} else {
is = zipFile.getInputStream(entry);
inStream = new BufferedInputStream(is);
final File dir = new File(file.getParent());
Util.doMkDirs(dir);
outStream = new FileOutputStream(file);
bufferedOutStream = new BufferedOutputStream(outStream);
int c;
while ((c = inStream.read()) != -1) {
bufferedOutStream.write((byte) c);
}
}
} catch (ZipException ze) {
final String message = "Zip error unzipping entry: " + entry.getName();
LOG.error(message, ze);
throw new RuntimeException(message, ze);
} catch (IOException ioe) {
final String message = "I/O error unzipping entry: " + entry.getName();
LOG.error(message, ioe);
throw new RuntimeException(message, ioe);
} finally {
if (bufferedOutStream != null) {
bufferedOutStream.close();
}
if (inStream != null) {
inStream.close();
}
}
}
/**
* Create a unique temp file for results to avoid collisions between concurrent builds.
* @param workDir directory in which to create the temp file
* @param projectName the project name being built
* @param resultsType the type of results
* @return a unique zip file to be filled with the content for the given results type
*/
public static File getTempResultsZipFile(final File workDir, final String projectName, final String resultsType) {
final File tempResultsFile;
try {
tempResultsFile = File.createTempFile(Builder.getFileSystemSafeProjectName(projectName)
+ "-" + resultsType + "-", ".zip", workDir)
.getCanonicalFile();
} catch (IOException e) {
final String message = "Couldn't create temp " + resultsType + " results zip file in: "
+ workDir.getAbsolutePath();
LOG.error(message, e);
throw new RuntimeException(message);
}
return tempResultsFile;
}
}