package org.marketcetera.strategyagent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import org.marketcetera.module.RefreshListener;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* A classloader to help load classes / files from module jars and
* their default configuration files. This classloader is able to discover
* new jars when invoked and adds them to its search class path as they
* are discovered.
* The classloader is initialized with a root directory that contains two
* sub-directories, <code>jars</code> and <code>conf</code>.
* The classloader loads all the files available in the <code>conf</code>
* sub-directory. It loads the jar files directly contained within the
* <code>jars</code> sub-directory. Any file with a suffix <code>.jar</code>
* is considered a jar file, other files are ignored.
* <p>
* The classloader adds the jars found in the <code>jars</code> sub-directory
* and the <code>conf</code> sub-directory to its search path. Whenever
* {@link #refresh()} is invoked, the classloader lists the jars files
* contained within the <code>jars</code> sub-directory and adds any new ones
* to its search path.
* <p>
* The classloader sorts the files found in the <code>jars</code> sub-directory
* by their {@link java.io.File#getName() base names} using
* {@link String#compareTo(String)} before adding them.
* The files are sorted before adding both during initialization and
* refresh. Do note that files that are added during refresh are always
* after the files added during initialization and previous refreshes, even
* though their base names may be lexicographically before the files that
* are already there.
*
* @author anshul@marketcetera.com
*/
@ClassVersion("$Id: JarClassLoader.java 16841 2014-02-20 19:59:04Z colin $")
class JarClassLoader
extends URLClassLoader
implements RefreshListener
{
/**
* Creates an instance that loads classes from all the jars in the
* supplied directory. When refreshed, the classloader will discover
* any jars added to the directory and load classes from them.
*
* @param inAppInfoProvider a <code>StrategyAgentApplicationInfoProvider</code> value
* @param inParent the parent class loader
*
* @throws MalformedURLException if there were errors
* constructing URLs from file paths.
* @throws FileNotFoundException if the supplied jar directory does not
* exist or is inaccessible.
*/
public JarClassLoader(StrategyAgentApplicationInfoProvider inAppInfoProvider,
ClassLoader inParent)
throws MalformedURLException, FileNotFoundException
{
super(new URL[0], inParent);
Messages.LOG_JAR_LOADER_INIT.info(this, inAppInfoProvider);
if(!inAppInfoProvider.getModulesDir().isDirectory()) {
throw new FileNotFoundException(Messages.JAR_DIR_DOES_NOT_EXIST.getText(inAppInfoProvider.getModulesDir().getAbsolutePath()));
}
//Initialize the directory containing all the module jars
mJarDir = new File(inAppInfoProvider.getModulesDir(),
"jars"); //$NON-NLS-1$
if(!mJarDir.isDirectory()) {
throw new FileNotFoundException(Messages.JAR_DIR_DOES_NOT_EXIST.
getText(mJarDir.getAbsolutePath()));
}
//The directory containing the module default configuration
//properties files.
File jarConfDir = new File(inAppInfoProvider.getModulesDir(),
"conf"); //$NON-NLS-1$
if(!jarConfDir.isDirectory()) {
throw new FileNotFoundException(Messages.JAR_DIR_DOES_NOT_EXIST.getText(jarConfDir.getAbsolutePath()));
}
//Add the conf directory to the classloader
addURL(jarConfDir.toURI().toURL());
List<URL> urls = getJarURLs();
if (urls != null) {
for(URL url:urls) {
addURL(url);
}
}
}
/**
* Refreshes the Jar loader's list of jars that it loads classes from.
* It lists the jar files in the loader's directory and adds any new
* files to the set of files it loads classes from.
*
* @throws MalformedURLException if there were errors
* constructing URLs from file paths.
*
* @return if any jars were found and added to the classloader
*/
public boolean refresh() throws MalformedURLException {
Messages.LOG_REFRESH_JAR_LOADER.info(this);
List<URL> urlList = getJarURLs();
if (urlList != null && !urlList.isEmpty()) {
//Use linked hash set to preserve URL orderings.
Set<URL> found = new LinkedHashSet<URL>(urlList);
Set<URL> loaded = new HashSet<URL>(Arrays.asList(getURLs()));
//Find out all the jars that are not loaded.
found.removeAll(loaded);
if(!found.isEmpty()) {
//Add all the jars that were found but are not already loaded
for(URL url: found) {
addURL(url);
}
return true;
}
}
return false;
}
@Override
protected void addURL(URL url) {
super.addURL(url);
//Overridden to be able to log
Messages.LOG_JAR_LOADER_ADD_URL.info(this, url);
}
/**
* Returns the list of URLs of jar files found in the classloader's
* directory.
*
* @return the list of jar URLs, null if the contents of the directory
* could not be listed.
*
* @throws MalformedURLException if there were errors creating the URLs
*/
private List<URL> getJarURLs() throws MalformedURLException {
File[] jars = mJarDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(JAR_SUFFIX); //$NON-NLS-1$
}
});
//Sort the Files by their base names
Arrays.sort(jars, new Comparator<File>() {
public int compare(File inFile1, File inFile2) {
return inFile1.getName().compareTo(inFile2.getName());
}
});
List<URL> urls = null;
if (jars != null) {
urls = new ArrayList<URL>(jars.length);
for(File f: jars) {
urls.add(f.toURI().toURL());
}
}
return urls;
}
private File mJarDir;
private static final String JAR_SUFFIX = ".jar";
}