/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.impl.util;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.utils.IOUtils;
import org.rioproject.deploy.DownloadRecord;
import org.rioproject.deploy.StagedData;
import org.rioproject.deploy.StagedSoftware;
import org.rioproject.deploy.StagedSoftware.PostInstallAttributes;
import org.rioproject.exec.ExecDescriptor;
import org.rioproject.impl.exec.ProcessManager;
import org.rioproject.impl.exec.ServiceExecutor;
import org.rioproject.impl.exec.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* The DownloadManager class provides support to manage the download and
* installation of artifacts
*
* @author Dennis Reedy
*/
public class DownloadManager {
/** The root directory to download the software */
private String installPath;
/** The Download */
private StagedData stagedData;
/** The DownloadRecord of the downloaded software */
private DownloadRecord downloadRecord;
/** The post-install DownloadRecord */
private DownloadRecord postInstallRecord;
/** The files extracted during post-install */
private List<File> postInstallExtractList;
private boolean showDownloadTo = true;
/** A suitable Logger */
private static final Logger logger = LoggerFactory.getLogger(DownloadManager.class.getName());
/**
* Create an instance of the DownloadManager
*
* @param stagedData The Download
*/
public DownloadManager(StagedData stagedData) {
if(stagedData == null)
throw new IllegalArgumentException("stagedData is null");
this.stagedData = stagedData;
}
/**
* Create an instance of the DownloadManager
*
* @param installPath The root directory to download the software. If the
* directory does not exist, it will be created. Read and write access
* permissions are required to the directory
* @param stagedData The Download
*/
public DownloadManager(String installPath, StagedData stagedData) {
if(installPath == null)
throw new IllegalArgumentException("installPath is null");
if(stagedData == null)
throw new IllegalArgumentException("stagedData is null");
this.installPath = installPath;
this.stagedData = stagedData;
}
/**
* Performs software download for a Download
*
* @return The DownloadRecord based on
* attributes from the downloaded software
*
* @throws IOException if there are errors accessing the file system
*/
public DownloadRecord download() throws IOException {
if(downloadRecord != null)
return (downloadRecord);
return doDownload(stagedData, false);
}
/**
* Whether to emit logger statements documenting where the data is being
* downloaded to
*
* @param show If true emit (default)
*/
public void setShowDownloadTo(boolean show) {
this.showDownloadTo= show;
}
/*
* Performs software stagedData for StagedData
*
* @param dAttrs The StagedData
* @param postInstall Whether this is for the post-install task
* @return The DownloadRecord based on
* attributes from the downloaded software.
*
* @throws IOException if there are errors accessing the file system
*/
private DownloadRecord doDownload(StagedData dAttrs, boolean postInstall) throws IOException {
String installRoot ;
int extractedSize = 0;
long extractTime = 0;
boolean unarchived = false;
boolean createdTargetPath = false;
if(dAttrs == null)
throw new IllegalArgumentException("dAttrs is null");
URL location = dAttrs.getLocationURL();
String extension = dAttrs.getInstallRoot();
boolean unarchive = dAttrs.unarchive();
if(extension.indexOf("/") != -1)
installRoot = extension.replace('/', File.separatorChar);
else
installRoot = extension.replace('\\', File.separatorChar);
File targetPath = new File(FileUtils.makeFileName(installPath, installRoot));
if(!targetPath.exists()) {
if(targetPath.mkdirs()) {
logger.trace("Created {}", targetPath.getPath());
}
if(!targetPath.exists())
throw new IOException("Failed to create: " + installPath);
createdTargetPath = true;
}
if(!targetPath.canWrite())
throw new IOException("Can not write to : " + installPath);
String source = location.toExternalForm();
int index = source.lastIndexOf("/");
if(index == -1)
throw new IllegalArgumentException("Don't know how to install : "+ source);
String software = source.substring(index + 1);
String target = FileUtils.getFilePath(targetPath);
File targetFile = new File(FileUtils.makeFileName(target, software));
if (targetFile.exists()) {
if(!dAttrs.overwrite()) {
logger.warn("{} exists, stagedData attributes indicate to not overwrite file",
FileUtils.getFilePath(targetFile));
return null;
} else {
if(showDownloadTo)
logger.info("Overwriting {} with {}", FileUtils.getFilePath(targetFile), location);
}
} else {
if(showDownloadTo)
logger.info("Downloading {} to {}", location, FileUtils.getFilePath(targetFile));
}
long t0 = System.currentTimeMillis();
URLConnection con = location.openConnection();
int downloadedSize = writeFileFromInputStream(con.getInputStream(),
targetFile,
con.getContentLength(),
System.console()!=null);
long t1 = System.currentTimeMillis();
long downloadTime = t1 - t0;
long downloadSecs = downloadTime/1000;
Date downloadDate = new Date();
ExtractResults results;
logger.info("Wrote {}K in {} seconds", (downloadedSize/1024), (downloadSecs<1?"< 1":downloadSecs));
String extractedToPath = null;
if(unarchive) {
t0 = System.currentTimeMillis();
results = extract(targetPath, targetFile);
t1 = System.currentTimeMillis();
extractedSize = results.extractedSize;
if(postInstall)
postInstallExtractList = results.postInstallExtractList;
extractTime = t1 - t0;
unarchived = true;
extractedToPath = results.extractedToPath;
if(extractedToPath==null) {
extractedToPath = FileUtils.getFilePath(targetPath);
}
}
downloadRecord = new DownloadRecord(location,
target,
software,
downloadDate,
downloadedSize,
extractedSize,
extractedToPath,
unarchived,
downloadTime,
extractTime);
downloadRecord.setCreatedParentDirectory(createdTargetPath);
return (downloadRecord);
}
/**
* Given an InputStream this method will write the contents to the desired
* File.
*
* @param in InputStream
* @param file The File object to write to
* @param total The total length
* @param show Whether to print out progress
*
* @return The size of what was written
*
* @throws IOException if there are errors accessing the file system
*/
private static int writeFileFromInputStream(InputStream in, File file, long total, boolean show) throws IOException {
int totalWrote = 0;
OutputStream out = null;
try {
out = new FileOutputStream(file);
int read;
byte[] buf = new byte[2048];
while((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
totalWrote += read;
if (show)
showTransferStatus(total, totalWrote);
}
if(show) {
String l = total >= 1024 ? ( total / 1024 ) + "K" : total + "b";
logger.info( l + " downloaded ("+file.getName()+")");
}
} catch(FileNotFoundException e) {
// catch so we can delete the file
if(file.delete()) {
logger.trace("Deleted {}", file.getName());
}
throw e;
} catch(IOException e) {
// catch so we can delete the file
if(file.delete()) {
logger.trace("Deleted {}", file.getName());
}
throw e;
} finally {
try {
if(in != null)
in.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(out != null)
out.close();
} catch(IOException e) {
e.printStackTrace();
}
}
return (totalWrote);
}
private static void showTransferStatus(long total, long complete) {
if (total >= 1024) {
System.out.print((complete/1024)+"/"+(total==-1?"?":(total/1024)+"K" )+"\r");
} else {
System.out.print(complete+"/"+(total==-1 ?"?":total+"b")+"\r" );
}
}
/**
* Extract an archive
*
* @param directory the directory to extract to
* @param archive The archive to extract
*
* @return An ExtractResults class detailing what was extracted
*
* @throws IOException if there are errors extracting the archive
*/
@SuppressWarnings("PMD.AvoidReassigningParameters")
public static ExtractResults extract(File directory, File archive) throws IOException {
String extractedToPath = null;
int extractSize = 0;
ZipFile zipFile = null;
List<File> extractList = new ArrayList<File>();
if(archive.getName().endsWith(".gz") ||
archive.getName().endsWith(".gzip")) {
archive = dealWithGZIP(archive);
}
if(archive.getName().endsWith("tar")) {
unTar(archive, directory);
return (new ExtractResults(extractedToPath,
extractSize,
extractList));
}
try {
zipFile = new ZipFile(archive);
Enumeration zipEntries = zipFile.entries();
while(zipEntries.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry)zipEntries.nextElement();
if(zipEntry.isDirectory()) {
File file = makeChildFile(directory, zipEntry.getName());
if(file.mkdirs()) {
logger.trace("Created {}", file.getPath());
}
if(extractedToPath==null) {
extractedToPath = getExtractedToPath(file, directory);
}
} else {
File file = makeChildFile(directory, zipEntry.getName());
//System.out.println("Writing : "+file.getCanonicalPath());
extractList.add(file);
String fullPath = FileUtils.getFilePath(file);
int index = fullPath.lastIndexOf(File.separatorChar);
String installPath = fullPath.substring(0, index);
File targetPath = new File(installPath);
if(!targetPath.exists()) {
if(targetPath.mkdirs()) {
logger.trace("Created {}", file.getPath());
}
if(!targetPath.exists())
throw new IOException("Failed to create : "+ installPath);
}
if(!targetPath.canWrite())
throw new IOException("Can not write to : "+ installPath);
InputStream in = zipFile.getInputStream(zipEntry);
extractSize += writeFileFromInputStream(in, file, archive.length(), false);
}
}
} finally {
if(zipFile != null)
zipFile.close();
}
return (new ExtractResults(extractedToPath, extractSize, extractList));
}
@SuppressWarnings("PMD.AvoidReassigningParameters")
private static String getExtractedToPath(File path, File rootDir) {
File parent;
do {
parent = path.getParentFile();
if(parent==null) {
logger.warn("No parent for {}", FileUtils.getFilePath(path));
break;
}
if(!parent.equals(rootDir))
path = parent;
} while(!parent.equals(rootDir));
return FileUtils.getFilePath(path);
}
private static File makeChildFile(File parent, String name) {
return new File(parent, name);
}
private static File dealWithGZIP(File gzip) throws IOException {
int ndx = gzip.getName().lastIndexOf(".");
File output = new File(gzip.getParentFile().getPath(),
gzip.getName().substring(0, ndx));
GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(gzip));
logger.info("Writing {} ...", FileUtils.getFilePath(output));
long t0 = System.currentTimeMillis();
int downloadedSize = writeFileFromInputStream(gzipInputStream, output, gzip.length(), System.console()!=null);
long t1 = System.currentTimeMillis();
long downloadTime = t1 - t0;
long downloadSecs = downloadTime/1000;
logger.info("Wrote {}K in {} millis", (downloadedSize/1024), (downloadSecs<1?"< 1":downloadSecs));
return output;
}
private static void unTar(File tarFile, File target) throws IOException {
InputStream is = new FileInputStream(tarFile);
ArchiveInputStream in;
try {
in = new ArchiveStreamFactory().createArchiveInputStream("tar", is);
} catch (ArchiveException e) {
IOException ioe = new IOException("Unarchving "+tarFile.getName());
ioe.initCause(e);
throw ioe;
}
try {
TarArchiveEntry entry;
while((entry = (TarArchiveEntry)in.getNextEntry())!=null) {
File f = new File(target, entry.getName());
if(entry.isDirectory()) {
if(f.mkdirs()) {
logger.trace("Created directory {}", f.getPath());
}
} else {
if(!f.getParentFile().exists()) {
if(f.getParentFile().mkdirs()) {
logger.trace("Created {}", f.getParentFile().getPath());
}
}
if(f.createNewFile()) {
logger.trace("Created {}", f.getName());
}
OutputStream out = new FileOutputStream(f);
IOUtils.copy(in, out);
out.close();
}
setPerms(f, entry.getMode());
}
} finally {
in.close();
}
}
private static void setPerms(File f, int mode) {
String jvmVersion = System.getProperty("java.version");
if(jvmVersion.contains("1.5"))
return;
int ownerPerm = mode >> 6 & 007;
int groupPerm = mode >> 3 & 007;
int userPerm = mode & 007;
/* Check read */
boolean ownerOnly = true;
if(userPerm>=4) {
userPerm-=4;
ownerOnly = false;
}
if(groupPerm>=4) {
groupPerm-=4;
ownerOnly = false;
}
if(ownerPerm>=4) {
ownerPerm-=4;
setFileAccess(f, "setReadable", true, ownerOnly);
}
/* Check write */
ownerOnly = true;
if(userPerm>=2) {
userPerm-=2;
ownerOnly = false;
}
if(groupPerm>=2) {
groupPerm-=2;
ownerOnly = false;
}
if(ownerPerm>=2) {
ownerPerm-=2;
setFileAccess(f, "setWritable", true, ownerOnly);
}
/* Check execute */
ownerOnly = true;
if(userPerm==1 || groupPerm==1)
ownerOnly = false;
if(ownerPerm==1) {
setFileAccess(f, "setExecutable", true, ownerOnly);
}
}
/*
* Use reflection so this can be compiled using 1.5
*/
private static void setFileAccess(File f,
String setter,
boolean allow,
boolean ownerOnly) {
try {
Method m = f.getClass().getMethod(setter, boolean.class, boolean.class);
m.invoke(f, allow, ownerOnly);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void removeExtractedTarFiles(File root, File tarFile) throws IOException {
InputStream is = new FileInputStream(tarFile);
ArchiveInputStream in;
try {
in = new ArchiveStreamFactory().createArchiveInputStream("tar", is);
} catch (ArchiveException e) {
IOException ioe = new IOException("Removing "+tarFile.getName());
ioe.initCause(e);
throw ioe;
}
File parent = null;
try {
TarArchiveEntry entry;
while((entry = (TarArchiveEntry)in.getNextEntry())!=null) {
File f = new File(root, entry.getName());
if(parent==null) {
parent = new File(getExtractedToPath(f, root));
}
FileUtils.remove(f);
}
} finally {
in.close();
}
FileUtils.remove(parent);
}
/*
* Container class holding results of the extract
*/
public static class ExtractResults {
String extractedToPath;
int extractedSize;
List<File> postInstallExtractList;
ExtractResults(String extractedToPath,
int extractedSize,
List<File>postInstallExtractList) {
this.extractedToPath = extractedToPath;
this.extractedSize = extractedSize;
this.postInstallExtractList = postInstallExtractList;
}
}
/**
* Perform post-install task(s) as described by the StagedSoftware object
*
* @return A DownloadRecord for the post
* install if software was downloaded to perform the post install task(s).
* If no software was downloaded to perform the task(s), return null
*
* @throws IOException if there are errors accessing the file system
*/
public DownloadRecord postInstall() throws IOException {
if(downloadRecord == null)
throw new IllegalStateException("software has not been downloaded");
if(!(stagedData instanceof StagedSoftware))
return null;
PostInstallAttributes postInstall =
((StagedSoftware) stagedData).getPostInstallAttributes();
if(postInstall == null)
return (null);
String path = downloadRecord.getPath();
try {
StagedData dAttrs = postInstall.getStagedData();
if(dAttrs != null) {
postInstallRecord = doDownload(dAttrs, true);
path = postInstallRecord.getPath();
}
ExecDescriptor execDesc = postInstall.getExecDescriptor();
if(execDesc != null) {
if(!execDesc.getCommandLine().startsWith(File.separator))
execDesc = Util.extendCommandLine(path, execDesc);
ServiceExecutor svcExecutor = new ServiceExecutor();
ProcessManager manager = svcExecutor.exec(execDesc);
manager.manage();
//manager.waitFor();
manager.destroy(false);
}
if(postInstall.getStagedData()!=null &&
postInstall.getStagedData().removeOnDestroy()) {
if(postInstallRecord != null) {
FileUtils.remove(new File(FileUtils.makeFileName(postInstallRecord.getPath(),
postInstallRecord.getName())));
}
if(postInstallExtractList != null) {
for (File file : postInstallExtractList)
FileUtils.remove(file);
}
}
} catch(IOException e) {
if(postInstallRecord != null)
remove(postInstallRecord);
throw e;
}
return (postInstallRecord);
}
/**
* Remove installed software
*/
public void remove() {
if(downloadRecord == null)
throw new IllegalStateException("software has not been downloaded");
remove(downloadRecord);
if(postInstallRecord != null)
remove(postInstallRecord);
}
/**
* Remove installed software
*
* @param record The DownloadRecord to remove
*
* @return the top-most directory/file that was removed
*/
public static String remove(DownloadRecord record) {
if(record == null)
throw new IllegalArgumentException("record is null");
File software = new File(FileUtils.makeFileName(record.getPath(),
record.getName()));
if(!software.exists()) {
logger.debug("Software recorded at [{}] does not exist or has already been removed "+
"from the file system, removal aborted", FileUtils.getFilePath(software));
return null;
}
String removed;
if(record.unarchived()) {
if(software.getName().endsWith(".gz") || software.getName().endsWith(".gzip")) {
FileUtils.remove(software);
/* Strip off the extension and see if we still have
* something to remove */
int ndx = software.getName().lastIndexOf(".");
if(ndx!=-1) {
String newName = software.getName().substring(0, ndx);
software = new File(FileUtils.makeFileName(record.getPath(), newName));
}
}
if(software.getName().endsWith("tar")) {
try {
File root = new File(record.getPath());
removeExtractedTarFiles(root, software);
} catch (IOException e) {
e.printStackTrace();
}
} else {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(software);
Enumeration zipEntries = zipFile.entries();
while(zipEntries.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry)zipEntries.nextElement();
File file = new File(record.getPath() + File.separator
+ zipEntry.getName());
FileUtils.remove(file);
}
} catch (ZipException e) {
logger.error("Error in opening zip file {}", FileUtils.getFilePath(software), e);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(zipFile!=null) {
try {
zipFile.close();
} catch (IOException e) {
logger.error("Could not close zip file", e);
}
}
}
}
removed = FileUtils.getFilePath(software);
FileUtils.remove(software);
File softwareDirectory = new File(record.getPath());
String[] list = softwareDirectory.list();
if(list.length == 0
&& !softwareDirectory.getName().equals("native"))
FileUtils.remove(softwareDirectory);
} else {
if(record.createdParentDirectory()) {
removed = record.getPath();
FileUtils.remove(new File(record.getPath()));
} else {
removed = FileUtils.getFilePath(software);
FileUtils.remove(software);
}
}
return removed;
}
public static void main(String args[]) {
try {
if(args.length < 2) {
System.out.println(
"Usage: org.rioproject.impl.util.DownloadManager " +
"download-URL install-root");
System.exit(-1);
}
String downloadFrom = args[0];
String installPath = args[1];
System.setSecurityManager(new java.rmi.RMISecurityManager());
StagedSoftware download = new StagedSoftware();
download.setLocation(downloadFrom);
download.setInstallRoot(installPath);
download.setUnarchive(true);
DownloadManager slm = new DownloadManager(installPath, download);
DownloadRecord record = slm.download();
System.out.println("Details");
System.out.println("-------");
System.out.println(record.toString());
//DownloadManager.remove(record);
} catch(Throwable throwable) {
throwable.printStackTrace();
}
}
}