/* * 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-2015 ForgeRock AS. */ package org.opends.server.admin; import static org.opends.messages.AdminMessages.*; import static org.opends.messages.ExtensionMessages.*; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.util.ServerConstants.EOL; import java.io.ByteArrayOutputStream; import java.io.BufferedReader; 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.opends.server.admin.std.meta.RootCfgDefn; import org.opends.server.core.DirectoryServer; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.types.InitializationException; import org.forgerock.util.Reject; /** * Manages the class loader which should be used for loading * configuration definition classes and associated extensions. *
* For extensions which define their own extended configuration * definitions, the class loader will make sure that the configuration * definition classes are loaded and initialized. *
* Initially the class loader provider 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 ClassLoaderProvider {
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
* 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(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
* 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();
}
}
/**
* Indicates whether this class loader provider is enabled.
*
* @return Returns
* 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;
/** 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 {
Reject.ifNull(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);
ArrayListtrue 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.
Listnull if there is no information available.
*/
public String printExtensionInformation() {
String pathname = DirectoryServer.getServerRoot() + File.separator + LIB_DIR + File.separator + EXTENSIONS_DIR;
File extensionsPath = new File(pathname);
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 :
*
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(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 = "