mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
06.52.2013 c2d0d212b510d8e82b9b123b4d06a80b835c8cd4
opendj-admin/src/main/java/org/opends/server/admin/ClassLoaderProvider.java
@@ -27,8 +27,9 @@
 */
package org.opends.server.admin;
import static com.forgerock.opendj.ldap.AdminMessages.*;
import static com.forgerock.opendj.ldap.ExtensionMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
import static com.forgerock.opendj.util.Validator.*;
import java.io.ByteArrayOutputStream;
@@ -54,767 +55,664 @@
import java.util.jar.Manifest;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.admin.meta.RootCfgDefn;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.InitializationException;
import org.opends.server.util.DynamicConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.forgerock.opendj.ldap.AdminMessages;
/**
 * Manages the class loader which should be used for loading
 * configuration definition classes and associated extensions.
 * Manages the class loader which should be used for loading configuration
 * definition classes and associated extensions.
 * <p>
 * For extensions which define their own extended configuration
 * definitions, the class loader will make sure that the configuration
 * definition classes are loaded and initialized.
 * For extensions which define their own extended configuration definitions, the
 * class loader will make sure that the configuration definition classes are
 * loaded and initialized.
 * <p>
 * Initially the class loader provider is disabled, and calls to the
 * {@link #getClassLoader()} will return the system default class
 * loader.
 * {@link #getClassLoader()} will return the system default class loader.
 * <p>
 * Applications <b>MUST NOT</b> maintain persistent references to the
 * class loader as it can change at run-time.
 * Applications <b>MUST NOT</b> maintain persistent references to the class
 * loader as it can change at run-time.
 */
