/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2009 Sun Microsystems, Inc. * Portions copyright 2012-2014 ForgeRock AS. */ package org.forgerock.opendj.config; import static com.forgerock.opendj.ldap.AdminMessages.*; import static com.forgerock.opendj.ldap.ExtensionMessages.NOTE_LOG_EXTENSION_INFORMATION; import static com.forgerock.opendj.util.StaticUtils.EOL; import static com.forgerock.opendj.util.StaticUtils.stackTraceToSingleLineString; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.server.config.meta.RootCfgDefn; import org.forgerock.util.Reject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.forgerock.opendj.ldap.AdminMessages; /** * This class is responsible for managing the configuration framework including: *
* Initially the configuration framework is disabled, and calls to the * {@link #getClassLoader()} will return the system default class loader. *
* Applications MUST NOT maintain persistent references to the class
* loader as it can change at run-time.
*/
public final class ConfigurationFramework {
/**
* Private URLClassLoader implementation. This is only required so that we
* can provide access to the addURL method.
*/
private static final class MyURLClassLoader extends URLClassLoader {
/**
* Create a class loader with the default parent class loader.
*/
public MyURLClassLoader() {
super(new URL[0]);
}
/**
* Create a class loader with the provided parent class loader.
*
* @param parent
* The parent class loader.
*/
public MyURLClassLoader(final 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(final File jarFile) throws MalformedURLException {
addURL(jarFile.toURI().toURL());
}
}
private static final String MANIFEST =
"/META-INF/services/org.forgerock.opendj.config.AbstractManagedObjectDefinition";
private static final LocalizedLogger adminLogger = LocalizedLogger
.getLocalizedLogger(AdminMessages.resourceName());
private static final Logger debugLogger = LoggerFactory.getLogger(ConfigurationFramework.class);
// 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 ConfigurationFramework INSTANCE = new ConfigurationFramework();
// 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 };
/**
* Returns the single application wide configuration framework instance.
*
* @return The single application wide configuration framework instance.
*/
public static ConfigurationFramework getInstance() {
return INSTANCE;
}
// Set of registered Jar files.
private Set
* Applications MUST NOT 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();
}
}
/**
* Initializes the configuration framework using the application's class
* loader as the parent class loader, and the current working directory as
* the install and instance path.
*
* @return The configuration framework.
* @throws ConfigException
* If the configuration framework could not initialize
* successfully.
* @throws IllegalStateException
* If the configuration framework has already been initialized.
*/
public ConfigurationFramework initialize() throws ConfigException {
return initialize(null);
}
/**
* Initializes the configuration framework using the application's class
* loader as the parent class loader, and the provided install/instance
* path.
*
* @param installAndInstancePath
* The path where application binaries and data are located.
* @return The configuration framework.
* @throws ConfigException
* If the configuration framework could not initialize
* successfully.
* @throws IllegalStateException
* If the configuration framework has already been initialized.
*/
public ConfigurationFramework initialize(final String installAndInstancePath)
throws ConfigException {
return initialize(installAndInstancePath, installAndInstancePath);
}
/**
* Initializes the configuration framework using the application's class
* loader as the parent class loader, and the provided install and instance
* paths.
*
* @param installPath
* The path where application binaries are located.
* @param instancePath
* The path where application data are located.
* @return The configuration framework.
* @throws ConfigException
* If the configuration framework could not initialize
* successfully.
* @throws IllegalStateException
* If the configuration framework has already been initialized.
*/
public ConfigurationFramework initialize(final String installPath, final String instancePath)
throws ConfigException {
return initialize(installPath, instancePath, RootCfgDefn.class.getClassLoader());
}
/**
* Initializes the configuration framework using the provided parent class
* loader and install and instance paths.
*
* @param installPath
* The path where application binaries are located.
* @param instancePath
* The path where application data are located.
* @param parent
* The parent class loader.
* @return The configuration framework.
* @throws ConfigException
* If the configuration framework could not initialize
* successfully.
* @throws IllegalStateException
* If the configuration framework has already been initialized.
*/
public synchronized ConfigurationFramework initialize(final String installPath,
final String instancePath, final ClassLoader parent) throws ConfigException {
if (loader != null) {
throw new IllegalStateException("configuration framework already initialized.");
}
this.installPath = installPath == null ? System.getProperty("user.dir") : installPath;
this.instancePath = instancePath == null ? installPath : instancePath;
this.parent = parent;
initialize0();
return this;
}
/**
* Returns {@code true} if the configuration framework is being used within
* a client application. Client applications will perform less property
* value validation than server applications because they do not have
* resources available such as the server schema.
*
* @return {@code true} if the configuration framework is being used within
* a client application.
*/
public boolean isClient() {
return isClient;
}
/**
* Returns {@code true} if the configuration framework has been initialized.
*
* @return {@code true} if the configuration framework has been initialized.
*/
public synchronized boolean isInitialized() {
return loader != null;
}
/**
* Prints out all information about extensions.
*
* @return A string representing all information about extensions;
* null if there is no information available.
*/
public String printExtensionInformation() {
final File extensionsPath =
new File(new StringBuilder(installPath).append(File.separator).append(LIB_DIR)
.append(File.separator).append(EXTENSIONS_DIR).toString());
if (!extensionsPath.exists() || !extensionsPath.isDirectory()) {
// no extensions' directory
return null;
}
final File[] extensions = extensionsPath.listFiles(new FileFilter() {
@Override
public boolean accept(final File pathname) {
// only files with names ending with ".jar"
return pathname.isFile() && pathname.getName().endsWith(".jar");
}
});
if (extensions.length == 0) {
return null;
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final 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 (final File extension : extensions) {
// retrieve MANIFEST entry and display name, build number and
// revision number
try {
final JarFile jarFile = new JarFile(extension);
final JarEntry entry = jarFile.getJarEntry(MANIFEST);
if (entry == null) {
continue;
}
final String[] information = getBuildInformation(jarFile);
ps.append("Extension: ");
boolean addBlank = false;
for (final 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 (final Exception e) {
// ignore extra information for this extension
}
}
return baos.toString();
}
/**
* Reloads the configuration framework.
*
* @throws ConfigException
* If the configuration framework could not initialize
* successfully.
* @throws IllegalStateException
* If the configuration framework has not yet been initialized.
*/
public synchronized void reload() throws ConfigException {
ensureInitialized();
loader = null;
jarFiles = new HashSet
* index 0: the name of the extension.
* index 1: the build number of the extension.
* 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(final JarFile extension) throws IOException {
final String[] result = new String[3];
// retrieve MANIFEST entry and display name, version and revision
final Manifest manifest = extension.getManifest();
if (manifest != null) {
final Attributes attributes = manifest.getMainAttributes();
int index = 0;
for (final String name : BUILD_INFORMATION_ATTRIBUTE_NAMES) {
String value = attributes.getValue(name);
if (value == null) {
value = "