// Copyright 2012 Georg-August-Universität Göttingen, Germany // // 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 de.ugoe.cs.autoquest.plugin; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.jar.JarInputStream; import java.util.jar.Manifest; /** *

* This class provides the functionality to load AutoQUEST plug-ins from a * pre-defined folder. *

* * @author Steffen Herbold * @version 1.0 */ public class PluginLoader { /** *

* Handle of the plug-in directory. *

*/ private final File pluginDir; /** *

* Collection of the loaded plug-ins. *

*/ private final Collection plugins; /** *

* Constructor. Creates a new PluginLoader that can load plug-ins the * defined directory. *

* * @param pluginDir * handle of the directory; in case the handle is * null or does not describe a directory, an * {@link IllegalArgumentException} is thrown */ public PluginLoader(File pluginDir) { if (pluginDir == null) { throw new IllegalArgumentException( "Parameter pluginDir must not be null!"); } if (!pluginDir.isDirectory()) { throw new IllegalArgumentException("File " + pluginDir.getPath() + " is not a directory"); } this.pluginDir = pluginDir; plugins = new LinkedList(); } /** *

* Loads plug-ins from {@link #pluginDir}. *

* * @throws PluginLoaderException * thrown if there is a problem loading a plug-in or updating * the classpath */ public void load() throws PluginLoaderException { File[] jarFiles = pluginDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return checkNameConformity(name); } }); for (File jarFile : jarFiles) { updateClassLoader(jarFile); String pluginName = jarFile.getName().split("-")[2]; String pluginClassName = "de.ugoe.cs.autoquest.plugin." + pluginName + "." + pluginName.toUpperCase() + "Plugin"; Class pluginClass = null; try { pluginClass = Class.forName(pluginClassName); } catch (ClassNotFoundException e) { throw new PluginLoaderException("No class '" + pluginClassName + "' found in " + pluginDir + "/" + jarFile.getName()); } try { AutoQUESTPlugin pluginObject = (AutoQUESTPlugin) pluginClass .newInstance(); plugins.add(pluginObject); } catch (InstantiationException e) { throw new PluginLoaderException("Could not instantiate " + pluginClassName); } catch (IllegalAccessException e) { throw new PluginLoaderException("Could not access " + pluginClassName); } catch (ClassCastException e) { throw new PluginLoaderException("Class " + pluginClassName + " not instance of AutoQUESTPlugin"); } } } /** *

* Retrieves the classpath from a Jar file's MANIFEST. *

* * @throws IOException * @throws FileNotFoundException */ protected String[] getClassPathFromJar(File jarFile) { String[] classPath; JarInputStream jarInputStream = null; Manifest manifest = null; try { FileInputStream fileStream = new FileInputStream(jarFile); try { jarInputStream = new JarInputStream(fileStream); manifest = jarInputStream.getManifest(); } finally { jarInputStream.close(); fileStream.close(); } } catch (FileNotFoundException e) { throw new AssertionError( "FileNotFoundException should be impossible!"); } catch (IOException e) { throw new PluginLoaderException(e); } String jarClassPath = manifest.getMainAttributes().getValue( "Class-Path"); if (jarClassPath != null) { String[] jarClassPathElements = jarClassPath.split(" "); classPath = new String[jarClassPathElements.length]; for (int i = 0; i < jarClassPathElements.length; i++) { classPath[i] = "file:" + jarFile.getParentFile().getAbsolutePath() + "/" + jarClassPathElements[i]; } try { jarInputStream.close(); } catch (IOException e) { throw new PluginLoaderException(e); } } else { classPath = new String[] {}; } return classPath; } /** *

* Updates the classpath of the {@link ClassLoader} to include the plug-in * jar as well as further libraries required by the plug-in jar as defined * in the Class-Path section of its manifest. *

* * @param jarFile * handle of the plug-in jar file * @throws PluginLoaderException * thrown if there is a problem updating the class loader or * loading the plug-in jar */ private void updateClassLoader(File jarFile) throws PluginLoaderException { String[] classPath = getClassPathFromJar(jarFile); URLClassLoader classLoader = (URLClassLoader) ClassLoader .getSystemClassLoader(); Method method; try { method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class }); } catch (SecurityException e) { throw new PluginLoaderException( "addURL method of URLClassLoader not accessible via reflection."); } catch (NoSuchMethodException e) { throw new AssertionError( "URLClassLoader does not have addURL method. Should be impossible!!"); } method.setAccessible(true); try { method.invoke( classLoader, new Object[] { new URL("file:" + jarFile.getAbsoluteFile()) }); for (String element : classPath) { method.invoke(classLoader, new Object[] { new URL(element) }); } } catch (IllegalArgumentException e) { throw new AssertionError( "Illegal arguments for addURL method. Should be impossible!!"); } catch (MalformedURLException e) { throw new PluginLoaderException(e); } catch (IllegalAccessException e) { throw new PluginLoaderException( "addURL method of URLClassLoader not accessible via reflection."); } catch (InvocationTargetException e) { throw new PluginLoaderException(e); } } /** *

* Checks if the name of a file indicates that it defines a AutoQUEST plug-in. * The structure of valid plug-in filenames is * autoquest-plugin-%PLUGIN_NAME%-version.jar, where * %PLUGIN_NAME% is replaced by the name of the plug-in. Note * that plug-in names must not contain any dashes. *

* * @param filename * filename that is checked * @return true if filename matches pattern of AutoQUEST plug-in; false * otherwise */ protected boolean checkNameConformity(String filename) { if (filename == null) { return false; } return filename.startsWith("autoquest-plugin-") && !filename.startsWith("autoquest-plugin-core") && ((filename.split("-").length == 4 && filename.endsWith(".jar")) || filename.split("-").length == 5 && filename.endsWith("SNAPSHOT.jar")); } public Collection getPlugins() { return Collections.unmodifiableCollection(plugins); } }