package org.enhydra.shark.toolagent;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.StringTokenizer;
import org.enhydra.shark.api.internal.working.CallbackUtilities;
/**
* Use the static <code>load</code> method to load classes that implement tool
* agents from jars encountered in CLASSPATH like properties or located in a
* plugin directory.
*
* There a three properties that may be set in Shark's configuration file
* (conf/Shark.conf).
* <ol>
* <li><code>ToolAgentClassPath</code>: This property specifies a colon separated list of
* bytecode containers, i.e. jar files or directories containing class files.
* Tool agent implementations found in one of these bytecode containers are
* loaded by a single static class loader, i.e. they can not be reloaded during
* the lifetime of the engine.</li>
* <li><code>ToolAgentPluginPath</code>: This property specifies a colon separated list of
* bytecode containers, i.e. jar files or directories containing class files.
* Tool agent implementations found in one of these bytecode containers are
* loaded by individual class loaders, i.e. they are reloaded on each tool agent
* invocation.</li>
* <li><code>ToolAgentPluginDir</code>: This property specifies a directory that may contain
* jar files which are loaded as if they were encountered in the
* <code>ToolAgentPluginPath</code>.</li>
* </ol>
* Tool agent implementations are first looked up by the default class loader,
* then if not found by the static class loader according to the
* <code>ToolAgentClassPath</code> and finally by an individual class loader according to the
* <code>ToolAgentPluginPath</code>.
* <p>
* That means that if you want your tool agent implementation to be reloadable
* it must neither be encountered in the <code>ToolAgentClassPath</code> nor in the default
* <code>CLASSPATH</code>.
* <p>
* If any of the properties mentioned above do not exist or are empty they will
* not be used for the search.
* <p>
* Note that classes that depend on your tool agent implementation are normally
* loaded by the same class loader that looked up the tool agent's byte code.
* That means that you can make even these classes being reloaded on each tool
* agent invocation if the are encountered in the <code>ToolAgentPluginPath</code>.
* <p>
* This feature is nice in the phase of development as you need not restart the
* whole workflow engine after you modified your tool agent classes or those
* classes it depends on.
* <p>
* If you think your classes do not change anymore move them from the
* <code>ToolAgentPluginPath</code> to the <code>ToolAgentClassPath</code> to avoid unnecessary class
* loading operations.
*
* @author Dirk Hoffmann, H+BEDV (www.antivir.de)
*/
public class ToolAgentLoader {
private static ClassLoader staticClassLoader;
/**
* Load a tool agent class.
*
* @param cus
* callback utilities used to query shark's properties
* @param name
* name of the class
* @return the tool agent class
* @throws ClassNotFoundException
* if tool agent class could not be found.
* @throws IOException
* if plugin dir could not be listed
*/
public static Class load(CallbackUtilities cus, String name)
throws ClassNotFoundException, IOException {
/*
* Create the static URLClassLoader. Provide the class loader that
* loaded this class as the parent class loader (the one that uses
* CLASSPATH). This is necessary to allow tool agents to use common
* classes.
*/
ClassLoader staticCl = provideStaticClassLoader(cus,
ToolAgentLoader.class.getClassLoader());
/*
* Create the URLClassLoader. Provide the static class loader that
* loaded this class as the parent class loader.
*/
ClassLoader cl = provideDynamicClassLoader(cus, staticCl);
// Finally load class
return cl.loadClass(name);
}
/**
* Returns the one and only static class loader that loads classes according
* to the ToolAgentClassPath property such that they cannot be reloaded
* during the workflow engine's life time.
*
* @param cus
* callback utilities used to query shark's properties
* @param parentClassLoader
* the parent class loader for delegation
* @return the static class loader
* @throws IOException
*/
private static ClassLoader provideStaticClassLoader(CallbackUtilities cus,
ClassLoader parentClassLoader) throws IOException {
if (staticClassLoader == null) {
String toolAgentClassPathPropVal = cus.getProperty(
"ToolAgentClassPath", "");
URL[] toolAgentClassSearchList = makeSearchList(
toolAgentClassPathPropVal, null);
staticClassLoader = new URLClassLoader(toolAgentClassSearchList,
parentClassLoader);
}
return staticClassLoader;
}
/**
* Return a new class loader ready to load a tool agent class according to
* the ToolAgentPluginPath property or from the directory specified by the
* ToolAgentPluginDir property such that it can be reloaded on the next
* invocation.
*
* @param cus
* callback utilities used to query shark's properties
* @param parentClassLoader
* the parent class loader for delegation
* @return the dynamic class loader
* @throws IOException
*/
private static ClassLoader provideDynamicClassLoader(CallbackUtilities cus,
ClassLoader parentClassLoader) throws IOException {
String toolAgentPluginPathPropVal = cus.getProperty(
"ToolAgentPluginPath", "");
String toolAgentPluginDirPropVal = cus.getProperty(
"ToolAgentPluginDir", null);
URL[] toolAgentPluginSearchList = makeSearchList(
toolAgentPluginPathPropVal, toolAgentPluginDirPropVal);
ClassLoader cl = new URLClassLoader(toolAgentPluginSearchList,
parentClassLoader);
return cl;
}
/**
* @param classPath
* @param libDir
* @return
* @throws IOException
*/
private static URL[] makeSearchList(String classPath, String libDir)
throws IOException {
URL[] searchList;
// Convert ToolAgentPluginPath property into tokenizer.
StringTokenizer tk = new StringTokenizer(classPath, ":");
File[] libDirFiles;
if (libDir == null || libDir.length() == 0) {
libDirFiles = new File[0];
} else {
// Search the lib dir for jars and property files.
FilenameFilter jarAndPropFilesFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".jar")
|| name.endsWith(".properties");
}
};
libDirFiles = (new File(libDir)).listFiles(jarAndPropFilesFilter);
if (libDirFiles == null) {
throw new IOException("Could not list files in " + libDir);
}
}
// Create the search list which will be used by the URLClassLoader
searchList = new URL[libDirFiles.length + tk.countTokens()];
int i = 0;
// Add jars encountered in the class path to the search list.
for (; tk.hasMoreTokens(); ++i) {
searchList[i] = new URL("file:" + tk.nextToken());
}
// Add jars found in the lib dir to the search list.
for (; i < libDirFiles.length; ++i) {
// urls[i] = files[i].toURL();
// ,-----------------^^^^^^^
// `-- depends on the user.dir property which may be corrupted.
// Therefore we construct the URL using string methods.
searchList[i] = new URL("file:" + libDirFiles[i].getPath());
}
return searchList;
}
}
|