/*
|
* 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 legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* 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 legal-notices/CDDLv1_0.txt.
|
* 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-2014 ForgeRock AS
|
*/
|
|
package org.opends.quicksetup.util;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
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 org.forgerock.i18n.slf4j.LocalizedLogger;
|
|
/**
|
* Class for extracting the contents of a zip file and managing
|
* the reporting of progress during extraction.
|
*/
|
public class ZipExtractor {
|
|
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
|
|
/** 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 {
|
logger.warn(LocalizableMessage.raw(
|
"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) {
|
LocalizableMessage 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) {
|
LocalizableMessage 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) {
|
LocalizableMessage progressSummary =
|
INFO_PROGRESS_EXTRACTING.get(Utils.getPath(destination));
|
if (application.isVerbose())
|
{
|
application.notifyListenersWithPoints(ratioBeforeCompleted,
|
progressSummary);
|
}
|
else
|
{
|
application.notifyListenersRatioChange(ratioBeforeCompleted);
|
}
|
}
|
logger.info(LocalizableMessage.raw("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";
|
}
|
|
}
|