Skip to content

Commit

Permalink
Support included jars in java plugin jars, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed May 24, 2011
1 parent 4874dd6 commit 5a906ab
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

import com.dtolabs.rundeck.core.execution.service.ProviderCreationException;
import com.dtolabs.rundeck.core.execution.service.ProviderLoaderException;
import com.dtolabs.rundeck.core.utils.FileUtils;
import com.dtolabs.rundeck.core.utils.ZipUtil;
import com.dtolabs.rundeck.core.utils.cache.FileCache;
import org.apache.log4j.Logger;

import java.io.File;
Expand All @@ -33,6 +36,8 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
Expand All @@ -44,17 +49,19 @@
*
* @author Greg Schueler <a href="mailto:greg@dtosolutions.com">greg@dtosolutions.com</a>
*/
class JarPluginProviderLoader implements ProviderLoader {
class JarPluginProviderLoader implements ProviderLoader, FileCache.Expireable {
private static Logger log = Logger.getLogger(JarPluginProviderLoader.class.getName());
public static final String RUNDECK_PLUGIN_ARCHIVE = "Rundeck-Plugin-Archive";
public static final String RUNDECK_PLUGIN_CLASSNAMES = "Rundeck-Plugin-Classnames";
public static final String RUNDECK_PLUGIN_LIBS = "Rundeck-Plugin-Libs";
public static final String JAR_PLUGIN_VERSION = "1.0";
public static final String RUNDECK_PLUGIN_VERSION = "Rundeck-Plugin-Version";
private final File file;
private final File cachedir;
private Map<ProviderIdent, Class> pluginProviderDefs =
new HashMap<ProviderIdent, Class>();

public JarPluginProviderLoader(final File file) {
public JarPluginProviderLoader(final File file, final File cachedir) {
if (null == file) {
throw new NullPointerException("file");
}
Expand All @@ -65,6 +72,7 @@ public JarPluginProviderLoader(final File file) {
throw new IllegalArgumentException("Not a file: " + file);
}
this.file = file;
this.cachedir=cachedir;
}

/**
Expand All @@ -85,8 +93,8 @@ public synchronized <T> T load(final PluggableService<T> service, final String p
pluginProviderDefs.put(ident, cls);
}
} catch (PluginException e) {
log.warn(
"Failed to verify class from " + file + ": classname: " + classname + ": " + e.getMessage());
log.error(
"Failed to load class from " + file + ": classname: " + classname + ": " + e.getMessage());
}
}
}
Expand Down Expand Up @@ -118,17 +126,27 @@ static boolean matchesProviderDeclaration(final ProviderIdent ident, final Class
* Get the declared list of provider classnames for the file
*/
public String[] getClassnames() {
if (null == mainAttributes) {
mainAttributes = getJarMainAttributes(file);
final Attributes attributes = getMainAttributes();
if (null == attributes) {
return null;
}

final String value = mainAttributes.getValue(RUNDECK_PLUGIN_CLASSNAMES);
final String value = attributes.getValue(RUNDECK_PLUGIN_CLASSNAMES);
if (null == value) {
return null;
}
return value.split(",");
}

/**
* return the main attributes from the jar manifest
*/
private Attributes getMainAttributes() {
if (null == mainAttributes) {
mainAttributes = getJarMainAttributes(file);
}
return mainAttributes;
}

/**
* Get the main attributes for the jar file
*/
Expand Down Expand Up @@ -220,9 +238,29 @@ private Class loadClass(final String classname, final File file) throws PluginEx
debug("loadClass! " + classname + ": " + file);
final ClassLoader parent = JarPluginProviderLoader.class.getClassLoader();
final Class cls;

//if jar manifest declares secondary lib deps, expand lib into cachedir, and setup classloader to use the libs
Collection<File> extlibs=null;
try {
extlibs=extractDependentLibs();
} catch (IOException e) {
throw new PluginException("Unable to expand plugin libs: "+e.getMessage(), e);
}

try {
final URL url = file.toURI().toURL();
final URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{url}, parent);
final URL[] urlarray;
if(null!=extlibs && extlibs.size()>0){
final ArrayList<URL> urls = new ArrayList<URL>();
urls.add(url);
for (final File extlib : extlibs) {
urls.add(extlib.toURI().toURL());
}
urlarray = urls.toArray(new URL[urls.size()]);
}else {
urlarray = new URL[]{url};
}
final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urlarray, parent);
cls = Class.forName(classname, true, urlClassLoader);
classCache.put(classname, cls);
} catch (ClassNotFoundException e) {
Expand All @@ -235,6 +273,76 @@ private Class loadClass(final String classname, final File file) throws PluginEx
return cls;
}

/**
* Extract the dependent libs and return the extracted jar files
* @return the collection of extracted files
*/
private Collection<File> extractDependentLibs() throws IOException {
final Attributes attributes = getMainAttributes();
if (null == attributes) {
debug("no manifest attributes");
return null;
}

final ArrayList<File> files = new ArrayList<File>();
final String libs = attributes.getValue(RUNDECK_PLUGIN_LIBS);
if(null!=libs) {
debug("jar libs listed: " + libs + " for file: " + file);

final String[] libsarr = libs.split(" ");
final File cachedir = getFileCacheDir();
extractJarContents(libsarr, cachedir);
for (final String s : libsarr) {
files.add(new File(cachedir, s));
}
}else {
debug("no jar libs listed in manifest: " + file);
}
return files;
}

/**
* Extract specific entries from the jar to a destination directory. Creates the
* destination directory if it does not exist
* @param entries the entries to extract
* @param destdir destination directory
*/
private void extractJarContents(final String[] entries, final File destdir) throws IOException {
if (!destdir.exists()) {
if (!destdir.mkdir()) {
log.warn("Unable to create cache dir for plugin: " + destdir.getAbsolutePath());
}
}

debug("extracting lib files from jar: " + file);
for (final String path : entries) {
debug("Expand zip " + file.getAbsolutePath() + " to dir: " + destdir + ", file: " + path);
ZipUtil.extractZipFile(file.getAbsolutePath(), destdir, path);
}

}

/**
* Basename of the file
*/
String getFileBasename() {
return basename(file);
}

/**
* Get basename of a file
*/
private static String basename(final File file) {
final String name = file.getName();
return name.substring(0, name.lastIndexOf("."));
}

/**
* Get the cache dir for use for this file
*/
File getFileCacheDir() {
return new File(cachedir, getFileBasename());
}
/**
* Return true if the file has a class that provides the ident.
*/
Expand All @@ -252,6 +360,24 @@ public synchronized boolean isLoaderFor(final ProviderIdent ident) {
return false;
}

/**
* Remove any cache dir for the file
*/
private synchronized boolean removeScriptPluginCache() {
final File fileExpandedDir = getFileCacheDir();
if (null != fileExpandedDir && fileExpandedDir.exists()) {
debug("removeScriptPluginCache: " + fileExpandedDir);
return FileUtils.deleteDir(fileExpandedDir);
}
return true;
}
/**
* Expire the loader cache item
*/
public void expire() {
removeScriptPluginCache();
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ public boolean accept(final File file) {
return file.isFile() && file.getName().endsWith(".jar");
}
};
final File cachedir;

JarPluginScanner(final File extdir, final FileCache<ProviderLoader> filecache, final int rescanInterval) {
JarPluginScanner(final File extdir, final File cachedir, final FileCache<ProviderLoader> filecache, final int rescanInterval) {
super(extdir, filecache, rescanInterval);
this.cachedir = cachedir;
}

public boolean isValidPluginFile(final File file) {
Expand All @@ -63,7 +65,7 @@ public ProviderLoader createLoader(final File file) {
if (log.isDebugEnabled()) {
log.debug("create JarFileProviderLoader: " + file);
}
return new JarPluginProviderLoader(file);
return new JarPluginProviderLoader(file,cachedir);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private PluginManagerService(final File extdir, final File cachedir) {
final FileCache<ProviderLoader> filecache = new FileCache<ProviderLoader>();
cache = new FilePluginCache(filecache);
final int rescanInterval = 5000;//TODO: use framework property to set interval
cache.addScanner(new JarPluginScanner(extdir, filecache, rescanInterval));
cache.addScanner(new JarPluginScanner(extdir, cachedir, filecache, rescanInterval));
cache.addScanner(new ScriptPluginScanner(extdir, cachedir, filecache, rescanInterval));
log.debug("Create PluginManagerService");
}
Expand Down
20 changes: 20 additions & 0 deletions core/src/java/com/dtolabs/rundeck/core/utils/ZipUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ public static void extractZip(final String path, final File dest) throws IOExcep
public static void extractZip(final String path, final File dest, final String prefix) throws IOException {
extractZip(path, dest, prefix, null);
}
/**
* Extracts a single entry from the zip
*
* @param path zip file path
* @param dest destination directory
* @param fileName specific filepath to extract
*
* @throws IOException
*/
public static void extractZipFile(final String path, final File dest, final String fileName) throws IOException {
FilenameFilter filter = null;
if (null != fileName) {
filter = new FilenameFilter() {
public boolean accept(final File file, final String name) {
return fileName.equals(name) || fileName.startsWith(name);
}
};
}
extractZip(path, dest, filter, null, null);
}

/**
* Extract the zip file to the destination, optionally only the matching files and renaming the files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,37 +53,38 @@ public class TestJarPluginProviderLoader extends AbstractBaseTest {
private File testDir = new File("src/test/com/dtolabs/rundeck/core/plugins");
private File testJar1 = new File("src/test/com/dtolabs/rundeck/core/plugins/test-plugin1.jar");
private File testJarDNE = new File("src/test/com/dtolabs/rundeck/core/plugins/DNE-plugin.jar");
private File testCachedir;

public TestJarPluginProviderLoader(final String name) {
super(name);
}

public void setUp() {
super.setUp();

testCachedir = getFrameworkInstance().getLibextCacheDir();
}

public void testConstruct() throws Exception {
try {
new JarPluginProviderLoader(null);
new JarPluginProviderLoader(null,null);
fail("expected npe");
} catch (NullPointerException e) {
assertNotNull(e);
}
try {
new JarPluginProviderLoader(testJarDNE);
new JarPluginProviderLoader(testJarDNE,null);
fail("expected illegal argument");
} catch (IllegalArgumentException e) {
assertNotNull(e);
}
try {
new JarPluginProviderLoader(testDir);
new JarPluginProviderLoader(testDir,null);
fail("expected illegal argument");
} catch (IllegalArgumentException e) {
assertNotNull(e);
}
final File testJar = createTestJar(null, null);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar, testCachedir);
assertNotNull(jarPluginProviderLoader);
}

Expand Down Expand Up @@ -256,7 +257,7 @@ public void testLoadInvalid() throws Exception {
final File testJar11 = createTestJar(entries, null, classes);


final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11, testCachedir);
//non-existent
final JarTestType1 testx = jarPluginProviderLoader.load(service, "testX");
assertNull(testx);
Expand Down Expand Up @@ -289,7 +290,7 @@ public void testLoadValid() throws Exception {
final File testJar11 = createTestJar(entries, null, classes);


final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11, testCachedir);
//non-existent
final JarTestType1 testx = jarPluginProviderLoader.load(service, "testX");
assertNull(testx);
Expand Down Expand Up @@ -319,7 +320,7 @@ public boolean isValidProviderClass(Class clazz) {
final File testJar11 = createTestJar(entries, null, classes);


final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11, testCachedir);
//non-existent
final JarTestType1 testx = jarPluginProviderLoader.load(service, "testX");
assertNull(testx);
Expand Down Expand Up @@ -426,7 +427,7 @@ public void testJarClassnames() throws Exception {
entries.put(JarPluginProviderLoader.RUNDECK_PLUGIN_CLASSNAMES, classnameString(classes));

final File testJar11 = createTestJar(entries, null, classes);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11);
final JarPluginProviderLoader jarPluginProviderLoader = new JarPluginProviderLoader(testJar11, testCachedir);
final String[] classnames = jarPluginProviderLoader.getClassnames();
assertTrue(Arrays.equals(classnames(classes), classnames));
}
Expand Down

0 comments on commit 5a906ab

Please sign in to comment.