source: trunk/quest-plugin-core/src/main/java/de/ugoe/cs/quest/plugin/PluginLoader.java @ 776

Last change on this file since 776 was 776, checked in by sherbold, 12 years ago
  • should remove FindBugs? warning about regarding a unclosed stream
  • Property svn:mime-type set to text/plain
File size: 7.0 KB
Line 
1package de.ugoe.cs.quest.plugin;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileNotFoundException;
6import java.io.FilenameFilter;
7import java.io.IOException;
8import java.lang.reflect.InvocationTargetException;
9import java.lang.reflect.Method;
10import java.net.MalformedURLException;
11import java.net.URL;
12import java.net.URLClassLoader;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.LinkedList;
16import java.util.jar.JarInputStream;
17import java.util.jar.Manifest;
18
19/**
20 * <p>
21 * This class provides the functionality to load QUEST plug-ins from a
22 * pre-defined folder.
23 * </p>
24 *
25 * @author Steffen Herbold
26 * @version 1.0
27 */
28public class PluginLoader {
29
30        /**
31         * <p>
32         * Handle of the plug-in directory.
33         * </p>
34         */
35        private final File pluginDir;
36
37        /**
38         * <p>
39         * Collection of the loaded plug-ins.
40         * </p>
41         */
42        private final Collection<QuestPlugin> plugins;
43
44        /**
45         * <p>
46         * Constructor. Creates a new PluginLoader that can load plug-ins the
47         * defined directory.
48         * </p>
49         *
50         * @param pluginDir
51         *            handle of the directory; in case the handle is
52         *            <code>null</code> or does not describe a directory, an
53         *            {@link IllegalArgumentException} is thrown
54         */
55        public PluginLoader(File pluginDir) {
56                if (pluginDir == null) {
57                        throw new IllegalArgumentException(
58                                        "Parameter pluginDir must not be null!");
59                }
60                if (!pluginDir.isDirectory()) {
61                        throw new IllegalArgumentException("File " + pluginDir.getPath()
62                                        + " is not a directory");
63                }
64                this.pluginDir = pluginDir;
65                plugins = new LinkedList<QuestPlugin>();
66        }
67
68        /**
69         * <p>
70         * Loads plug-ins from {@link #pluginDir}.
71         * </p>
72         *
73         * @throws PluginLoaderException
74         *             thrown if there is a problem loading a plug-in or updating
75         *             the classpath
76         */
77        public void load() throws PluginLoaderException {
78                File[] jarFiles = pluginDir.listFiles(new FilenameFilter() {
79                        @Override
80                        public boolean accept(File dir, String name) {
81                                return checkNameConformity(name);
82                        }
83                });
84
85                for (File jarFile : jarFiles) {
86                        updateClassLoader(jarFile);
87
88                        String pluginName = jarFile.getName().split("-")[2];
89                        String pluginClassName = "de.ugoe.cs.quest.plugin." + pluginName
90                                        + "." + pluginName.toUpperCase() + "Plugin";
91
92                        Class<?> pluginClass = null;
93                        try {
94                                pluginClass = Class.forName(pluginClassName);
95                        } catch (ClassNotFoundException e) {
96                                throw new PluginLoaderException("No class '" + pluginClassName
97                                                + "' found in " + pluginDir + "/" + jarFile.getName());
98                        }
99                        try {
100                                QuestPlugin pluginObject = (QuestPlugin) pluginClass
101                                                .newInstance();
102                                plugins.add(pluginObject);
103                        } catch (InstantiationException e) {
104                                throw new PluginLoaderException("Could not instantiate "
105                                                + pluginClassName);
106                        } catch (IllegalAccessException e) {
107                                throw new PluginLoaderException("Could not access "
108                                                + pluginClassName);
109                        } catch (ClassCastException e) {
110                                throw new PluginLoaderException("Class " + pluginClassName
111                                                + " not instance of QuestPlugin");
112                        }
113                }
114        }
115
116        /**
117         * <p>
118         * Retrieves the classpath from a Jar file's MANIFEST.
119         * </p>
120         *
121         * @throws IOException
122         * @throws FileNotFoundException
123         */
124        protected String[] getClassPathFromJar(File jarFile) {
125                String[] classPath;
126
127                JarInputStream jarInputStream = null;
128                Manifest manifest = null;
129                try {
130                    FileInputStream fileStream = new FileInputStream(jarFile);
131                    try {
132                        jarInputStream = new JarInputStream(fileStream);
133                        manifest = jarInputStream.getManifest();
134                    } finally {
135                        jarInputStream.close();
136                        fileStream.close();
137                    }
138                } catch (FileNotFoundException e) {
139                        throw new AssertionError(
140                                        "FileNotFoundException should be impossible!");
141                } catch (IOException e) {
142                        throw new PluginLoaderException(e);
143                }
144
145                String jarClassPath = manifest.getMainAttributes().getValue(
146                                "Class-Path");
147
148                if (jarClassPath != null) {
149                        String[] jarClassPathElements = jarClassPath.split(" ");
150                        classPath = new String[jarClassPathElements.length];
151                        for (int i = 0; i < jarClassPathElements.length; i++) {
152                                classPath[i] = "file:"
153                                                + jarFile.getParentFile().getAbsolutePath() + "/"
154                                                + jarClassPathElements[i];
155                        }
156                        try {
157                                jarInputStream.close();
158                        } catch (IOException e) {
159                                throw new PluginLoaderException(e);
160                        }
161                } else {
162                        classPath = new String[] {};
163                }
164                return classPath;
165        }
166
167        /**
168         * <p>
169         * Updates the classpath of the {@link ClassLoader} to include the plug-in
170         * jar as well as further libraries required by the plug-in jar as defined
171         * in the <code>Class-Path</code> section of its manifest.
172         * </p>
173         *
174         * @param jarFile
175         *            handle of the plug-in jar file
176         * @throws PluginLoaderException
177         *             thrown if there is a problem updating the class loader or
178         *             loading the plug-in jar
179         */
180        private void updateClassLoader(File jarFile) throws PluginLoaderException {
181                String[] classPath = getClassPathFromJar(jarFile);
182                URLClassLoader classLoader = (URLClassLoader) ClassLoader
183                                .getSystemClassLoader();
184                Method method;
185
186                try {
187                        method = URLClassLoader.class.getDeclaredMethod("addURL",
188                                        new Class[] { URL.class });
189                } catch (SecurityException e) {
190                        throw new PluginLoaderException(
191                                        "addURL method of URLClassLoader not accessible via reflection.");
192                } catch (NoSuchMethodException e) {
193                        throw new AssertionError(
194                                        "URLClassLoader does not have addURL method. Should be impossible!!");
195                }
196                method.setAccessible(true);
197
198                try {
199                        method.invoke(
200                                        classLoader,
201                                        new Object[] { new URL("file:" + jarFile.getAbsoluteFile()) });
202                        for (String element : classPath) {
203                                method.invoke(classLoader, new Object[] { new URL(element) });
204                        }
205                } catch (IllegalArgumentException e) {
206                        throw new AssertionError(
207                                        "Illegal arguments for addURL method. Should be impossible!!");
208                } catch (MalformedURLException e) {
209                        throw new PluginLoaderException(e);
210                } catch (IllegalAccessException e) {
211                        throw new PluginLoaderException(
212                                        "addURL method of URLClassLoader not accessible via reflection.");
213                } catch (InvocationTargetException e) {
214                        throw new PluginLoaderException(e);
215                }
216        }
217
218        /**
219         * <p>
220         * Checks if the name of a file indicates that it defines a QUEST plug-in.
221         * The structure of valid plug-in filenames is
222         * <code>quest-plugin-%PLUGIN_NAME%-version.jar</code>, where
223         * <code>%PLUGIN_NAME%</code> is replaced by the name of the plug-in. Note
224         * that plug-in names must not contain any dashes.
225         * </p>
226         *
227         * @param filename
228         *            filename that is checked
229         * @return true if filename matches pattern of QUEST plug-in; false
230         *         otherwise
231         */
232        protected boolean checkNameConformity(String filename) {
233                if (filename == null) {
234                        return false;
235                }
236                return filename.startsWith("quest-plugin-") && !filename.startsWith("quest-plugin-core")
237                                &&
238                                ((filename.split("-").length == 4 && filename.endsWith(".jar")) ||
239                                  filename.split("-").length == 5 && filename.endsWith("SNAPSHOT.jar"));
240        }
241       
242        public Collection<QuestPlugin> getPlugins() {
243                return Collections.unmodifiableCollection(plugins);
244        }
245}
Note: See TracBrowser for help on using the repository browser.