public final class ClassLoaderProvider {
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Private URLClassLoader implementation. This is only required so
   * that we can provide access to the addURL method.
   */
  private static final class MyURLClassLoader extends URLClassLoader {
    private static final LocalizedLogger adminLogger = LocalizedLogger.getLocalizedLogger(AdminMessages.resourceName());
    private static final Logger debugLogger = LoggerFactory.getLogger(ClassLoaderProvider.class);
    /**
     * Create a class loader with the default parent class loader.
     * Private URLClassLoader implementation. This is only required so that we
     * can provide access to the addURL method.
     */
    public MyURLClassLoader() {
      super(new URL[0]);
    }
    /**
     * Create a class loader with the provided parent class loader.
     *
     * @param parent
     *          The parent class loader.
     */
    public MyURLClassLoader(ClassLoader parent) {
      super(new URL[0], parent);
    }
    /**
     * Add a Jar file to this class loader.
     *
     * @param jarFile
     *          The name of the Jar file.
     * @throws MalformedURLException
     *           If a protocol handler for the URL could not be found,
     *           or if some other error occurred while constructing
     *           the URL.
     * @throws SecurityException
     *           If a required system property value cannot be
     *           accessed.
     */
    public void addJarFile(File jarFile) throws SecurityException,
        MalformedURLException {
      addURL(jarFile.toURI().toURL());
    }
  }
  // The name of the manifest file listing the core configuration
  // definition classes.
  private static final String CORE_MANIFEST = "core.manifest";
  // The name of the manifest file listing a extension's configuration
  // definition classes.
  private static final String EXTENSION_MANIFEST = "extension.manifest";
  // The name of the lib directory.
  private static final String LIB_DIR = "lib";
  // The name of the extensions directory.
  private static final String EXTENSIONS_DIR = "extensions";
  // The singleton instance.
  private static final ClassLoaderProvider INSTANCE = new ClassLoaderProvider();
  // Attribute name in jar's MANIFEST corresponding to the revision number.
  private static final String REVISION_NUMBER = "Revision-Number";
  // The attribute names for build information is name, version and revision
  // number
  private static final String[] BUILD_INFORMATION_ATTRIBUTE_NAMES =
                 new String[]{Attributes.Name.EXTENSION_NAME.toString(),
                              Attributes.Name.IMPLEMENTATION_VERSION.toString(),
                              REVISION_NUMBER};
  /**
   * Get the single application wide class loader provider instance.
   *
   * @return Returns the single application wide class loader provider
   *         instance.
   */
  public static ClassLoaderProvider getInstance() {
    return INSTANCE;
  }
  // Set of registered Jar files.
  private Set<File> jarFiles = new HashSet<File>();
  // Underlying class loader used to load classes and resources (null
  // if disabled).
  //
  // We contain a reference to the URLClassLoader rather than
  // sub-class it so that it is possible to replace the loader at
  // run-time. For example, when removing or replacing extension Jar
  // files (the URLClassLoader only supports adding new
  // URLs, not removal).
  private MyURLClassLoader loader = null;
  // Private constructor.
  private ClassLoaderProvider() {
    // No implementation required.
  }
  /**
   * Add the named extensions to this class loader provider.
   *
   * @param extensions
   *          The names of the extensions to be loaded. The names
   *          should not contain any path elements and must be located
   *          within the extensions folder.
   * @throws InitializationException
   *           If one of the extensions could not be loaded and
   *           initialized.
   * @throws IllegalStateException
   *           If this class loader provider is disabled.
   * @throws IllegalArgumentException
   *           If one of the extension names was not a single relative
   *           path name element or was an absolute path.
   */
  public synchronized void addExtension(String... extensions)
      throws InitializationException, IllegalStateException,
      IllegalArgumentException {
    ensureNotNull(extensions);
    if (loader == null) {
      throw new IllegalStateException(
          "Class loader provider is disabled.");
    }
    File libPath = new File(DirectoryServer.getInstanceRoot(), LIB_DIR);
    File extensionsPath = new File(libPath, EXTENSIONS_DIR);
    ArrayList<File> files = new ArrayList<File>(extensions.length);
    for (String extension : extensions) {
      File file = new File(extensionsPath, extension);
      // For security reasons we need to make sure that the file name
      // passed in did not contain any path elements and names a file
      // in the extensions folder.
      // Can handle potential null parent.
      if (!extensionsPath.equals(file.getParentFile())) {
        throw new IllegalArgumentException("Illegal file name: "
            + extension);
      }
      // The file is valid.
      files.add(file);
    }
    // Add the extensions.
    addExtension(files.toArray(new File[files.size()]));
  }
  /**
   * Disable this class loader provider and removed any registered
   * extensions.
   *
   * @throws IllegalStateException
   *           If this class loader provider is already disabled.
   */
  public synchronized void disable() throws IllegalStateException {
    if (loader == null) {
      throw new IllegalStateException(
          "Class loader provider already disabled.");
    }
    loader = null;
    jarFiles = new HashSet<File>();
  }
  /**
   * Enable this class loader provider using the application's
   * class loader as the parent class loader.
   *
   * @throws InitializationException
   *           If the class loader provider could not initialize
   *           successfully.
   * @throws IllegalStateException
   *           If this class loader provider is already enabled.
   */
  public synchronized void enable() throws InitializationException,
      IllegalStateException {
    enable(RootCfgDefn.class.getClassLoader());
  }
  /**
   * Enable this class loader provider using the provided parent class
   * loader.
   *
   * @param parent
   *          The parent class loader.
   * @throws InitializationException
   *           If the class loader provider could not initialize
   *           successfully.
   * @throws IllegalStateException
   *           If this class loader provider is already enabled.
   */
  public synchronized void enable(ClassLoader parent)
      throws InitializationException, IllegalStateException {
    if (loader != null) {
      throw new IllegalStateException(
          "Class loader provider already enabled.");
    }
    if (parent != null) {
      loader = new MyURLClassLoader(parent);
    } else {
      loader = new MyURLClassLoader();
    }
    // Forcefully load all configuration definition classes in
    // OpenDS.jar.
    initializeCoreComponents();
    // Put extensions jars into the class loader and load all
    // configuration definition classes in that they contain.
    // First load the extension from the install directory, then
    // from the instance directory.
    File libDir ;
    File installExtensionsPath ;
    File instanceExtensionsPath ;
    // load install dir extension
    libDir = new File(DirectoryServer.getServerRoot(), LIB_DIR);
    try
    {
      installExtensionsPath =
        new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
    }
    catch (Exception e)
    {
      installExtensionsPath = new File(libDir, EXTENSIONS_DIR);
    }
    initializeAllExtensions(installExtensionsPath);
    // load instance dir extension
    libDir = new File(DirectoryServer.getInstanceRoot(),LIB_DIR);
    try
    {
      instanceExtensionsPath =
        new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
    }
    catch (Exception e)
    {
      instanceExtensionsPath = new File(libDir, EXTENSIONS_DIR);
    }
    if (! installExtensionsPath.getAbsolutePath().equals(
        instanceExtensionsPath.getAbsolutePath()))
    {
      initializeAllExtensions(instanceExtensionsPath);
    }
  }
  /**
   * Gets the class loader which should be used for loading classes
   * and resources. When this class loader provider is disabled, the
   * system default class loader will be returned by default.
   * <p>
   * Applications <b>MUST NOT</b> maintain persistent references to
   * the class loader as it can change at run-time.
   *
   * @return Returns the class loader which should be used for loading
   *         classes and resources.
   */
  public synchronized ClassLoader getClassLoader() {
    if (loader != null) {
      return loader;
    } else {
      return ClassLoader.getSystemClassLoader();
    }
  }
  /**
   * Indicates whether this class loader provider is enabled.
   *
   * @return Returns <code>true</code> if this class loader provider
   *         is enabled.
   */
  public synchronized boolean isEnabled() {
    return loader != null;
  }
  /**
   * Add the named extensions to this class loader.
   *
   * @param extensions
   *          The names of the extensions to be loaded.
   * @throws InitializationException
   *           If one of the extensions could not be loaded and
   *           initialized.
   */
  private synchronized void addExtension(File... extensions)
      throws InitializationException {
    // First add the Jar files to the class loader.
    List<JarFile> jars = new LinkedList<JarFile>();
    for (File extension : extensions) {
      if (jarFiles.contains(extension)) {
        // Skip this file as it is already loaded.
        continue;
      }
      // Attempt to load it.
      jars.add(loadJarFile(extension));
      // Register the Jar file with the class loader.
      try {
        loader.addJarFile(extension);
      } catch (Exception e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        LocalizableMessage message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE.
            get(extension.getName(), extension.getParent(),
                stackTraceToSingleLineString(e));
        throw new InitializationException(message);
      }
      jarFiles.add(extension);
    }
    // Now forcefully load the configuration definition classes.
    for (JarFile jar : jars) {
      initializeExtension(jar);
    }
  }
  /**
   * Prints out all information about extensions.
   *
   * @return a String instance representing all information about extensions;
   *         <code>null</code> if there is no information available.
   */
  public String printExtensionInformation() {
    File extensionsPath =
            new File(new StringBuilder(DirectoryServer.getServerRoot()).
                                append(File.separator).
                                append(LIB_DIR).
                                append(File.separator).
                                append(EXTENSIONS_DIR).
                                toString());
    if (!extensionsPath.exists() || !extensionsPath.isDirectory()) {
      // no extensions' directory
      return null;
    }
    File[] extensions = extensionsPath.listFiles(new FileFilter(){
      public boolean accept(File pathname) {
        // only files with names ending with ".jar"
        return pathname.isFile() && pathname.getName().endsWith(".jar");
      }
    });
    if ( extensions.length == 0 ) {
      return null;
    }
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(baos);
    // prints:
    // --
    //            Name                 Build number         Revision number
    ps.printf("--%s           %-20s %-20s %-20s%s",
              EOL,
              "Name",
              "Build number",
              "Revision number",
              EOL);
    for(File extension : extensions) {
      // retrieve MANIFEST entry and display name, build number and revision
      // number
      try {
        JarFile jarFile = new JarFile(extension);
        JarEntry entry = jarFile.getJarEntry("admin/" + EXTENSION_MANIFEST);
        if (entry == null)
        {
          continue;
        }
        String[] information = getBuildInformation(jarFile);
        ps.append("Extension: ");
        boolean addBlank = false;
        for(String name : information) {
          if ( addBlank ) {
            ps.append(addBlank ? " " : ""); // add blank if not first append
          } else {
            addBlank = true;
          }
          ps.printf("%-20s", name);
        }
        ps.append(EOL);
      } catch(Exception e) {
        // ignore extra information for this extension
      }
    }
    return baos.toString();
  }
  /**
   * Returns a String array with the following information :
   * <br>index 0: the name of the extension.
   * <br>index 1: the build number of the extension.
   * <br>index 2: the revision number of the extension.
   *
   * @param extension the jar file of the extension
   * @return a String array containing the name, the build number and the
   *         revision number of the extension given in argument
   * @throws java.io.IOException thrown if the jar file has been closed.
   */
  private String[] getBuildInformation(JarFile extension) throws IOException {
    String[] result = new String[3];
    // retrieve MANIFEST entry and display name, version and revision
    Manifest manifest = extension.getManifest();
    if ( manifest != null ) {
      Attributes attributes = manifest.getMainAttributes();
      int index = 0;
      for(String name : BUILD_INFORMATION_ATTRIBUTE_NAMES) {
        String value = attributes.getValue(name);
        if ( value == null ) {
          value = "<unknown>";
        }
        result[index++] = value;
      }
    }
    return result;
  }
  /**
   * Put extensions jars into the class loader and load all
   * configuration definition classes in that they contain.
   * @param extensionsPath Indicates where extensions are located.
   *
   * @throws InitializationException
   *           If the extensions folder could not be accessed or if a
   *           extension jar file could not be accessed or if one of
   *           the configuration definition classes could not be
   *           initialized.
   */
  private void initializeAllExtensions(File extensionsPath)
      throws InitializationException {
    try {
      if (!extensionsPath.exists()) {
        // The extensions directory does not exist. This is not a
        // critical problem.
        LocalizableMessage message = ERR_ADMIN_NO_EXTENSIONS_DIR.get(
                String.valueOf(extensionsPath));
        logError(message);
        return;
      }
      if (!extensionsPath.isDirectory()) {
        // The extensions directory is not a directory. This is more
        // critical.
        LocalizableMessage message =
            ERR_ADMIN_EXTENSIONS_DIR_NOT_DIRECTORY.get(
                    String.valueOf(extensionsPath));
        throw new InitializationException(message);
      }
      // Get each extension file name.
      FileFilter filter = new FileFilter() {
    private static final class MyURLClassLoader extends URLClassLoader {
        /**
         * Must be a Jar file.
         * Create a class loader with the default parent class loader.
         */
        public boolean accept(File pathname) {
          if (!pathname.isFile()) {
            return false;
          }
          String name = pathname.getName();
          return name.endsWith(".jar");
        public MyURLClassLoader() {
            super(new URL[0]);
        }
      };
      // Add and initialize the extensions.
      addExtension(extensionsPath.listFiles(filter));
    } catch (InitializationException e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw e;
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      LocalizableMessage message = ERR_ADMIN_EXTENSIONS_CANNOT_LIST_FILES.get(
          String.valueOf(extensionsPath), stackTraceToSingleLineString(e));
      throw new InitializationException(message, e);
    }
  }
  /**
   * Make sure all core configuration definitions are loaded.
   *
   * @throws InitializationException
   *           If the core manifest file could not be read or if one
   *           of the configuration definition classes could not be
   *           initialized.
   */
  private void initializeCoreComponents()
      throws InitializationException {
    InputStream is = RootCfgDefn.class.getResourceAsStream("/admin/"
        + CORE_MANIFEST);
    if (is == null) {
      LocalizableMessage message = ERR_ADMIN_CANNOT_FIND_CORE_MANIFEST.get(CORE_MANIFEST);
      throw new InitializationException(message);
    }
    try {
      loadDefinitionClasses(is);
    } catch (InitializationException e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      LocalizableMessage message = ERR_CLASS_LOADER_CANNOT_LOAD_CORE.get(CORE_MANIFEST,
          stackTraceToSingleLineString(e));
      throw new InitializationException(message);
    }
  }
  /**
   * Make sure all the configuration definition classes in a extension
   * are loaded.
   *
   * @param jarFile
   *          The extension's Jar file.
   * @throws InitializationException
   *           If the extension jar file could not be accessed or if
   *           one of the configuration definition classes could not
   *           be initialized.
   */
  private void initializeExtension(JarFile jarFile)
      throws InitializationException {
    JarEntry entry = jarFile.getJarEntry("admin/"
        + EXTENSION_MANIFEST);
    if (entry != null) {
      InputStream is;
      try {
        is = jarFile.getInputStream(entry);
      } catch (Exception e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        /**
         * Create a class loader with the provided parent class loader.
         *
         * @param parent
         *            The parent class loader.
         */
        public MyURLClassLoader(ClassLoader parent) {
            super(new URL[0], parent);
        }
        LocalizableMessage message = ERR_ADMIN_CANNOT_READ_EXTENSION_MANIFEST.get(
            EXTENSION_MANIFEST, jarFile.getName(),
            stackTraceToSingleLineString(e));
        throw new InitializationException(message);
      }
      try {
        loadDefinitionClasses(is);
      } catch (InitializationException e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        /**
         * Add a Jar file to this class loader.
         *
         * @param jarFile
         *            The name of the Jar file.
         * @throws MalformedURLException
         *             If a protocol handler for the URL could not be found, or
         *             if some other error occurred while constructing the URL.
         * @throws SecurityException
         *             If a required system property value cannot be accessed.
         */
        public void addJarFile(File jarFile) throws SecurityException, MalformedURLException {
            addURL(jarFile.toURI().toURL());
        }
        LocalizableMessage message = ERR_CLASS_LOADER_CANNOT_LOAD_EXTENSION.get(jarFile
            .getName(), EXTENSION_MANIFEST, stackTraceToSingleLineString(e));
        throw new InitializationException(message);
      }
      try {
        // Log build information of extensions in the error log
        String[] information = getBuildInformation(jarFile);
        logError(
          NOTE_LOG_EXTENSION_INFORMATION.
            get(jarFile.getName(),
                information[1],
                information[2]));
      } catch(Exception e) {
        // Do not log information for that extension
      }
    }
  }
    // The name of the manifest file listing the core configuration
    // definition classes.
    private static final String CORE_MANIFEST = "core.manifest";
    // The name of the manifest file listing a extension's configuration
    // definition classes.
    private static final String EXTENSION_MANIFEST = "extension.manifest";
  /**
   * Forcefully load configuration definition classes named in a
   * manifest file.
   *
   * @param is
   *          The manifest file input stream.
   * @throws InitializationException
   *           If the definition classes could not be loaded and
   *           initialized.
   */
  private void loadDefinitionClasses(InputStream is)
      throws InitializationException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(
        is));
    List<AbstractManagedObjectDefinition<?, ?>> definitions =
      new LinkedList<AbstractManagedObjectDefinition<?,?>>();
    while (true) {
      String className;
      try {
        className = reader.readLine();
      } catch (IOException e) {
        LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_READ_MANIFEST_FILE.get(String
            .valueOf(e.getLocalizableMessage()));
        throw new InitializationException(msg, e);
      }
    // The name of the lib directory.
    private static final String LIB_DIR = "lib";
      // Break out when the end of the manifest is reached.
      if (className == null) {
        break;
      }
    // The name of the extensions directory.
    private static final String EXTENSIONS_DIR = "extensions";
      // Skip blank lines.
      className = className.trim();
      if (className.length() == 0) {
        continue;
      }
    // The singleton instance.
    private static final ClassLoaderProvider INSTANCE = new ClassLoaderProvider();
      // Skip lines beginning with #.
      if (className.startsWith("#")) {
        continue;
      }
    // Attribute name in jar's MANIFEST corresponding to the revision number.
    private static final String REVISION_NUMBER = "Revision-Number";
      TRACER.debugLocalizableMessage(DebugLogLevel.INFO, "Loading class " + className);
    // The attribute names for build information is name, version and revision
    // number
    private static final String[] BUILD_INFORMATION_ATTRIBUTE_NAMES = new String[] {
            Attributes.Name.EXTENSION_NAME.toString(), Attributes.Name.IMPLEMENTATION_VERSION.toString(),
            REVISION_NUMBER };
      // Load the class and get an instance of it if it is a definition.
      Class<?> theClass;
      try {
        theClass = Class.forName(className, true, loader);
      } catch (Exception e) {
        LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_LOAD_CLASS.get(className, String
            .valueOf(e.getLocalizableMessage()));
        throw new InitializationException(msg, e);
      }
      if (AbstractManagedObjectDefinition.class.isAssignableFrom(theClass)) {
        // We need to instantiate it using its getInstance() static method.
        Method method;
    /**
     * Get the single application wide class loader provider instance.
     *
     * @return Returns the single application wide class loader provider
     *         instance.
     */
    public static ClassLoaderProvider getInstance() {
        return INSTANCE;
    }
    // Set of registered Jar files.
    private Set<File> jarFiles = new HashSet<File>();
    // Underlying class loader used to load classes and resources (null
    // if disabled).
    //
    // We contain a reference to the URLClassLoader rather than
    // sub-class it so that it is possible to replace the loader at
    // run-time. For example, when removing or replacing extension Jar
    // files (the URLClassLoader only supports adding new
    // URLs, not removal).
    private MyURLClassLoader loader = null;
    // Private constructor.
    private ClassLoaderProvider() {
        // No implementation required.
    }
    /**
     * Add the named extensions to this class loader provider.
     *
     * @param extensions
     *            The names of the extensions to be loaded. The names should not
     *            contain any path elements and must be located within the
     *            extensions folder.
     * @throws InitializationException
     *             If one of the extensions could not be loaded and initialized.
     * @throws IllegalStateException
     *             If this class loader provider is disabled.
     * @throws IllegalArgumentException
     *             If one of the extension names was not a single relative path
     *             name element or was an absolute path.
     */
    public synchronized void addExtension(String... extensions) throws InitializationException, IllegalStateException,
            IllegalArgumentException {
        ensureNotNull(extensions);
        if (loader == null) {
            throw new IllegalStateException("Class loader provider is disabled.");
        }
        File libPath = new File(DirectoryServer.getInstanceRoot(), LIB_DIR);
        File extensionsPath = new File(libPath, EXTENSIONS_DIR);
        ArrayList<File> files = new ArrayList<File>(extensions.length);
        for (String extension : extensions) {
            File file = new File(extensionsPath, extension);
            // For security reasons we need to make sure that the file name
            // passed in did not contain any path elements and names a file
            // in the extensions folder.
            // Can handle potential null parent.
            if (!extensionsPath.equals(file.getParentFile())) {
                throw new IllegalArgumentException("Illegal file name: " + extension);
            }
            // The file is valid.
            files.add(file);
        }
        // Add the extensions.
        addExtension(files.toArray(new File[files.size()]));
    }
    /**
     * Disable this class loader provider and removed any registered extensions.
     *
     * @throws IllegalStateException
     *             If this class loader provider is already disabled.
     */
    public synchronized void disable() throws IllegalStateException {
        if (loader == null) {
            throw new IllegalStateException("Class loader provider already disabled.");
        }
        loader = null;
        jarFiles = new HashSet<File>();
    }
    /**
     * Enable this class loader provider using the application's class loader as
     * the parent class loader.
     *
     * @throws InitializationException
     *             If the class loader provider could not initialize
     *             successfully.
     * @throws IllegalStateException
     *             If this class loader provider is already enabled.
     */
    public synchronized void enable() throws InitializationException, IllegalStateException {
        enable(RootCfgDefn.class.getClassLoader());
    }
    /**
     * Enable this class loader provider using the provided parent class loader.
     *
     * @param parent
     *            The parent class loader.
     * @throws InitializationException
     *             If the class loader provider could not initialize
     *             successfully.
     * @throws IllegalStateException
     *             If this class loader provider is already enabled.
     */
    public synchronized void enable(ClassLoader parent) throws InitializationException, IllegalStateException {
        if (loader != null) {
            throw new IllegalStateException("Class loader provider already enabled.");
        }
        if (parent != null) {
            loader = new MyURLClassLoader(parent);
        } else {
            loader = new MyURLClassLoader();
        }
        // Forcefully load all configuration definition classes in
        // OpenDS.jar.
        initializeCoreComponents();
        // Put extensions jars into the class loader and load all
        // configuration definition classes in that they contain.
        // First load the extension from the install directory, then
        // from the instance directory.
        File libDir;
        File installExtensionsPath;
        File instanceExtensionsPath;
        // load install dir extension
        libDir = new File(DirectoryServer.getServerRoot(), LIB_DIR);
        try {
          method = theClass.getMethod("getInstance");
            installExtensionsPath = new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
        } catch (Exception e) {
          LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_FIND_GET_INSTANCE_METHOD.get(
              className, String.valueOf(e.getLocalizableMessage()));
          throw new InitializationException(msg, e);
            installExtensionsPath = new File(libDir, EXTENSIONS_DIR);
        }
        initializeAllExtensions(installExtensionsPath);
        // Get the definition instance.
        AbstractManagedObjectDefinition<?, ?> d;
        // load instance dir extension
        libDir = new File(DirectoryServer.getInstanceRoot(), LIB_DIR);
        try {
          d = (AbstractManagedObjectDefinition<?, ?>) method.invoke(null);
            instanceExtensionsPath = new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
        } catch (Exception e) {
          LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_INVOKE_GET_INSTANCE_METHOD.get(
              className, String.valueOf(e.getLocalizableMessage()));
          throw new InitializationException(msg, e);
            instanceExtensionsPath = new File(libDir, EXTENSIONS_DIR);
        }
        definitions.add(d);
      }
        if (!installExtensionsPath.getAbsolutePath().equals(instanceExtensionsPath.getAbsolutePath())) {
            initializeAllExtensions(instanceExtensionsPath);
        }
    }
    // Initialize any definitions that were loaded.
    for (AbstractManagedObjectDefinition<?, ?> d : definitions) {
      try {
        d.initialize();
      } catch (Exception e) {
        LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_INITIALIZE_DEFN.get(d.getName(),
            d.getClass().getName(), String.valueOf(e.getLocalizableMessage()));
        throw new InitializationException(msg, e);
      }
    /**
     * Gets the class loader which should be used for loading classes and
     * resources. When this class loader provider is disabled, the system
     * default class loader will be returned by default.
     * <p>
     * Applications <b>MUST NOT</b> maintain persistent references to the class
     * loader as it can change at run-time.
     *
     * @return Returns the class loader which should be used for loading classes
     *         and resources.
     */
    public synchronized ClassLoader getClassLoader() {
        if (loader != null) {
            return loader;
        } else {
            return ClassLoader.getSystemClassLoader();
        }
    }
  }
  /**
   * Load the named Jar file.
   *
   * @param jar
   *          The name of the Jar file to load.
   * @return Returns the loaded Jar file.
   * @throws InitializationException
   *           If the Jar file could not be loaded.
   */
  private JarFile loadJarFile(File jar)
      throws InitializationException {
    JarFile jarFile;
    try {
      // Load the extension jar file.
      jarFile = new JarFile(jar);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      LocalizableMessage message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get(
          jar.getName(), jar.getParent(), stackTraceToSingleLineString(e));
      throw new InitializationException(message);
    /**
     * Indicates whether this class loader provider is enabled.
     *
     * @return Returns <code>true</code> if this class loader provider is
     *         enabled.
     */
    public synchronized boolean isEnabled() {
        return loader != null;
    }
    return jarFile;
  }
    /**
     * Add the named extensions to this class loader.
     *
     * @param extensions
     *            The names of the extensions to be loaded.
     * @throws InitializationException
     *             If one of the extensions could not be loaded and initialized.
     */
    private synchronized void addExtension(File... extensions) throws InitializationException {
        // First add the Jar files to the class loader.
        List<JarFile> jars = new LinkedList<JarFile>();
        for (File extension : extensions) {
            if (jarFiles.contains(extension)) {
                // Skip this file as it is already loaded.
                continue;
            }
            // Attempt to load it.
            jars.add(loadJarFile(extension));
            // Register the Jar file with the class loader.
            try {
                loader.addJarFile(extension);
            } catch (Exception e) {
                debugLogger.trace("Unable to register the jar file with the class loader", e);
                LocalizableMessage message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get(extension.getName(),
                        extension.getParent(), stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
                throw new InitializationException(message);
            }
            jarFiles.add(extension);
        }
        // Now forcefully load the configuration definition classes.
        for (JarFile jar : jars) {
            initializeExtension(jar);
        }
    }
    /**
     * Prints out all information about extensions.
     *
     * @return a String instance representing all information about extensions;
     *         <code>null</code> if there is no information available.
     */
    public String printExtensionInformation() {
        File extensionsPath = new File(new StringBuilder(DirectoryServer.getServerRoot()).append(File.separator)
                .append(LIB_DIR).append(File.separator).append(EXTENSIONS_DIR).toString());
        if (!extensionsPath.exists() || !extensionsPath.isDirectory()) {
            // no extensions' directory
            return null;
        }
        File[] extensions = extensionsPath.listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                // only files with names ending with ".jar"
                return pathname.isFile() && pathname.getName().endsWith(".jar");
            }
        });
        if (extensions.length == 0) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);
        // prints:
        // --
        // Name Build number Revision number
        ps.printf("--%s           %-20s %-20s %-20s%s", EOL, "Name", "Build number", "Revision number", EOL);
        for (File extension : extensions) {
            // retrieve MANIFEST entry and display name, build number and
            // revision
            // number
            try {
                JarFile jarFile = new JarFile(extension);
                JarEntry entry = jarFile.getJarEntry("admin/" + EXTENSION_MANIFEST);
                if (entry == null) {
                    continue;
                }
                String[] information = getBuildInformation(jarFile);
                ps.append("Extension: ");
                boolean addBlank = false;
                for (String name : information) {
                    if (addBlank) {
                        ps.append(addBlank ? " " : ""); // add blank if not
                                                        // first append
                    } else {
                        addBlank = true;
                    }
                    ps.printf("%-20s", name);
                }
                ps.append(EOL);
            } catch (Exception e) {
                // ignore extra information for this extension
            }
        }
        return baos.toString();
    }
    /**
     * Returns a String array with the following information : <br>
     * index 0: the name of the extension. <br>
     * index 1: the build number of the extension. <br>
     * index 2: the revision number of the extension.
     *
     * @param extension
     *            the jar file of the extension
     * @return a String array containing the name, the build number and the
     *         revision number of the extension given in argument
     * @throws java.io.IOException
     *             thrown if the jar file has been closed.
     */
    private String[] getBuildInformation(JarFile extension) throws IOException {
        String[] result = new String[3];
        // retrieve MANIFEST entry and display name, version and revision
        Manifest manifest = extension.getManifest();
        if (manifest != null) {
            Attributes attributes = manifest.getMainAttributes();
            int index = 0;
            for (String name : BUILD_INFORMATION_ATTRIBUTE_NAMES) {
                String value = attributes.getValue(name);
                if (value == null) {
                    value = "<unknown>";
                }
                result[index++] = value;
            }
        }
        return result;
    }
    /**
     * Put extensions jars into the class loader and load all configuration
     * definition classes in that they contain.
     *
     * @param extensionsPath
     *            Indicates where extensions are located.
     * @throws InitializationException
     *             If the extensions folder could not be accessed or if a
     *             extension jar file could not be accessed or if one of the
     *             configuration definition classes could not be initialized.
     */
    private void initializeAllExtensions(File extensionsPath) throws InitializationException {
        try {
            if (!extensionsPath.exists()) {
                // The extensions directory does not exist. This is not a
                // critical problem.
                adminLogger.error(ERR_ADMIN_NO_EXTENSIONS_DIR, String.valueOf(extensionsPath));
                return;
            }
            if (!extensionsPath.isDirectory()) {
                // The extensions directory is not a directory. This is more
                // critical.
                LocalizableMessage message = ERR_ADMIN_EXTENSIONS_DIR_NOT_DIRECTORY.get(String.valueOf(extensionsPath));
                throw new InitializationException(message);
            }
            // Get each extension file name.
            FileFilter filter = new FileFilter() {
                /**
                 * Must be a Jar file.
                 */
                public boolean accept(File pathname) {
                    if (!pathname.isFile()) {
                        return false;
                    }
                    String name = pathname.getName();
                    return name.endsWith(".jar");
                }
            };
            // Add and initialize the extensions.
            addExtension(extensionsPath.listFiles(filter));
        } catch (InitializationException e) {
            debugLogger.trace("Unable to initialize all extensions", e);
            throw e;
        } catch (Exception e) {
            debugLogger.trace("Unable to initialize all extensions", e);
            LocalizableMessage message = ERR_ADMIN_EXTENSIONS_CANNOT_LIST_FILES.get(String.valueOf(extensionsPath),
                    stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
            throw new InitializationException(message, e);
        }
    }
    /**
     * Make sure all core configuration definitions are loaded.
     *
     * @throws InitializationException
     *             If the core manifest file could not be read or if one of the
     *             configuration definition classes could not be initialized.
     */
    private void initializeCoreComponents() throws InitializationException {
        InputStream is = RootCfgDefn.class.getResourceAsStream("/admin/" + CORE_MANIFEST);
        if (is == null) {
            LocalizableMessage message = ERR_ADMIN_CANNOT_FIND_CORE_MANIFEST.get(CORE_MANIFEST);
            throw new InitializationException(message);
        }
        try {
            loadDefinitionClasses(is);
        } catch (InitializationException e) {
            debugLogger.trace("Unable to initialize core components", e);
            LocalizableMessage message = ERR_CLASS_LOADER_CANNOT_LOAD_CORE.get(CORE_MANIFEST,
                    stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
            throw new InitializationException(message);
        }
    }
    /**
     * Make sure all the configuration definition classes in a extension are
     * loaded.
     *
     * @param jarFile
     *            The extension's Jar file.
     * @throws InitializationException
     *             If the extension jar file could not be accessed or if one of
     *             the configuration definition classes could not be
     *             initialized.
     */
    private void initializeExtension(JarFile jarFile) throws InitializationException {
        JarEntry entry = jarFile.getJarEntry("admin/" + EXTENSION_MANIFEST);
        if (entry != null) {
            InputStream is;
            try {
                is = jarFile.getInputStream(entry);
            } catch (Exception e) {
                debugLogger.trace("Unable to get input stream from jar", e);
                LocalizableMessage message = ERR_ADMIN_CANNOT_READ_EXTENSION_MANIFEST.get(EXTENSION_MANIFEST,
                        jarFile.getName(), stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
                throw new InitializationException(message);
            }
            try {
                loadDefinitionClasses(is);
            } catch (InitializationException e) {
                debugLogger.trace("Unable to load classes from input stream", e);
                LocalizableMessage message = ERR_CLASS_LOADER_CANNOT_LOAD_EXTENSION.get(jarFile.getName(),
                        EXTENSION_MANIFEST, stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
                throw new InitializationException(message);
            }
            try {
                // Log build information of extensions in the error log
                String[] information = getBuildInformation(jarFile);
                LocalizableMessage message = NOTE_LOG_EXTENSION_INFORMATION.get(jarFile.getName(), information[1],
                        information[2]);
                LocalizedLogger.getLocalizedLogger(message.resourceName()).error(message);
            } catch (Exception e) {
                // Do not log information for that extension
            }
        }
    }
    /**
     * Forcefully load configuration definition classes named in a manifest
     * file.
     *
     * @param is
     *            The manifest file input stream.
     * @throws InitializationException
     *             If the definition classes could not be loaded and
     *             initialized.
     */
    private void loadDefinitionClasses(InputStream is) throws InitializationException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        List<AbstractManagedObjectDefinition<?, ?>> definitions = new LinkedList<AbstractManagedObjectDefinition<?, ?>>();
        while (true) {
            String className;
            try {
                className = reader.readLine();
            } catch (IOException e) {
                LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_READ_MANIFEST_FILE.get(String.valueOf(e.getMessage()));
                throw new InitializationException(msg, e);
            }
            // Break out when the end of the manifest is reached.
            if (className == null) {
                break;
            }
            // Skip blank lines.
            className = className.trim();
            if (className.length() == 0) {
                continue;
            }
            // Skip lines beginning with #.
            if (className.startsWith("#")) {
                continue;
            }
            debugLogger.trace("Loading class " + className);
            // Load the class and get an instance of it if it is a definition.
            Class<?> theClass;
            try {
                theClass = Class.forName(className, true, loader);
            } catch (Exception e) {
                LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_LOAD_CLASS.get(className,
                        String.valueOf(e.getMessage()));
                throw new InitializationException(msg, e);
            }
            if (AbstractManagedObjectDefinition.class.isAssignableFrom(theClass)) {
                // We need to instantiate it using its getInstance() static
                // method.
                Method method;
                try {
                    method = theClass.getMethod("getInstance");
                } catch (Exception e) {
                    LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_FIND_GET_INSTANCE_METHOD.get(className,
                            String.valueOf(e.getMessage()));
                    throw new InitializationException(msg, e);
                }
                // Get the definition instance.
                AbstractManagedObjectDefinition<?, ?> d;
                try {
                    d = (AbstractManagedObjectDefinition<?, ?>) method.invoke(null);
                } catch (Exception e) {
                    LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_INVOKE_GET_INSTANCE_METHOD.get(className,
                            String.valueOf(e.getMessage()));
                    throw new InitializationException(msg, e);
                }
                definitions.add(d);
            }
        }
        // Initialize any definitions that were loaded.
        for (AbstractManagedObjectDefinition<?, ?> d : definitions) {
            try {
                d.initialize();
            } catch (Exception e) {
                LocalizableMessage msg = ERR_CLASS_LOADER_CANNOT_INITIALIZE_DEFN.get(d.getName(), d.getClass()
                        .getName(), String.valueOf(e.getMessage()));
                throw new InitializationException(msg, e);
            }
        }
    }
    /**
     * Load the named Jar file.
     *
     * @param jar
     *            The name of the Jar file to load.
     * @return Returns the loaded Jar file.
     * @throws InitializationException
     *             If the Jar file could not be loaded.
     */
    private JarFile loadJarFile(File jar) throws InitializationException {
        JarFile jarFile;
        try {
            // Load the extension jar file.
            jarFile = new JarFile(jar);
        } catch (Exception e) {
            debugLogger.trace("Unable to load jar file: " + jar, e);
            LocalizableMessage message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get(jar.getName(), jar.getParent(),
                    stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
            throw new InitializationException(message);
        }
        return jarFile;
    }
}