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

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