// 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.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.List; 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 implements AutoCloseable { /** *

* Handle of the plug-in directory. *

*/ private final File pluginDir; /** *

* Collection of the loaded plug-ins. *

*/ private final Collection plugins; /** *

* the class loaders instantiated internally *

*/ private List instantiatedClassLoaders = new LinkedList<>(); /** *

* 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); } }); if (jarFiles != null) { for (File jarFile : jarFiles) { ClassLoader loader = getClassLoader(jarFile); String pluginName = jarFile.getName().split("-")[2]; String pluginClassName = "de.ugoe.cs.autoquest.plugin." + pluginName + "." + pluginName.toUpperCase() + "Plugin"; Class pluginClass = null; try { pluginClass = loader.loadClass(pluginClassName); } catch (ClassNotFoundException e) { throw new PluginLoaderException("No class '" + pluginClassName + "' found in " + pluginDir + "/" + jarFile.getName()); } try { AutoQUESTPlugin pluginObject = (AutoQUESTPlugin) pluginClass.getDeclaredConstructor().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"); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } /** *

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

*/ 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 ClassLoader getClassLoader(File jarFile) throws PluginLoaderException { String[] otherJars = getClassPathFromJar(jarFile); try { URL[] urls = new URL[otherJars.length + 1]; urls[0] = new URL("jar:file:" + jarFile.getAbsoluteFile() + "!/"); int index = 1; for (String jar : otherJars) { urls[index++] = new URL("jar:file:" + new File(jar).getAbsoluteFile() + "!/"); } URLClassLoader loader = new URLClassLoader(new URL[] { new URL("jar:file:" + jarFile.getAbsoluteFile() + "!/") }, ClassLoader.getSystemClassLoader()); instantiatedClassLoaders.add(loader); return loader; } catch (MalformedURLException 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") || filename.split("-").length == 6 && filename.endsWith(".jar")); } public Collection getPlugins() { return Collections.unmodifiableCollection(plugins); } @Override public void close() throws Exception { for (URLClassLoader loader : instantiatedClassLoaders) { loader.close(); } } }