/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugin.pc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Classloader for the plugin jar itself and any embedded lib.
*/
// Note that this was an almost direct copy of the agent-side plugin container's PluginClassLoader at one time
public class ServerPluginClassLoader extends URLClassLoader {
private File embeddedJarsDirectory = null;
public ServerPluginClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void destroy() {
purge(embeddedJarsDirectory, true);
}
public static ServerPluginClassLoader create(String pluginJarName, URL pluginUrl, boolean unpackNestedJars,
ClassLoader parent, File tmpDirectory) throws Exception {
return create(pluginJarName, new URL[] { pluginUrl }, unpackNestedJars, parent, tmpDirectory);
}
public static ServerPluginClassLoader create(String pluginJarName, URL[] pluginUrls, boolean unpackNestedJars,
ClassLoader parent, File tmpDirectory) throws Exception {
List<URL> classpathUrlList = new ArrayList<URL>();
File unpackedDirectory = null;
for (URL pluginUrl : pluginUrls) {
classpathUrlList.add(pluginUrl);
if (unpackNestedJars) {
try {
unpackedDirectory = unpackEmbeddedJars(pluginJarName, pluginUrl, classpathUrlList, tmpDirectory);
} catch (Exception e) {
throw new Exception("Failed to unpack embedded JARs within: " + pluginUrl, e);
}
}
}
URL[] classpath = classpathUrlList.toArray(new URL[classpathUrlList.size()]);
ServerPluginClassLoader newLoader = new ServerPluginClassLoader(classpath, parent);
newLoader.embeddedJarsDirectory = unpackedDirectory;
return newLoader;
}
/**
* Unpacks all lib/* resources into a temporary directory, adds URLs to those newly extracted resources and returns
* the directory where the jars were extracted. This will actually create a unique subdirectory under the given
* <code>tmpDirectory</code>) which is where the extracted resources will be placed. If the give <code>
* tmpDirectory</code> is <code>null</code>, the standard platform's tmp directory will be used.
*
* @param pluginJarName name of the main plugin jar, used as part of the name to the tmp directory
* @param pluginUrl the URL to the main plugin jar we are unpacking
* @param urls the URLs to the tmp directory resources that were unpacked
* @param tmpDirectory the parent directory that will contain the child directory which will contain all extracted
* resources
*
* @return the location where all the extract files are now located
*
* @throws IOException
*/
private static File unpackEmbeddedJars(String pluginJarName, URL pluginUrl, List<URL> urls, File tmpDirectory)
throws IOException {
InputStream pluginStream = pluginUrl.openStream();
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(pluginStream));
ZipEntry entry;
File extractionDirectory = null; // this is where we will actually store the files we extract
try {
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
// Only care about entries in the lib directory
if (entryName.startsWith("lib") && (entryName.length() > 4)) {
if (extractionDirectory == null) {
extractionDirectory = createTempDirectory(tmpDirectory, pluginJarName);
}
int i = entryName.lastIndexOf('/');
if (i < 0) {
i = entryName.lastIndexOf('\\');
}
String s = entryName.substring(i + 1);
File file = null;
try {
if (s.endsWith(".jar")) {
file = File.createTempFile(s, null, extractionDirectory);
urls.add(file.toURI().toURL());
} else {
// All non-jar files are extracted as-is with the
// same filename.
file = new File(extractionDirectory, s);
// since we have a regular file, we need to make sure the tmp dir is in classpath so it can be found
URL tmpUrl = extractionDirectory.toURI().toURL();
if (!urls.contains(tmpUrl)) {
urls.add(tmpUrl);
}
}
FileOutputStream fileOutputStream;
try {
fileOutputStream = new FileOutputStream(file);
} catch (FileNotFoundException ex) {
if (file.exists() && (file.length() > 0)) {
// e.g. on win32, agent running w/ dll loaded PluginDumper cannot overwrite file inuse.
continue;
}
throw ex;
}
try {
BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream);
try {
file.deleteOnExit();
// do NOT close this inputStream since it is buffering the ZipInputStream
// and we are going to still process that input stream later. We close
// this ZipInputStream down below in the outer most try-finally block.
BufferedInputStream inputStream = new BufferedInputStream(zis);
int count;
byte[] b = new byte[8192];
while ((count = inputStream.read(b)) > -1) {
outputStream.write(b, 0, count);
}
} finally {
outputStream.close(); // this also closes the fileOutputStream
}
} finally {
fileOutputStream.close();
}
} catch (IOException ioe) {
if (file != null) {
file.delete();
}
throw ioe;
}
}
}
} finally {
zis.close();
}
return extractionDirectory;
}
private static File createTempDirectory(File tmpDirectory, String pluginName) throws IOException {
// Let's reuse the algorithm the JDK uses to determine a unique name:
// 1) create a temp file to get a unique name using JDK createTempFile
// 2) then quickly delete the file and...
// 3) convert it to a directory
File tmpDir = File.createTempFile(pluginName, ".classloader", tmpDirectory); // create file with unique name
boolean deleteOk = tmpDir.delete(); // delete the tmp file and...
boolean mkdirsOk = tmpDir.mkdirs(); // ...convert it to a directory
if (!deleteOk || !mkdirsOk) {
throw new IOException("Failed to create temp classloader directory named [" + tmpDir + "]");
}
tmpDir.deleteOnExit();
return tmpDir;
}
private void purge(File dir, boolean deleteIt) {
if (dir != null) {
if (dir.isDirectory()) {
File[] doomedFiles = dir.listFiles();
if (doomedFiles != null) {
for (File doomedFile : doomedFiles) {
purge(doomedFile, true); // call this method recursively
}
}
}
if (deleteIt) {
dir.delete();
}
}
return;
}
}