You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/03/29 06:20:22 UTC

[13/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java
new file mode 100644
index 0000000..3e2a8ae
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionManifest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.apache.guacamole.extension;
+
+import java.util.Collection;
+import java.util.Map;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Java representation of the JSON manifest contained within every Guacamole
+ * extension, identifying an extension and describing its contents.
+ *
+ * @author Michael Jumper
+ */
+public class ExtensionManifest {
+
+    /**
+     * The version of Guacamole for which this extension was built.
+     * Compatibility rules built into the web application will guard against
+     * incompatible extensions being loaded.
+     */
+    private String guacamoleVersion;
+
+    /**
+     * The name of the extension associated with this manifest. The extension
+     * name is human-readable, and used for display purposes only.
+     */
+    private String name;
+
+    /**
+     * The namespace of the extension associated with this manifest. The
+     * extension namespace is required for internal use, and is used wherever
+     * extension-specific files or resources need to be isolated from those of
+     * other extensions.
+     */
+    private String namespace;
+
+    /**
+     * The paths of all JavaScript resources within the .jar of the extension
+     * associated with this manifest.
+     */
+    private Collection<String> javaScriptPaths;
+
+    /**
+     * The paths of all CSS resources within the .jar of the extension
+     * associated with this manifest.
+     */
+    private Collection<String> cssPaths;
+
+    /**
+     * The paths of all HTML patch resources within the .jar of the extension
+     * associated with this manifest.
+     */
+    private Collection<String> htmlPaths;
+
+    /**
+     * The paths of all translation JSON files within this extension, if any.
+     */
+    private Collection<String> translationPaths;
+
+    /**
+     * The mimetypes of all resources within this extension which are not
+     * already declared as JavaScript, CSS, or translation resources, if any.
+     * The key of each entry is the resource path, while the value is the
+     * corresponding mimetype.
+     */
+    private Map<String, String> resourceTypes;
+
+    /**
+     * The names of all authentication provider classes within this extension,
+     * if any.
+     */
+    private Collection<String> authProviders;
+
+    /**
+     * The path to the small favicon. If provided, this will replace the default
+     * Guacamole icon.
+     */
+    private String smallIcon;
+
+    /**
+     * The path to the large favicon. If provided, this will replace the default
+     * Guacamole icon.
+     */
+    private String largeIcon;
+
+    /**
+     * Returns the version of the Guacamole web application for which the
+     * extension was built, such as "0.9.7".
+     *
+     * @return
+     *     The version of the Guacamole web application for which the extension
+     *     was built.
+     */
+    public String getGuacamoleVersion() {
+        return guacamoleVersion;
+    }
+
+    /**
+     * Sets the version of the Guacamole web application for which the
+     * extension was built, such as "0.9.7".
+     *
+     * @param guacamoleVersion
+     *     The version of the Guacamole web application for which the extension
+     *     was built.
+     */
+    public void setGuacamoleVersion(String guacamoleVersion) {
+        this.guacamoleVersion = guacamoleVersion;
+    }
+
+    /**
+     * Returns the name of the extension associated with this manifest. The
+     * name is human-readable, for display purposes only, and is defined within
+     * the manifest by the "name" property.
+     *
+     * @return
+     *     The name of the extension associated with this manifest.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the name of the extension associated with this manifest. The name
+     * is human-readable, for display purposes only, and is defined within the
+     * manifest by the "name" property.
+     *
+     * @param name
+     *     The name of the extension associated with this manifest.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the namespace of the extension associated with this manifest.
+     * The namespace is required for internal use, and is used wherever
+     * extension-specific files or resources need to be isolated from those of
+     * other extensions. It is defined within the manifest by the "namespace"
+     * property.
+     *
+     * @return
+     *     The namespace of the extension associated with this manifest.
+     */
+    public String getNamespace() {
+        return namespace;
+    }
+
+    /**
+     * Sets the namespace of the extension associated with this manifest. The
+     * namespace is required for internal use, and is used wherever extension-
+     * specific files or resources need to be isolated from those of other
+     * extensions. It is defined within the manifest by the "namespace"
+     * property.
+     *
+     * @param namespace
+     *     The namespace of the extension associated with this manifest.
+     */
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    /**
+     * Returns the paths to all JavaScript resources within the extension.
+     * These paths are defined within the manifest by the "js" property as an
+     * array of strings, where each string is a path relative to the root of
+     * the extension .jar.
+     *
+     * @return
+     *     A collection of paths to all JavaScript resources within the
+     *     extension.
+     */
+    @JsonProperty("js")
+    public Collection<String> getJavaScriptPaths() {
+        return javaScriptPaths;
+    }
+
+    /**
+     * Sets the paths to all JavaScript resources within the extension. These
+     * paths are defined within the manifest by the "js" property as an array
+     * of strings, where each string is a path relative to the root of the
+     * extension .jar.
+     *
+     * @param javaScriptPaths
+     *     A collection of paths to all JavaScript resources within the
+     *     extension.
+     */
+    @JsonProperty("js")
+    public void setJavaScriptPaths(Collection<String> javaScriptPaths) {
+        this.javaScriptPaths = javaScriptPaths;
+    }
+
+    /**
+     * Returns the paths to all CSS resources within the extension. These paths
+     * are defined within the manifest by the "js" property as an array of
+     * strings, where each string is a path relative to the root of the
+     * extension .jar.
+     *
+     * @return
+     *     A collection of paths to all CSS resources within the extension.
+     */
+    @JsonProperty("css")
+    public Collection<String> getCSSPaths() {
+        return cssPaths;
+    }
+
+    /**
+     * Sets the paths to all CSS resources within the extension. These paths
+     * are defined within the manifest by the "js" property as an array of
+     * strings, where each string is a path relative to the root of the
+     * extension .jar.
+     *
+     * @param cssPaths
+     *     A collection of paths to all CSS resources within the extension.
+     */
+    @JsonProperty("css")
+    public void setCSSPaths(Collection<String> cssPaths) {
+        this.cssPaths = cssPaths;
+    }
+
+    /**
+     * Returns the paths to all HTML patch resources within the extension. These
+     * paths are defined within the manifest by the "html" property as an array
+     * of strings, where each string is a path relative to the root of the
+     * extension .jar.
+     *
+     * @return
+     *     A collection of paths to all HTML patch resources within the
+     *     extension.
+     */
+    @JsonProperty("html")
+    public Collection<String> getHTMLPaths() {
+        return htmlPaths;
+    }
+
+    /**
+     * Sets the paths to all HTML patch resources within the extension. These
+     * paths are defined within the manifest by the "html" property as an array
+     * of strings, where each string is a path relative to the root of the
+     * extension .jar.
+     *
+     * @param htmlPatchPaths
+     *     A collection of paths to all HTML patch resources within the
+     *     extension.
+     */
+    @JsonProperty("html")
+    public void setHTMLPaths(Collection<String> htmlPatchPaths) {
+        this.htmlPaths = htmlPatchPaths;
+    }
+
+    /**
+     * Returns the paths to all translation resources within the extension.
+     * These paths are defined within the manifest by the "translations"
+     * property as an array of strings, where each string is a path relative to
+     * the root of the extension .jar.
+     *
+     * @return
+     *     A collection of paths to all translation resources within the
+     *     extension.
+     */
+    @JsonProperty("translations")
+    public Collection<String> getTranslationPaths() {
+        return translationPaths;
+    }
+
+    /**
+     * Sets the paths to all translation resources within the extension. These
+     * paths are defined within the manifest by the "translations" property as
+     * an array of strings, where each string is a path relative to the root of
+     * the extension .jar.
+     *
+     * @param translationPaths
+     *     A collection of paths to all translation resources within the
+     *     extension.
+     */
+    @JsonProperty("translations")
+    public void setTranslationPaths(Collection<String> translationPaths) {
+        this.translationPaths = translationPaths;
+    }
+
+    /**
+     * Returns a map of all resources to their corresponding mimetypes, for all
+     * resources not already declared as JavaScript, CSS, or translation
+     * resources. These paths and corresponding types are defined within the
+     * manifest by the "resources" property as an object, where each property
+     * name is a path relative to the root of the extension .jar, and each
+     * value is a mimetype.
+     *
+     * @return
+     *     A map of all resources within the extension to their corresponding
+     *     mimetypes.
+     */
+    @JsonProperty("resources")
+    public Map<String, String> getResourceTypes() {
+        return resourceTypes;
+    }
+
+    /**
+     * Sets the map of all resources to their corresponding mimetypes, for all
+     * resources not already declared as JavaScript, CSS, or translation
+     * resources. These paths and corresponding types are defined within the
+     * manifest by the "resources" property as an object, where each property
+     * name is a path relative to the root of the extension .jar, and each
+     * value is a mimetype.
+     *
+     * @param resourceTypes
+     *     A map of all resources within the extension to their corresponding
+     *     mimetypes.
+     */
+    @JsonProperty("resources")
+    public void setResourceTypes(Map<String, String> resourceTypes) {
+        this.resourceTypes = resourceTypes;
+    }
+
+    /**
+     * Returns the classnames of all authentication provider classes within the
+     * extension. These classnames are defined within the manifest by the
+     * "authProviders" property as an array of strings, where each string is an
+     * authentication provider classname.
+     *
+     * @return
+     *     A collection of classnames of all authentication providers within
+     *     the extension.
+     */
+    public Collection<String> getAuthProviders() {
+        return authProviders;
+    }
+
+    /**
+     * Sets the classnames of all authentication provider classes within the
+     * extension. These classnames are defined within the manifest by the
+     * "authProviders" property as an array of strings, where each string is an
+     * authentication provider classname.
+     *
+     * @param authProviders
+     *     A collection of classnames of all authentication providers within
+     *     the extension.
+     */
+    public void setAuthProviders(Collection<String> authProviders) {
+        this.authProviders = authProviders;
+    }
+
+    /**
+     * Returns the path to the small favicon, relative to the root of the
+     * extension.
+     *
+     * @return 
+     *     The path to the small favicon.
+     */
+    public String getSmallIcon() {
+        return smallIcon;
+    }
+
+    /**
+     * Sets the path to the small favicon. This will replace the default
+     * Guacamole icon.
+     *
+     * @param smallIcon 
+     *     The path to the small favicon.
+     */
+    public void setSmallIcon(String smallIcon) {
+        this.smallIcon = smallIcon;
+    }
+
+    /**
+     * Returns the path to the large favicon, relative to the root of the
+     * extension.
+     *
+     * @return
+     *     The path to the large favicon.
+     */
+    public String getLargeIcon() {
+        return largeIcon;
+    }
+
+    /**
+     * Sets the path to the large favicon. This will replace the default
+     * Guacamole icon.
+     *
+     * @param largeIcon
+     *     The path to the large favicon.
+     */
+    public void setLargeIcon(String largeIcon) {
+        this.largeIcon = largeIcon;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
new file mode 100644
index 0000000..cb3c0ef
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java
@@ -0,0 +1,450 @@
+/*
+ * 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.apache.guacamole.extension;
+
+import com.google.inject.Provides;
+import com.google.inject.servlet.ServletModule;
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.guacamole.auth.basic.BasicFileAuthenticationProvider;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.net.auth.AuthenticationProvider;
+import org.apache.guacamole.properties.BasicGuacamoleProperties;
+import org.apache.guacamole.resource.Resource;
+import org.apache.guacamole.resource.ResourceServlet;
+import org.apache.guacamole.resource.SequenceResource;
+import org.apache.guacamole.resource.WebApplicationResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Guice Module which loads all extensions within the
+ * GUACAMOLE_HOME/extensions directory, if any.
+ *
+ * @author Michael Jumper
+ */
+public class ExtensionModule extends ServletModule {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class);
+
+    /**
+     * The version strings of all Guacamole versions whose extensions are
+     * compatible with this release.
+     */
+    private static final List<String> ALLOWED_GUACAMOLE_VERSIONS =
+        Collections.unmodifiableList(Arrays.asList(
+            "*",
+            "0.9.9"
+        ));
+
+    /**
+     * 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.
+     */
+    private static final String EXTENSIONS_DIRECTORY = "extensions";
+
+    /**
+     * The string that the filenames of all extensions must end with to be
+     * recognized as extensions.
+     */
+    private static final String EXTENSION_SUFFIX = ".jar";
+
+    /**
+     * The Guacamole server environment.
+     */
+    private final Environment environment;
+
+    /**
+     * All currently-bound authentication providers, if any.
+     */
+    private final List<AuthenticationProvider> boundAuthenticationProviders =
+            new ArrayList<AuthenticationProvider>();
+
+    /**
+     * Service for adding and retrieving language resources.
+     */
+    private final LanguageResourceService languageResourceService;
+
+    /**
+     * Service for adding and retrieving HTML patch resources.
+     */
+    private final PatchResourceService patchResourceService;
+    
+    /**
+     * 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.
+     *
+     * @param environment
+     *     The environment to use when configuring authentication.
+     */
+    public ExtensionModule(Environment environment) {
+        this.environment = environment;
+        this.languageResourceService = new LanguageResourceService(environment);
+        this.patchResourceService = new PatchResourceService();
+    }
+
+    /**
+     * Reads the value of the now-deprecated "auth-provider" property from
+     * guacamole.properties, returning the corresponding AuthenticationProvider
+     * class. If no authentication provider could be read, or the property is
+     * not present, null is returned.
+     *
+     * As this property is deprecated, this function will also log warning
+     * messages if the property is actually specified.
+     *
+     * @return
+     *     The value of the deprecated "auth-provider" property, or null if the
+     *     property is not present.
+     */
+    @SuppressWarnings("deprecation") // We must continue to use this property until it is truly no longer supported
+    private Class<AuthenticationProvider> getAuthProviderProperty() {
+
+        // Get and bind auth provider instance, if defined via property
+        try {
+
+            // Use "auth-provider" property if present, but warn about deprecation
+            Class<AuthenticationProvider> authenticationProvider = environment.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
+            if (authenticationProvider != null)
+                logger.warn("The \"auth-provider\" and \"lib-directory\" properties are now deprecated. Please use the \"extensions\" and \"lib\" directories within GUACAMOLE_HOME instead.");
+
+            return authenticationProvider;
+
+        }
+        catch (GuacamoleException e) {
+            logger.warn("Value of deprecated \"auth-provider\" property within guacamole.properties is not valid: {}", e.getMessage());
+            logger.debug("Error reading authentication provider from guacamole.properties.", e);
+        }
+
+        return null;
+
+    }
+
+    /**
+     * Binds the given AuthenticationProvider class such that any service
+     * requiring access to the AuthenticationProvider can obtain it via
+     * injection, along with any other bound AuthenticationProviders.
+     *
+     * @param authenticationProvider
+     *     The AuthenticationProvider class to bind.
+     */
+    private void bindAuthenticationProvider(Class<? extends AuthenticationProvider> authenticationProvider) {
+
+        // Bind authentication provider
+        logger.debug("[{}] Binding AuthenticationProvider \"{}\".",
+                boundAuthenticationProviders.size(), authenticationProvider.getName());
+        boundAuthenticationProviders.add(new AuthenticationProviderFacade(authenticationProvider));
+
+    }
+
+    /**
+     * Binds each of the the given AuthenticationProvider classes such that any
+     * service requiring access to the AuthenticationProvider can obtain it via
+     * injection.
+     *
+     * @param authProviders
+     *     The AuthenticationProvider classes to bind.
+     */
+    private void bindAuthenticationProviders(Collection<Class<AuthenticationProvider>> authProviders) {
+
+        // Bind each authentication provider within extension
+        for (Class<AuthenticationProvider> authenticationProvider : authProviders)
+            bindAuthenticationProvider(authenticationProvider);
+
+    }
+
+    /**
+     * Returns a list of all currently-bound AuthenticationProvider instances.
+     *
+     * @return
+     *     A List of all currently-bound AuthenticationProvider. The List is
+     *     not modifiable.
+     */
+    @Provides
+    public List<AuthenticationProvider> getAuthenticationProviders() {
+        return Collections.unmodifiableList(boundAuthenticationProviders);
+    }
+
+    /**
+     * Serves each of the given resources as a language resource. Language
+     * resources are served from within the "/translations" directory as JSON
+     * files, where the name of each JSON file is the language key.
+     *
+     * @param resources
+     *     A map of all language resources to serve, where the key of each
+     *     entry in the language key from which the name of the JSON file will
+     *     be derived.
+     */
+    private void serveLanguageResources(Map<String, Resource> resources) {
+
+        // Add all resources to language resource service
+        for (Map.Entry<String, Resource> translationResource : resources.entrySet()) {
+
+            // Get path and resource from path/resource pair
+            String path = translationResource.getKey();
+            Resource resource = translationResource.getValue();
+
+            // Derive key from path
+            String languageKey = languageResourceService.getLanguageKey(path);
+            if (languageKey == null) {
+                logger.warn("Invalid language file name: \"{}\"", path);
+                continue;
+            }
+
+            // Add language resource
+            languageResourceService.addLanguageResource(languageKey, resource);
+
+        }
+
+    }
+
+    /**
+     * Serves each of the given resources under the given prefix. The path of
+     * each resource relative to the prefix is the key of its entry within the
+     * map.
+     *
+     * @param prefix
+     *     The prefix under which each resource should be served.
+     *
+     * @param resources
+     *     A map of all resources to serve, where the key of each entry in the
+     *     map is the desired path of that resource relative to the prefix.
+     */
+    private void serveStaticResources(String prefix, Map<String, Resource> resources) {
+
+        // Add all resources under given prefix
+        for (Map.Entry<String, Resource> staticResource : resources.entrySet()) {
+
+            // Get path and resource from path/resource pair
+            String path = staticResource.getKey();
+            Resource resource = staticResource.getValue();
+
+            // Serve within namespace-derived path
+            serve(prefix + path).with(new ResourceServlet(resource));
+
+        }
+
+    }
+
+    /**
+     * Returns whether the given version of Guacamole is compatible with this
+     * version of Guacamole as far as extensions are concerned.
+     *
+     * @param guacamoleVersion
+     *     The version of Guacamole the extension was built for.
+     *
+     * @return
+     *     true if the given version of Guacamole is compatible with this
+     *     version of Guacamole, false otherwise.
+     */
+    private boolean isCompatible(String guacamoleVersion) {
+        return ALLOWED_GUACAMOLE_VERSIONS.contains(guacamoleVersion);
+    }
+
+    /**
+     * Loads all extensions within the GUACAMOLE_HOME/extensions directory, if
+     * any, adding their static resource to the given resoure collections.
+     *
+     * @param javaScriptResources
+     *     A modifiable collection of static JavaScript resources which may
+     *     receive new JavaScript resources from extensions.
+     *
+     * @param cssResources
+     *     A modifiable collection of static CSS resources which may receive
+     *     new CSS resources from extensions.
+     */
+    private void loadExtensions(Collection<Resource> javaScriptResources,
+            Collection<Resource> cssResources) {
+
+        // Retrieve and validate extensions directory
+        File extensionsDir = new File(environment.getGuacamoleHome(), EXTENSIONS_DIRECTORY);
+        if (!extensionsDir.isDirectory())
+            return;
+
+        // Retrieve list of all extension files within extensions directory
+        File[] extensionFiles = extensionsDir.listFiles(new FileFilter() {
+
+            @Override
+            public boolean accept(File file) {
+                return file.isFile() && file.getName().endsWith(EXTENSION_SUFFIX);
+            }
+
+        });
+
+        // Verify contents are accessible
+        if (extensionFiles == null) {
+            logger.warn("Although GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " exists, its contents cannot be read.");
+            return;
+        }
+
+        // Sort files lexicographically
+        Arrays.sort(extensionFiles);
+
+        // Load each extension within the extension directory
+        for (File extensionFile : extensionFiles) {
+
+            logger.debug("Loading extension: \"{}\"", extensionFile.getName());
+
+            try {
+
+                // Load extension from file
+                Extension extension = new Extension(getParentClassLoader(), extensionFile);
+
+                // Validate Guacamole version of extension
+                if (!isCompatible(extension.getGuacamoleVersion())) {
+                    logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.",
+                            extension.getGuacamoleVersion(), extensionFile.getName());
+                    throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not "
+                            + "compatible with this version of Guacamole.");
+                }
+
+                // Add any JavaScript / CSS resources
+                javaScriptResources.addAll(extension.getJavaScriptResources().values());
+                cssResources.addAll(extension.getCSSResources().values());
+
+                // Attempt to load all authentication providers
+                bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
+
+                // Add any translation resources
+                serveLanguageResources(extension.getTranslationResources());
+
+                // Add all HTML patch resources
+                patchResourceService.addPatchResources(extension.getHTMLResources().values());
+
+                // Add all static resources under namespace-derived prefix
+                String staticResourcePrefix = "/app/ext/" + extension.getNamespace() + "/";
+                serveStaticResources(staticResourcePrefix, extension.getStaticResources());
+
+                // Serve up the small favicon if provided
+                if(extension.getSmallIcon() != null)
+                    serve("/images/logo-64.png").with(new ResourceServlet(extension.getSmallIcon()));
+
+                // Serve up the large favicon if provided
+                if(extension.getLargeIcon()!= null)
+                    serve("/images/logo-144.png").with(new ResourceServlet(extension.getLargeIcon()));
+
+                // Log successful loading of extension by name
+                logger.info("Extension \"{}\" loaded.", extension.getName());
+
+            }
+            catch (GuacamoleException e) {
+                logger.error("Extension \"{}\" could not be loaded: {}", extensionFile.getName(), e.getMessage());
+                logger.debug("Unable to load extension.", e);
+            }
+
+        }
+
+    }
+    
+    @Override
+    protected void configureServlets() {
+
+        // Bind resource services
+        bind(LanguageResourceService.class).toInstance(languageResourceService);
+        bind(PatchResourceService.class).toInstance(patchResourceService);
+
+        // Load initial language resources from servlet context
+        languageResourceService.addLanguageResources(getServletContext());
+
+        // Load authentication provider from guacamole.properties for sake of backwards compatibility
+        Class<AuthenticationProvider> authProviderProperty = getAuthProviderProperty();
+        if (authProviderProperty != null)
+            bindAuthenticationProvider(authProviderProperty);
+
+        // Init JavaScript resources with base guacamole.min.js
+        Collection<Resource> javaScriptResources = new ArrayList<Resource>();
+        javaScriptResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.js"));
+
+        // Init CSS resources with base guacamole.min.css
+        Collection<Resource> cssResources = new ArrayList<Resource>();
+        cssResources.add(new WebApplicationResource(getServletContext(), "/guacamole.min.css"));
+
+        // Load all extensions
+        loadExtensions(javaScriptResources, cssResources);
+
+        // Always bind basic auth last
+        bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
+
+        // Dynamically generate app.js and app.css from extensions
+        serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources)));
+        serve("/app.css").with(new ResourceServlet(new SequenceResource(cssResources)));
+
+        // Dynamically serve all language resources
+        for (Map.Entry<String, Resource> entry : languageResourceService.getLanguageResources().entrySet()) {
+
+            // Get language key/resource pair
+            String languageKey = entry.getKey();
+            Resource resource = entry.getValue();
+
+            // Serve resource within /translations
+            serve("/translations/" + languageKey + ".json").with(new ResourceServlet(resource));
+            
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java b/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java
new file mode 100644
index 0000000..c043887
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/LanguageResourceService.java
@@ -0,0 +1,442 @@
+/*
+ * 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.apache.guacamole.extension;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.ServletContext;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.node.JsonNodeFactory;
+import org.codehaus.jackson.node.ObjectNode;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.properties.BasicGuacamoleProperties;
+import org.apache.guacamole.resource.ByteArrayResource;
+import org.apache.guacamole.resource.Resource;
+import org.apache.guacamole.resource.WebApplicationResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service which provides access to all built-in languages as resources, and
+ * allows other resources to be added or overlaid against existing resources.
+ *
+ * @author Michael Jumper
+ */
+public class LanguageResourceService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(LanguageResourceService.class);
+    
+    /**
+     * The path to the translation folder within the webapp.
+     */
+    private static final String TRANSLATION_PATH = "/translations";
+    
+    /**
+     * The JSON property for the human readable display name.
+     */
+    private static final String LANGUAGE_DISPLAY_NAME_KEY = "NAME";
+    
+    /**
+     * The Jackson parser for parsing the language JSON files.
+     */
+    private static final ObjectMapper mapper = new ObjectMapper();
+    
+    /**
+     * The regular expression to use for parsing the language key from the
+     * filename.
+     */
+    private static final Pattern LANGUAGE_KEY_PATTERN = Pattern.compile(".*/([a-z]+(_[A-Z]+)?)\\.json");
+
+    /**
+     * The set of all language keys which are explicitly listed as allowed
+     * within guacamole.properties, or null if all defined languages should be
+     * allowed.
+     */
+    private final Set<String> allowedLanguages;
+
+    /**
+     * Map of all language resources by language key. Language keys are
+     * language and country code pairs, separated by an underscore, like
+     * "en_US". The country code and underscore SHOULD be omitted in the case
+     * that only one dialect of that language is defined, or in the case of the
+     * most universal or well-supported of all supported dialects of that
+     * language.
+     */
+    private final Map<String, Resource> resources = new HashMap<String, Resource>();
+
+    /**
+     * Creates a new service for tracking and parsing available translations
+     * which reads its configuration from the given environment.
+     *
+     * @param environment
+     *     The environment from which the configuration properties of this
+     *     service should be read.
+     */
+    public LanguageResourceService(Environment environment) {
+
+        Set<String> parsedAllowedLanguages;
+
+        // Parse list of available languages from properties
+        try {
+            parsedAllowedLanguages = environment.getProperty(BasicGuacamoleProperties.ALLOWED_LANGUAGES);
+            logger.debug("Available languages will be restricted to: {}", parsedAllowedLanguages);
+        }
+
+        // Warn of failure to parse
+        catch (GuacamoleException e) {
+            parsedAllowedLanguages = null;
+            logger.error("Unable to parse list of allowed languages: {}", e.getMessage());
+            logger.debug("Error parsing list of allowed languages.", e);
+        }
+
+        this.allowedLanguages = parsedAllowedLanguages;
+
+    }
+
+    /**
+     * Derives a language key from the filename within the given path, if
+     * possible. If the filename is not a valid language key, null is returned.
+     *
+     * @param path
+     *     The path containing the filename to derive the language key from.
+     *
+     * @return
+     *     The derived language key, or null if the filename is not a valid
+     *     language key.
+     */
+    public String getLanguageKey(String path) {
+
+        // Parse language key from filename
+        Matcher languageKeyMatcher = LANGUAGE_KEY_PATTERN.matcher(path);
+        if (!languageKeyMatcher.matches())
+            return null;
+
+        // Return parsed key
+        return languageKeyMatcher.group(1);
+
+    }
+
+    /**
+     * Merges the given JSON objects. Any leaf node in overlay will overwrite
+     * the corresponding path in original.
+     *
+     * @param original
+     *     The original JSON object to which changes should be applied.
+     *
+     * @param overlay
+     *     The JSON object containing changes that should be applied.
+     *
+     * @return
+     *     The newly constructed JSON object that is the result of merging
+     *     original and overlay.
+     */
+    private JsonNode mergeTranslations(JsonNode original, JsonNode overlay) {
+
+        // If we are at a leaf node, the result of merging is simply the overlay
+        if (!overlay.isObject() || original == null)
+            return overlay;
+
+        // Create mutable copy of original
+        ObjectNode newNode = JsonNodeFactory.instance.objectNode();
+        Iterator<String> fieldNames = original.getFieldNames();
+        while (fieldNames.hasNext()) {
+            String fieldName = fieldNames.next();
+            newNode.put(fieldName, original.get(fieldName));
+        }
+
+        // Merge each field
+        fieldNames = overlay.getFieldNames();
+        while (fieldNames.hasNext()) {
+            String fieldName = fieldNames.next();
+            newNode.put(fieldName, mergeTranslations(original.get(fieldName), overlay.get(fieldName)));
+        }
+
+        return newNode;
+
+    }
+
+    /**
+     * Parses the given language resource, returning the resulting JsonNode.
+     * If the resource cannot be read because it does not exist, null is
+     * returned.
+     *
+     * @param resource
+     *     The language resource to parse. Language resources must have the
+     *     mimetype "application/json".
+     *
+     * @return
+     *     A JsonNode representing the root of the parsed JSON tree, or null if
+     *     the given resource does not exist.
+     *
+     * @throws IOException
+     *     If an error occurs while parsing the resource as JSON.
+     */
+    private JsonNode parseLanguageResource(Resource resource) throws IOException {
+
+        // Get resource stream
+        InputStream stream = resource.asStream();
+        if (stream == null)
+            return null;
+
+        // Parse JSON tree
+        try {
+            JsonNode tree = mapper.readTree(stream);
+            return tree;
+        }
+
+        // Ensure stream is always closed
+        finally {
+            stream.close();
+        }
+
+    }
+
+    /**
+     * Returns whether a language having the given key should be allowed to be
+     * loaded. If language availability restrictions are imposed through
+     * guacamole.properties, this may return false in some cases. By default,
+     * this function will always return true. Note that just because a language
+     * key is allowed to be loaded does not imply that the language key is
+     * valid.
+     *
+     * @param languageKey
+     *     The language key of the language to test.
+     *
+     * @return
+     *     true if the given language key should be allowed to be loaded, false
+     *     otherwise.
+     */
+    private boolean isLanguageAllowed(String languageKey) {
+
+        // If no list is provided, all languages are implicitly available
+        if (allowedLanguages == null)
+            return true;
+
+        return allowedLanguages.contains(languageKey);
+
+    }
+
+    /**
+     * Adds or overlays the given language resource, which need not exist in
+     * the ServletContext. If a language resource is already defined for the
+     * given language key, the strings from the given resource will be overlaid
+     * on top of the existing strings, augmenting or overriding the available
+     * strings for that language.
+     *
+     * @param key
+     *     The language key of the resource being added. Language keys are
+     *     pairs consisting of a language code followed by an underscore and
+     *     country code, such as "en_US".
+     *
+     * @param resource
+     *     The language resource to add. This resource must have the mimetype
+     *     "application/json".
+     */
+    public void addLanguageResource(String key, Resource resource) {
+
+        // Skip loading of language if not allowed
+        if (!isLanguageAllowed(key)) {
+            logger.debug("OMITTING language: \"{}\"", key);
+            return;
+        }
+
+        // Merge language resources if already defined
+        Resource existing = resources.get(key);
+        if (existing != null) {
+
+            try {
+
+                // Read the original language resource
+                JsonNode existingTree = parseLanguageResource(existing);
+                if (existingTree == null) {
+                    logger.warn("Base language resource \"{}\" does not exist.", key);
+                    return;
+                }
+
+                // Read new language resource
+                JsonNode resourceTree = parseLanguageResource(resource);
+                if (resourceTree == null) {
+                    logger.warn("Overlay language resource \"{}\" does not exist.", key);
+                    return;
+                }
+
+                // Merge the language resources
+                JsonNode mergedTree = mergeTranslations(existingTree, resourceTree);
+                resources.put(key, new ByteArrayResource("application/json", mapper.writeValueAsBytes(mergedTree)));
+
+                logger.debug("Merged strings with existing language: \"{}\"", key);
+
+            }
+            catch (IOException e) {
+                logger.error("Unable to merge language resource \"{}\": {}", key, e.getMessage());
+                logger.debug("Error merging language resource.", e);
+            }
+
+        }
+
+        // Otherwise, add new language resource
+        else {
+            resources.put(key, resource);
+            logger.debug("Added language: \"{}\"", key);
+        }
+
+    }
+
+    /**
+     * Adds or overlays all languages defined within the /translations
+     * directory of the given ServletContext. If no such language files exist,
+     * nothing is done. If a language is already defined, the strings from the
+     * will be overlaid on top of the existing strings, augmenting or
+     * overriding the available strings for that language. The language key
+     * for each language file is derived from the filename.
+     *
+     * @param context
+     *     The ServletContext from which language files should be loaded.
+     */
+    public void addLanguageResources(ServletContext context) {
+
+        // Get the paths of all the translation files
+        Set<?> resourcePaths = context.getResourcePaths(TRANSLATION_PATH);
+        
+        // If no translation files found, nothing to add
+        if (resourcePaths == null)
+            return;
+        
+        // Iterate through all the found language files and add them to the map
+        for (Object resourcePathObject : resourcePaths) {
+
+            // Each resource path is guaranteed to be a string
+            String resourcePath = (String) resourcePathObject;
+
+            // Parse language key from path
+            String languageKey = getLanguageKey(resourcePath);
+            if (languageKey == null) {
+                logger.warn("Invalid language file name: \"{}\"", resourcePath);
+                continue;
+            }
+
+            // Add/overlay new resource
+            addLanguageResource(
+                languageKey,
+                new WebApplicationResource(context, "application/json", resourcePath)
+            );
+
+        }
+
+    }
+
+    /**
+     * Returns a set of all unique language keys currently associated with
+     * language resources stored in this service. The returned set cannot be
+     * modified.
+     *
+     * @return
+     *     A set of all unique language keys currently associated with this
+     *     service.
+     */
+    public Set<String> getLanguageKeys() {
+        return Collections.unmodifiableSet(resources.keySet());
+    }
+
+    /**
+     * Returns a map of all languages currently associated with this service,
+     * where the key of each map entry is the language key. The returned map
+     * cannot be modified.
+     *
+     * @return
+     *     A map of all languages currently associated with this service.
+     */
+    public Map<String, Resource> getLanguageResources() {
+        return Collections.unmodifiableMap(resources);
+    }
+
+    /**
+     * Returns a mapping of all language keys to their corresponding human-
+     * readable language names. If an error occurs while parsing a language
+     * resource, its key/name pair will simply be omitted. The returned map
+     * cannot be modified.
+     *
+     * @return
+     *     A map of all language keys and their corresponding human-readable
+     *     names.
+     */
+    public Map<String, String> getLanguageNames() {
+
+        Map<String, String> languageNames = new HashMap<String, String>();
+
+        // For each language key/resource pair
+        for (Map.Entry<String, Resource> entry : resources.entrySet()) {
+
+            // Get language key and resource
+            String languageKey = entry.getKey();
+            Resource resource = entry.getValue();
+
+            // Get stream for resource
+            InputStream resourceStream = resource.asStream();
+            if (resourceStream == null) {
+                logger.warn("Expected language resource does not exist: \"{}\".", languageKey);
+                continue;
+            }
+            
+            // Get name node of language
+            try {
+                JsonNode tree = mapper.readTree(resourceStream);
+                JsonNode nameNode = tree.get(LANGUAGE_DISPLAY_NAME_KEY);
+                
+                // Attempt to read language name from node
+                String languageName;
+                if (nameNode == null || (languageName = nameNode.getTextValue()) == null) {
+                    logger.warn("Root-level \"" + LANGUAGE_DISPLAY_NAME_KEY + "\" string missing or invalid in language \"{}\"", languageKey);
+                    languageName = languageKey;
+                }
+                
+                // Add language key/name pair to map
+                languageNames.put(languageKey, languageName);
+
+            }
+
+            // Continue with next language if unable to read
+            catch (IOException e) {
+                logger.warn("Unable to read language resource \"{}\".", languageKey);
+                logger.debug("Error reading language resource.", e);
+            }
+
+        }
+        
+        return Collections.unmodifiableMap(languageNames);
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/PatchResourceService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/PatchResourceService.java b/guacamole/src/main/java/org/apache/guacamole/extension/PatchResourceService.java
new file mode 100644
index 0000000..e0c1c0b
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/PatchResourceService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.apache.guacamole.extension;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.guacamole.resource.Resource;
+
+/**
+ * Service which provides access to all HTML patches as resources, and allows
+ * other patch resources to be added.
+ *
+ * @author Michael Jumper
+ */
+public class PatchResourceService {
+
+    /**
+     * A list of all HTML patch resources currently defined, in the order they
+     * should be applied.
+     */
+    private final List<Resource> resources = new ArrayList<Resource>();
+
+    /**
+     * Adds the given HTML patch resource such that it will apply to the
+     * Guacamole UI. The patch will be applied by the JavaScript side of the
+     * web application in the order that addPatchResource() is invoked.
+     *
+     * @param resource
+     *     The HTML patch resource to add. This resource must have the mimetype
+     *     "text/html".
+     */
+    public void addPatchResource(Resource resource) {
+        resources.add(resource);
+    }
+
+    /**
+     * Adds the given HTML patch resources such that they will apply to the
+     * Guacamole UI. The patches will be applied by the JavaScript side of the
+     * web application in the order provided.
+     *
+     * @param resources
+     *     The HTML patch resources to add. Each resource must have the
+     *     mimetype "text/html".
+     */
+    public void addPatchResources(Collection<Resource> resources) {
+        for (Resource resource : resources)
+            addPatchResource(resource);
+    }
+
+    /**
+     * Returns a list of all HTML patches currently associated with this
+     * service, in the order they should be applied. The returned list cannot
+     * be modified.
+     *
+     * @return
+     *     A list of all HTML patches currently associated with this service.
+     */
+    public List<Resource> getPatchResources() {
+        return Collections.unmodifiableList(resources);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/extension/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/package-info.java b/guacamole/src/main/java/org/apache/guacamole/extension/package-info.java
new file mode 100644
index 0000000..02f4afc
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/extension/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes which represent and facilitate the loading of extensions to the
+ * Guacamole web application.
+ */
+package org.apache.guacamole.extension;

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java
new file mode 100644
index 0000000..67e93c4
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/log/LogModule.java
@@ -0,0 +1,99 @@
+/*
+ * 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.apache.guacamole.log;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
+import com.google.inject.AbstractModule;
+import java.io.File;
+import org.apache.guacamole.environment.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Initializes the logging subsystem.
+ *
+ * @author Michael Jumper
+ */
+public class LogModule extends AbstractModule {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(LogModule.class);
+
+    /**
+     * The Guacamole server environment.
+     */
+    private final Environment environment;
+
+    /**
+     * Creates a new LogModule which uses the given environment to determine
+     * the logging configuration.
+     *
+     * @param environment
+     *     The environment to use when configuring logging.
+     */
+    public LogModule(Environment environment) {
+        this.environment = environment;
+    }
+    
+    @Override
+    protected void configure() {
+
+        // Only load logback configuration if GUACAMOLE_HOME exists
+        File guacamoleHome = environment.getGuacamoleHome();
+        if (!guacamoleHome.isDirectory())
+            return;
+
+        // Check for custom logback.xml
+        File logbackConfiguration = new File(guacamoleHome, "logback.xml");
+        if (!logbackConfiguration.exists())
+            return;
+
+        logger.info("Loading logback configuration from \"{}\".", logbackConfiguration);
+
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        context.reset();
+
+        try {
+
+            // Initialize logback
+            JoranConfigurator configurator = new JoranConfigurator();
+            configurator.setContext(context);
+            configurator.doConfigure(logbackConfiguration);
+
+            // Dump any errors that occur during logback init
+            StatusPrinter.printInCaseOfErrorsOrWarnings(context);
+
+        }
+        catch (JoranException e) {
+            logger.error("Initialization of logback failed: {}", e.getMessage());
+            logger.debug("Unable to load logback configuration..", e);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicFileAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicFileAuthenticationProvider.java
deleted file mode 100644
index 7fe6bbd..0000000
--- a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicFileAuthenticationProvider.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2013 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.apache.guacamole.net.basic;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.environment.Environment;
-import org.apache.guacamole.environment.LocalEnvironment;
-import org.apache.guacamole.net.auth.Credentials;
-import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider;
-import org.apache.guacamole.net.basic.auth.Authorization;
-import org.apache.guacamole.net.basic.auth.UserMapping;
-import org.apache.guacamole.xml.DocumentHandler;
-import org.apache.guacamole.net.basic.xml.usermapping.UserMappingTagHandler;
-import org.apache.guacamole.properties.FileGuacamoleProperty;
-import org.apache.guacamole.protocol.GuacamoleConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-/**
- * Authenticates users against a static list of username/password pairs.
- * Each username/password may be associated with multiple configurations.
- * This list is stored in an XML file which is reread if modified.
- *
- * @author Michael Jumper, Michal Kotas
- */
-public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvider {
-
-    /**
-     * Logger for this class.
-     */
-    private final Logger logger = LoggerFactory.getLogger(BasicFileAuthenticationProvider.class);
-
-    /**
-     * The time the user mapping file was last modified. If the file has never
-     * been read, and thus no modification time exists, this will be
-     * Long.MIN_VALUE.
-     */
-    private long lastModified = Long.MIN_VALUE;
-
-    /**
-     * The parsed UserMapping read when the user mapping file was last parsed.
-     */
-    private UserMapping cachedUserMapping;
-
-    /**
-     * Guacamole server environment.
-     */
-    private final Environment environment;
-
-    /**
-     * The XML file to read the user mapping from.
-     */
-    public static final FileGuacamoleProperty BASIC_USER_MAPPING = new FileGuacamoleProperty() {
-
-        @Override
-        public String getName() { return "basic-user-mapping"; }
-
-    };
-
-    /**
-     * The default filename to use for the user mapping, if not defined within
-     * guacamole.properties.
-     */
-    public static final String DEFAULT_USER_MAPPING = "user-mapping.xml";
-    
-    /**
-     * Creates a new BasicFileAuthenticationProvider that authenticates users
-     * against simple, monolithic XML file.
-     *
-     * @throws GuacamoleException
-     *     If a required property is missing, or an error occurs while parsing
-     *     a property.
-     */
-    public BasicFileAuthenticationProvider() throws GuacamoleException {
-        environment = new LocalEnvironment();
-    }
-
-    @Override
-    public String getIdentifier() {
-        return "default";
-    }
-
-    /**
-     * Returns a UserMapping containing all authorization data given within
-     * the XML file specified by the "basic-user-mapping" property in
-     * guacamole.properties. If the XML file has been modified or has not yet
-     * been read, this function may reread the file.
-     *
-     * @return
-     *     A UserMapping containing all authorization data within the user
-     *     mapping XML file, or null if the file cannot be found/parsed.
-     */
-    private UserMapping getUserMapping() {
-
-        // Get user mapping file, defaulting to GUACAMOLE_HOME/user-mapping.xml
-        File userMappingFile;
-        try {
-            userMappingFile = environment.getProperty(BASIC_USER_MAPPING);
-            if (userMappingFile == null)
-                userMappingFile = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING);
-        }
-
-        // Abort if property cannot be parsed
-        catch (GuacamoleException e) {
-            logger.warn("Unable to read user mapping filename from properties: {}", e.getMessage());
-            logger.debug("Error parsing user mapping property.", e);
-            return null;
-        }
-
-        // Abort if user mapping does not exist
-        if (!userMappingFile.exists()) {
-            logger.debug("User mapping file \"{}\" does not exist and will not be read.", userMappingFile);
-            return null;
-        }
-
-        // Refresh user mapping if file has changed
-        if (lastModified < userMappingFile.lastModified()) {
-
-            logger.debug("Reading user mapping file: \"{}\"", userMappingFile);
-
-            // Parse document
-            try {
-
-                // Get handler for root element
-                UserMappingTagHandler userMappingHandler =
-                        new UserMappingTagHandler();
-
-                // Set up document handler
-                DocumentHandler contentHandler = new DocumentHandler(
-                        "user-mapping", userMappingHandler);
-
-                // Set up XML parser
-                XMLReader parser = XMLReaderFactory.createXMLReader();
-                parser.setContentHandler(contentHandler);
-
-                // Read and parse file
-                InputStream input = new BufferedInputStream(new FileInputStream(userMappingFile));
-                parser.parse(new InputSource(input));
-                input.close();
-
-                // Store mod time and user mapping
-                lastModified = userMappingFile.lastModified();
-                cachedUserMapping = userMappingHandler.asUserMapping();
-
-            }
-
-            // If the file is unreadable, return no mapping
-            catch (IOException e) {
-                logger.warn("Unable to read user mapping file \"{}\": {}", userMappingFile, e.getMessage());
-                logger.debug("Error reading user mapping file.", e);
-                return null;
-            }
-
-            // If the file cannot be parsed, return no mapping
-            catch (SAXException e) {
-                logger.warn("User mapping file \"{}\" is not valid: {}", userMappingFile, e.getMessage());
-                logger.debug("Error parsing user mapping file.", e);
-                return null;
-            }
-
-        }
-
-        // Return (possibly cached) user mapping
-        return cachedUserMapping;
-
-    }
-
-    @Override
-    public Map<String, GuacamoleConfiguration>
-            getAuthorizedConfigurations(Credentials credentials)
-            throws GuacamoleException {
-
-        // Abort authorization if no user mapping exists
-        UserMapping userMapping = getUserMapping();
-        if (userMapping == null)
-            return null;
-
-        // Validate and return info for given user and pass
-        Authorization auth = userMapping.getAuthorization(credentials.getUsername());
-        if (auth != null && auth.validate(credentials.getUsername(), credentials.getPassword()))
-            return auth.getConfigurations();
-
-        // Unauthorized
-        return null;
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicGuacamoleTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicGuacamoleTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicGuacamoleTunnelServlet.java
deleted file mode 100644
index d2bff1a..0000000
--- a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicGuacamoleTunnelServlet.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2013 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.apache.guacamole.net.basic;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import javax.servlet.http.HttpServletRequest;
-import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.net.GuacamoleTunnel;
-import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Connects users to a tunnel associated with the authorized connection
- * having the given ID.
- *
- * @author Michael Jumper
- */
-@Singleton
-public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
-
-    /**
-     * Service for handling tunnel requests.
-     */
-    @Inject
-    private TunnelRequestService tunnelRequestService;
-    
-    /**
-     * Logger for this class.
-     */
-    private static final Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
-
-    @Override
-    protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
-
-        // Attempt to create HTTP tunnel
-        GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
-
-        // If successful, warn of lack of WebSocket
-        logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
-
-        return tunnel;
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicServletContextListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicServletContextListener.java
deleted file mode 100644
index 3bf6b9e..0000000
--- a/guacamole/src/main/java/org/apache/guacamole/net/basic/BasicServletContextListener.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.apache.guacamole.net.basic;
-
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.google.inject.servlet.GuiceServletContextListener;
-import javax.servlet.ServletContextEvent;
-import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.environment.Environment;
-import org.apache.guacamole.environment.LocalEnvironment;
-import org.apache.guacamole.net.basic.extension.ExtensionModule;
-import org.apache.guacamole.net.basic.log.LogModule;
-import org.apache.guacamole.net.basic.rest.RESTServiceModule;
-import org.apache.guacamole.net.basic.rest.auth.BasicTokenSessionMap;
-import org.apache.guacamole.net.basic.rest.auth.TokenSessionMap;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A ServletContextListener to listen for initialization of the servlet context
- * in order to set up dependency injection.
- *
- * @author James Muehlner
- */
-public class BasicServletContextListener extends GuiceServletContextListener {
-
-    /**
-     * Logger for this class.
-     */
-    private final Logger logger = LoggerFactory.getLogger(BasicServletContextListener.class);
-
-    /**
-     * The Guacamole server environment.
-     */
-    private Environment environment;
-
-    /**
-     * Singleton instance of a TokenSessionMap.
-     */
-    private TokenSessionMap sessionMap;
-
-    @Override
-    public void contextInitialized(ServletContextEvent servletContextEvent) {
-
-        try {
-            environment = new LocalEnvironment();
-            sessionMap = new BasicTokenSessionMap(environment);
-        }
-        catch (GuacamoleException e) {
-            logger.error("Unable to read guacamole.properties: {}", e.getMessage());
-            logger.debug("Error reading guacamole.properties.", e);
-            throw new RuntimeException(e);
-        }
-
-        super.contextInitialized(servletContextEvent);
-
-    }
-
-    @Override
-    protected Injector getInjector() {
-        return Guice.createInjector(Stage.PRODUCTION,
-            new EnvironmentModule(environment),
-            new LogModule(environment),
-            new ExtensionModule(environment),
-            new RESTServiceModule(sessionMap),
-            new TunnelModule()
-        );
-    }
-
-    @Override
-    public void contextDestroyed(ServletContextEvent servletContextEvent) {
-
-        super.contextDestroyed(servletContextEvent);
-
-        // Shutdown TokenSessionMap
-        if (sessionMap != null)
-            sessionMap.shutdown();
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/net/basic/ClipboardState.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/net/basic/ClipboardState.java b/guacamole/src/main/java/org/apache/guacamole/net/basic/ClipboardState.java
deleted file mode 100644
index dae5d74..0000000
--- a/guacamole/src/main/java/org/apache/guacamole/net/basic/ClipboardState.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2014 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.apache.guacamole.net.basic;
-
-/**
- * Provides central storage for a cross-connection clipboard state. This
- * clipboard state is shared only for a single HTTP session. Multiple HTTP
- * sessions will all have their own state.
- * 
- * @author Michael Jumper
- */
-public class ClipboardState {
-
-    /**
-     * The maximum number of bytes to track.
-     */
-    private static final int MAXIMUM_LENGTH = 262144;
-
-     /**
-     * The mimetype of the current contents.
-     */
-    private String mimetype = "text/plain";
-
-    /**
-     * The mimetype of the pending contents.
-     */
-    private String pending_mimetype = "text/plain";
-    
-    /**
-     * The current contents.
-     */
-    private byte[] contents = new byte[0];
-
-    /**
-     * The pending clipboard contents.
-     */
-    private final byte[] pending = new byte[MAXIMUM_LENGTH];
-
-    /**
-     * The length of the pending data, in bytes.
-     */
-    private int pending_length = 0;
-    
-    /**
-     * The timestamp of the last contents update.
-     */
-    private long last_update = 0;
-    
-    /**
-     * Returns the current clipboard contents.
-     * @return The current clipboard contents
-     */
-    public synchronized byte[] getContents() {
-        return contents;
-    }
-
-    /**
-     * Returns the mimetype of the current clipboard contents.
-     * @return The mimetype of the current clipboard contents.
-     */
-    public synchronized String getMimetype() {
-        return mimetype;
-    }
-
-    /**
-     * Begins a new update of the clipboard contents. The actual contents will
-     * not be saved until commit() is called.
-     * 
-     * @param mimetype The mimetype of the contents being added.
-     */
-    public synchronized void begin(String mimetype) {
-        pending_length = 0;
-        this.pending_mimetype = mimetype;
-    }
-
-    /**
-     * Appends the given data to the clipboard contents.
-     * 
-     * @param data The raw data to append.
-     */
-    public synchronized void append(byte[] data) {
-
-        // Calculate size of copy
-        int length = data.length;
-        int remaining = pending.length - pending_length;
-        if (remaining < length)
-            length = remaining;
-    
-        // Append data
-        System.arraycopy(data, 0, pending, pending_length, length);
-        pending_length += length;
-
-    }
-
-    /**
-     * Commits the pending contents to the clipboard, notifying any threads
-     * waiting for clipboard updates.
-     */
-    public synchronized void commit() {
-
-        // Commit contents
-        mimetype = pending_mimetype;
-        contents = new byte[pending_length];
-        System.arraycopy(pending, 0, contents, 0, pending_length);
-
-        // Notify of update
-        last_update = System.currentTimeMillis();
-        this.notifyAll();
-
-    }
-    
-    /**
-     * Wait up to the given timeout for new clipboard data.
-     * 
-     * @param timeout The amount of time to wait, in milliseconds.
-     * @return true if the contents were updated within the timeframe given,
-     *         false otherwise.
-     */
-    public synchronized boolean waitForContents(int timeout) {
-
-        // Wait for new contents if it's been a while
-        if (System.currentTimeMillis() - last_update > timeout) {
-            try {
-                this.wait(timeout);
-                return true;
-            }
-            catch (InterruptedException e) { /* ignore */ }
-        }
-
-        return false;
-
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/net/basic/EnvironmentModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/net/basic/EnvironmentModule.java b/guacamole/src/main/java/org/apache/guacamole/net/basic/EnvironmentModule.java
deleted file mode 100644
index 76559d1..0000000
--- a/guacamole/src/main/java/org/apache/guacamole/net/basic/EnvironmentModule.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.apache.guacamole.net.basic;
-
-import com.google.inject.AbstractModule;
-import org.apache.guacamole.environment.Environment;
-
-/**
- * Guice module which binds the base Guacamole server environment.
- *
- * @author Michael Jumper
- */
-public class EnvironmentModule extends AbstractModule {
-
-    /**
-     * The Guacamole server environment.
-     */
-    private final Environment environment;
-
-    /**
-     * Creates a new EnvironmentModule which will bind the given environment
-     * for future injection.
-     *
-     * @param environment
-     *     The environment to bind.
-     */
-    public EnvironmentModule(Environment environment) {
-        this.environment = environment;
-    }
-
-    @Override
-    protected void configure() {
-
-        // Bind environment
-        bind(Environment.class).toInstance(environment);
-
-    }
-
-}
-