diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java new file mode 100644 index 000000000..ad9f8c6dc --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/DirectoryClassLoader.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.extension; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import org.glyptodon.guacamole.GuacamoleException; + +/** + * A ClassLoader implementation which finds classes within .jar files within a + * given directory. + * + * @author Michael Jumper + */ +public class DirectoryClassLoader extends URLClassLoader { + + /** + * Returns an instance of DirectoryClassLoader configured to load .jar + * files from the given directory. Calling this function multiple times + * will not affect previously-returned instances of DirectoryClassLoader. + * + * @param dir + * The directory from which .jar files should be read. + * + * @return + * A DirectoryClassLoader instance which loads classes from the .jar + * files in the given directory. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + public static DirectoryClassLoader getInstance(final File dir) + throws GuacamoleException { + + try { + // Attempt to create singleton classloader which loads classes from + // all .jar's in the lib directory defined in guacamole.properties + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public DirectoryClassLoader run() throws GuacamoleException { + return new DirectoryClassLoader(dir); + } + + }); + } + + catch (PrivilegedActionException e) { + throw (GuacamoleException) e.getException(); + } + + } + + /** + * Returns all .jar files within the given directory as an array of URLs. + * + * @param dir + * The directory to retrieve all .jar files from + * + * @return + * An array of the URLs of all .jar files within the given directory. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + private static URL[] getJarURLs(File dir) throws GuacamoleException { + + // Validate directory is indeed a directory + if (!dir.isDirectory()) + throw new GuacamoleException(dir + " is not a directory."); + + // Get list of URLs for all .jar's in the lib directory + Collection jarURLs = new ArrayList(); + File[] files = dir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + + // If it ends with .jar, accept the file + return name.endsWith(".jar"); + + } + + }); + + // Verify directory was successfully read + if (files == null) + throw new GuacamoleException("Unable to read contents of directory " + dir); + + // Add the URL for each .jar to the jar URL list + for (File file : files) { + + try { + jarURLs.add(file.toURI().toURL()); + } + catch (MalformedURLException e) { + throw new GuacamoleException(e); + } + + } + + // Set delegate classloader to new URLClassLoader which loads from the + // .jars found above. + + URL[] urls = new URL[jarURLs.size()]; + return jarURLs.toArray(urls); + + } + + /** + * Creates a new DirectoryClassLoader configured to load .jar files from + * the given directory. + * + * @param dir + * The directory from which .jar files should be read. + * + * @throws GuacamoleException + * If the given file is not a directory, or the contents of the given + * directory cannot be read. + */ + + private DirectoryClassLoader(File dir) throws GuacamoleException { + super(getJarURLs(dir), DirectoryClassLoader.class.getClassLoader()); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java index 16aa55979..c2defbafb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java @@ -51,6 +51,12 @@ public class ExtensionModule extends ServletModule { */ private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class); + /** + * The name of the directory within GUACAMOLE_HOME containing any .jars + * which should be included in the classpath of all extensions. + */ + private static final String LIB_DIRECTORY = "lib"; + /** * The name of the directory within GUACAMOLE_HOME containing all * extensions. @@ -68,6 +74,34 @@ public class ExtensionModule extends ServletModule { */ private final Environment environment; + /** + * Returns the classloader that should be used as the parent classloader + * for all extensions. If the GUACAMOLE_HOME/lib directory exists, this + * will be a classloader that loads classes from within the .jar files in + * that directory. Lacking the GUACAMOLE_HOME/lib directory, this will + * simply be the classloader associated with the ExtensionModule class. + * + * @return + * The classloader that should be used as the parent classloader for + * all extensions. + * + * @throws GuacamoleException + * If an error occurs while retrieving the classloader. + */ + private ClassLoader getParentClassLoader() throws GuacamoleException { + + // Retrieve lib directory + File libDir = new File(environment.getGuacamoleHome(), LIB_DIRECTORY); + + // If lib directory does not exist, use default class loader + if (!libDir.isDirectory()) + return ExtensionModule.class.getClassLoader(); + + // Return classloader which loads classes from all .jars within the lib directory + return DirectoryClassLoader.getInstance(libDir); + + } + /** * Creates a module which loads all extensions within the * GUACAMOLE_HOME/extensions directory. @@ -112,9 +146,8 @@ public class ExtensionModule extends ServletModule { try { - // FIXME: Use class loader which reads from the lib directory // Load extension from file - Extension extension = new Extension(ExtensionModule.class.getClassLoader(), extensionFile); + Extension extension = new Extension(getParentClassLoader(), extensionFile); // Add any JavaScript / CSS resources javaScriptResources.addAll(extension.getJavaScriptResources());