/**
   * 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.
   *
   * <p>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;
  }
  /**
   * 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);
  }
  /**
   * 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);
      }
    }
  }