From c2d0d212b510d8e82b9b123b4d06a80b835c8cd4 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 06 Dec 2013 16:52:01 +0000
Subject: [PATCH] OpenDJ 3 : config framework
---
opendj-admin/src/main/java/org/opends/server/admin/ClassLoaderProvider.java | 1336 ++++++++++++++++++++++++++-------------------------------
1 files changed, 617 insertions(+), 719 deletions(-)
diff --git a/opendj-admin/src/main/java/org/opends/server/admin/ClassLoaderProvider.java b/opendj-admin/src/main/java/org/opends/server/admin/ClassLoaderProvider.java
index ca8e856..ec8166e 100644
--- a/opendj-admin/src/main/java/org/opends/server/admin/ClassLoaderProvider.java
+++ b/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;
+ }
}
--
Gitblit v1.10.0