/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2007-2008 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.quicksetup.util;
import org.opends.messages.Message;
import static org.opends.messages.QuickSetupMessages.*;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.Application;
import org.opends.quicksetup.ReturnCode;
import java.io.*;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class for extracting the contents of a zip file and managing
* the reporting of progress during extraction.
*/
public class ZipExtractor {
static private final Logger LOG =
Logger.getLogger(ZipExtractor.class.getName());
/** Path separator for zip file entry names on Windows and *nix. */
static private final char ZIP_ENTRY_NAME_SEP = '/';
private InputStream is;
private int minRatio;
private int maxRatio;
private int numberZipEntries;
private String zipFileName;
private Application application;
/**
* Creates an instance of an ZipExtractor.
* @param zipFile File the zip file to extract
* @throws FileNotFoundException if the specified file does not exist
* @throws IllegalArgumentException if the zip file is not a zip file
*/
public ZipExtractor(File zipFile)
throws FileNotFoundException, IllegalArgumentException
{
this(zipFile, 0, 0, 1, null);
}
/**
* Creates an instance of an ZipExtractor.
* @param in InputStream for zip content
* @param zipFileName name of the input zip file
* @throws FileNotFoundException if the specified file does not exist
* @throws IllegalArgumentException if the zip file is not a zip file
*/
public ZipExtractor(InputStream in, String zipFileName)
throws FileNotFoundException, IllegalArgumentException
{
this(in, 0, 0, 1, zipFileName, null);
}
/**
* Creates an instance of an ZipExtractor.
* @param zipFile File the zip file to extract
* @param minRatio int indicating the max ration
* @param maxRatio int indicating the min ration
* @param numberZipEntries number of entries in the input stream
* @param app application to be notified about progress
* @throws FileNotFoundException if the specified file does not exist
* @throws IllegalArgumentException if the zip file is not a zip file
*/
public ZipExtractor(File zipFile, int minRatio, int maxRatio,
int numberZipEntries,
Application app)
throws FileNotFoundException, IllegalArgumentException
{
this(new FileInputStream(zipFile),
minRatio,
maxRatio,
numberZipEntries,
zipFile.getName(),
app);
if (!zipFile.getName().endsWith(".zip")) {
throw new IllegalArgumentException("File must have extension .zip");
}
}
/**
* Creates an instance of an ZipExtractor.
* @param is InputStream of zip file content
* @param minRatio int indicating the max ration
* @param maxRatio int indicating the min ration
* @param numberZipEntries number of entries in the input stream
* @param zipFileName name of the input zip file
* @param app application to be notified about progress
*/
public ZipExtractor(InputStream is, int minRatio, int maxRatio,
int numberZipEntries,
String zipFileName,
Application app) {
this.is = is;
this.minRatio = minRatio;
this.maxRatio = maxRatio;
this.numberZipEntries = numberZipEntries;
this.zipFileName = zipFileName;
this.application = app;
}
/**
* Performs the zip extraction.
* @param destination File where the zip file will be extracted
* @throws ApplicationException if something goes wrong
*/
public void extract(File destination) throws ApplicationException {
extract(Utils.getPath(destination));
}
/**
* Performs the zip extraction.
* @param destination File where the zip file will be extracted
* @throws ApplicationException if something goes wrong
*/
public void extract(String destination) throws ApplicationException {
extract(destination, true);
}
/**
* Performs the zip extraction.
* @param destDir String representing the directory where the zip file will
* be extracted
* @param removeFirstPath when true removes each zip entry's initial path
* when copied to the destination folder. So for instance if the zip entry's
* name was /OpenDJ-2.4.x/some_file the file would appear in the destination
* directory as 'some_file'.
* @throws ApplicationException if something goes wrong
*/
public void extract(String destDir, boolean removeFirstPath)
throws ApplicationException
{
ZipInputStream zipIn = new ZipInputStream(is);
int nEntries = 1;
/* This map is updated in the copyZipEntry method with the permissions
* of the files that have been copied. Once all the files have
* been copied to the file system we will update the file permissions of
* these files. This is done this way to group the number of calls to
* Runtime.exec (which is required to update the file system permissions).
*/
Map<String, ArrayList<String>> permissions =
new HashMap<String, ArrayList<String>>();
ArrayList<String> list = new ArrayList<String>();
list.add(destDir);
permissions.put(getProtectedDirectoryPermissionUnix(), list);
try {
if(application != null)
application.checkAbort();
ZipEntry entry = zipIn.getNextEntry();
while (entry != null) {
if(application != null)
application.checkAbort();
int ratioBeforeCompleted = minRatio
+ ((nEntries - 1) * (maxRatio - minRatio) / numberZipEntries);
int ratioWhenCompleted =
minRatio + (nEntries * (maxRatio - minRatio) /
numberZipEntries);
String name = entry.getName();
if (name != null && removeFirstPath) {
int sepPos = name.indexOf(ZIP_ENTRY_NAME_SEP);
if (sepPos != -1) {
name = name.substring(sepPos + 1);
} else {
LOG.log(Level.WARNING,
"zip entry name does not contain a path separator");
}
}
if (name != null && name.length() > 0) {
try {
File destination = new File(destDir, name);
copyZipEntry(entry, destination, zipIn,
ratioBeforeCompleted, ratioWhenCompleted, permissions);
} catch (IOException ioe) {
Message errorMsg =
Utils.getThrowableMsg(
INFO_ERROR_COPYING.get(entry.getName()), ioe);
throw new ApplicationException(
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
errorMsg, ioe);
}
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
nEntries++;
}
if (Utils.isUnix()) {
// Change the permissions for UNIX systems
for (String perm : permissions.keySet()) {
ArrayList<String> paths = permissions.get(perm);
try {
int result = Utils.setPermissionsUnix(paths, perm);
if (result != 0) {
throw new IOException("Could not set permissions on files "
+ paths + ". The chmod error code was: " + result);
}
} catch (InterruptedException ie) {
IOException ioe =
new IOException("Could not set permissions on files " +
paths + ". The chmod call returned an " +
"InterruptedException.");
ioe.initCause(ie);
throw ioe;
}
}
}
} catch (IOException ioe) {
Message errorMsg =
Utils.getThrowableMsg(
INFO_ERROR_ZIP_STREAM.get(zipFileName), ioe);
throw new ApplicationException(
ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
errorMsg, ioe);
}
}
/**
* Copies a zip entry in the file system.
* @param entry the ZipEntry object.
* @param destination File where the entry will be copied.
* @param is the ZipInputStream that contains the contents to be copied.
* @param ratioBeforeCompleted the progress ratio before the zip file is
* copied.
* @param ratioWhenCompleted the progress ratio after the zip file is
* copied.
* @param permissions an ArrayList with permissions whose contents will be
* updated.
* @throws IOException if an error occurs.
*/
private void copyZipEntry(ZipEntry entry, File destination,
ZipInputStream is, int ratioBeforeCompleted,
int ratioWhenCompleted, Map<String, ArrayList<String>> permissions)
throws IOException
{
if (application != null) {
Message progressSummary =
INFO_PROGRESS_EXTRACTING.get(Utils.getPath(destination));
if (application.isVerbose())
{
application.notifyListenersWithPoints(ratioBeforeCompleted,
progressSummary);
}
else
{
application.notifyListenersRatioChange(ratioBeforeCompleted);
}
}
LOG.log(Level.INFO, "extracting " + Utils.getPath(destination));
if (Utils.insureParentsExist(destination))
{
if (entry.isDirectory())
{
String perm = getDirectoryFileSystemPermissions(destination);
ArrayList<String> list = permissions.get(perm);
if (list == null)
{
list = new ArrayList<String>();
}
list.add(Utils.getPath(destination));
permissions.put(perm, list);
if (!Utils.createDirectory(destination))
{
throw new IOException("Could not create path: " + destination);
}
} else
{
String perm = Utils.getFileSystemPermissions(destination);
ArrayList<String> list = permissions.get(perm);
if (list == null)
{
list = new ArrayList<String>();
}
list.add(Utils.getPath(destination));
permissions.put(perm, list);
Utils.createFile(destination, is);
}
} else
{
throw new IOException("Could not create parent path: " + destination);
}
if (application != null) {
if (application.isVerbose())
{
application.notifyListenersDone(ratioWhenCompleted);
}
}
}
/**
* Returns the UNIX permissions to be applied to a protected directory.
* @return the UNIX permissions to be applied to a protected directory.
*/
private String getProtectedDirectoryPermissionUnix()
{
return "700";
}
/**
* Returns the file system permissions for a directory.
* @param path the directory for which we want the file permissions.
* @return the file system permissions for the directory.
*/
private String getDirectoryFileSystemPermissions(File path)
{
// TODO We should get this dynamically during build?
return "755";
}
}