001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2009 Sun Microsystems, Inc.
025 *      Portions copyright 2012-2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.config;
028
029import static com.forgerock.opendj.ldap.AdminMessages.*;
030import static com.forgerock.opendj.ldap.ExtensionMessages.NOTE_LOG_EXTENSION_INFORMATION;
031import static com.forgerock.opendj.util.StaticUtils.EOL;
032import static com.forgerock.opendj.util.StaticUtils.stackTraceToSingleLineString;
033
034import java.io.BufferedReader;
035import java.io.ByteArrayOutputStream;
036import java.io.File;
037import java.io.FileFilter;
038import java.io.IOException;
039import java.io.InputStream;
040import java.io.InputStreamReader;
041import java.io.PrintStream;
042import java.lang.reflect.Method;
043import java.net.MalformedURLException;
044import java.net.URL;
045import java.net.URLClassLoader;
046import java.util.ArrayList;
047import java.util.HashSet;
048import java.util.LinkedList;
049import java.util.List;
050import java.util.Set;
051import java.util.jar.Attributes;
052import java.util.jar.JarEntry;
053import java.util.jar.JarFile;
054import java.util.jar.Manifest;
055
056import org.forgerock.i18n.LocalizableMessage;
057import org.forgerock.i18n.slf4j.LocalizedLogger;
058import org.forgerock.opendj.config.server.ConfigException;
059import org.forgerock.opendj.server.config.meta.RootCfgDefn;
060import org.forgerock.util.Reject;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import com.forgerock.opendj.ldap.AdminMessages;
065
066/**
067 * This class is responsible for managing the configuration framework including:
068 * <ul>
069 * <li>loading core components during application initialization
070 * <li>loading extensions during and after application initialization
071 * <li>changing the property validation strategy based on whether the
072 * application is a client or server.
073 * </ul>
074 * This class defines a class loader which will be used for loading components.
075 * For extensions which define their own extended configuration definitions, the
076 * class loader will make sure that the configuration definition classes are
077 * loaded and initialized.
078 * <p>
079 * Initially the configuration framework is disabled, and calls to the
080 * {@link #getClassLoader()} will return the system default class loader.
081 * <p>
082 * Applications <b>MUST NOT</b> maintain persistent references to the class
083 * loader as it can change at run-time.
084 */
085public final class ConfigurationFramework {
086    /**
087     * Private URLClassLoader implementation. This is only required so that we
088     * can provide access to the addURL method.
089     */
090    private static final class MyURLClassLoader extends URLClassLoader {
091
092        /**
093         * Create a class loader with the default parent class loader.
094         */
095        public MyURLClassLoader() {
096            super(new URL[0]);
097        }
098
099        /**
100         * Create a class loader with the provided parent class loader.
101         *
102         * @param parent
103         *            The parent class loader.
104         */
105        public MyURLClassLoader(final ClassLoader parent) {
106            super(new URL[0], parent);
107        }
108
109        /**
110         * Add a Jar file to this class loader.
111         *
112         * @param jarFile
113         *            The name of the Jar file.
114         * @throws MalformedURLException
115         *             If a protocol handler for the URL could not be found, or
116         *             if some other error occurred while constructing the URL.
117         * @throws SecurityException
118         *             If a required system property value cannot be accessed.
119         */
120        public void addJarFile(final File jarFile) throws MalformedURLException {
121            addURL(jarFile.toURI().toURL());
122        }
123
124    }
125
126    private static final String MANIFEST =
127            "/META-INF/services/org.forgerock.opendj.config.AbstractManagedObjectDefinition";
128
129    private static final LocalizedLogger adminLogger = LocalizedLogger
130            .getLocalizedLogger(AdminMessages.resourceName());
131    private static final Logger debugLogger = LoggerFactory.getLogger(ConfigurationFramework.class);
132
133    /** The name of the lib directory. */
134    private static final String LIB_DIR = "lib";
135
136    /** The name of the extensions directory. */
137    private static final String EXTENSIONS_DIR = "extensions";
138
139    /** The singleton instance. */
140    private static final ConfigurationFramework INSTANCE = new ConfigurationFramework();
141
142    /** Attribute name in jar's MANIFEST corresponding to the revision number. */
143    private static final String REVISION_NUMBER = "Revision-Number";
144
145    /**
146     * The attribute names for build information is name, version and revision
147     * number.
148     */
149    private static final String[] BUILD_INFORMATION_ATTRIBUTE_NAMES = new String[] {
150        Attributes.Name.EXTENSION_NAME.toString(),
151        Attributes.Name.IMPLEMENTATION_VERSION.toString(), REVISION_NUMBER };
152
153    /**
154     * Returns the single application wide configuration framework instance.
155     *
156     * @return The single application wide configuration framework instance.
157     */
158    public static ConfigurationFramework getInstance() {
159        return INSTANCE;
160    }
161
162    /** Set of registered Jar files. */
163    private Set<File> jarFiles = new HashSet<>();
164
165    /**
166     * Underlying class loader used to load classes and resources (null
167     * if disabled).
168     * <p>
169     * We contain a reference to the URLClassLoader rather than
170     * sub-class it so that it is possible to replace the loader at
171     * run-time. For example, when removing or replacing extension Jar
172     * files (the URLClassLoader only supports adding new URLs, not removal).
173     */
174    private MyURLClassLoader loader;
175
176    private boolean isClient = true;
177    private String installPath;
178    private String instancePath;
179    private ClassLoader parent;
180
181    /** Private constructor. */
182    private ConfigurationFramework() {
183        // No implementation required.
184    }
185
186    /**
187     * Loads the named extensions into the configuration framework.
188     *
189     * @param extensions
190     *            The names of the extensions to be loaded. The names should not
191     *            contain any path elements and must be located within the
192     *            extensions folder.
193     * @throws ConfigException
194     *             If one of the extensions could not be loaded and initialized.
195     * @throws IllegalStateException
196     *             If the configuration framework has not yet been initialized.
197     * @throws IllegalArgumentException
198     *             If one of the extension names was not a single relative path
199     *             name element or was an absolute path.
200     */
201    public synchronized void addExtension(final String... extensions) throws ConfigException {
202        Reject.ifNull(extensions);
203        ensureInitialized();
204
205        final File libPath = new File(instancePath, LIB_DIR);
206        final File extensionsPath = new File(libPath, EXTENSIONS_DIR);
207
208        final ArrayList<File> files = new ArrayList<>(extensions.length);
209        for (final String extension : extensions) {
210            final File file = new File(extensionsPath, extension);
211
212            // For security reasons we need to make sure that the file name
213            // passed in did not contain any path elements and names a file
214            // in the extensions folder.
215
216            // Can handle potential null parent.
217            if (!extensionsPath.equals(file.getParentFile())) {
218                throw new IllegalArgumentException("Illegal file name: " + extension);
219            }
220
221            // The file is valid.
222            files.add(file);
223        }
224
225        // Add the extensions.
226        addExtension(files.toArray(new File[files.size()]));
227    }
228
229    /**
230     * Returns the class loader which should be used for loading classes and
231     * resources. When this configuration framework is disabled, the system
232     * default class loader will be returned by default.
233     * <p>
234     * Applications <b>MUST NOT</b> maintain persistent references to the class
235     * loader as it can change at run-time.
236     *
237     * @return Returns the class loader which should be used for loading classes
238     *         and resources.
239     */
240    public synchronized ClassLoader getClassLoader() {
241        if (loader != null) {
242            return loader;
243        } else {
244            return ClassLoader.getSystemClassLoader();
245        }
246    }
247
248    /**
249     * Initializes the configuration framework using the application's class
250     * loader as the parent class loader, and the current working directory as
251     * the install and instance path.
252     *
253     * @return The configuration framework.
254     * @throws ConfigException
255     *             If the configuration framework could not initialize
256     *             successfully.
257     * @throws IllegalStateException
258     *             If the configuration framework has already been initialized.
259     */
260    public ConfigurationFramework initialize() throws ConfigException {
261        return initialize(null);
262    }
263
264    /**
265     * Initializes the configuration framework using the application's class
266     * loader as the parent class loader, and the provided install/instance
267     * path.
268     *
269     * @param installAndInstancePath
270     *            The path where application binaries and data are located.
271     * @return The configuration framework.
272     * @throws ConfigException
273     *             If the configuration framework could not initialize
274     *             successfully.
275     * @throws IllegalStateException
276     *             If the configuration framework has already been initialized.
277     */
278    public ConfigurationFramework initialize(final String installAndInstancePath)
279            throws ConfigException {
280        return initialize(installAndInstancePath, installAndInstancePath);
281    }
282
283    /**
284     * Initializes the configuration framework using the application's class
285     * loader as the parent class loader, and the provided install and instance
286     * paths.
287     *
288     * @param installPath
289     *            The path where application binaries are located.
290     * @param instancePath
291     *            The path where application data are located.
292     * @return The configuration framework.
293     * @throws ConfigException
294     *             If the configuration framework could not initialize
295     *             successfully.
296     * @throws IllegalStateException
297     *             If the configuration framework has already been initialized.
298     */
299    public ConfigurationFramework initialize(final String installPath, final String instancePath)
300            throws ConfigException {
301        return initialize(installPath, instancePath, RootCfgDefn.class.getClassLoader());
302    }
303
304    /**
305     * Initializes the configuration framework using the provided parent class
306     * loader and install and instance paths.
307     *
308     * @param installPath
309     *            The path where application binaries are located.
310     * @param instancePath
311     *            The path where application data are located.
312     * @param parent
313     *            The parent class loader.
314     * @return The configuration framework.
315     * @throws ConfigException
316     *             If the configuration framework could not initialize
317     *             successfully.
318     * @throws IllegalStateException
319     *             If the configuration framework has already been initialized.
320     */
321    public synchronized ConfigurationFramework initialize(final String installPath,
322            final String instancePath, final ClassLoader parent) throws ConfigException {
323        if (loader != null) {
324            throw new IllegalStateException("configuration framework already initialized.");
325        }
326        this.installPath = installPath == null ? System.getenv("INSTALL_ROOT") : installPath;
327        if (instancePath != null) {
328            this.instancePath = instancePath;
329        } else {
330            this.instancePath = System.getenv("INSTANCE_ROOT") != null ? System.getenv("INSTANCE_ROOT")
331                    : this.installPath;
332        }
333        this.parent = parent;
334        initialize0();
335        return this;
336    }
337
338    /**
339     * Returns {@code true} if the configuration framework is being used within
340     * a client application. Client applications will perform less property
341     * value validation than server applications because they do not have
342     * resources available such as the server schema.
343     *
344     * @return {@code true} if the configuration framework is being used within
345     *         a client application.
346     */
347    public boolean isClient() {
348        return isClient;
349    }
350
351    /**
352     * Returns {@code true} if the configuration framework has been initialized.
353     *
354     * @return {@code true} if the configuration framework has been initialized.
355     */
356    public synchronized boolean isInitialized() {
357        return loader != null;
358    }
359
360    /**
361     * Prints out all information about extensions.
362     *
363     * @return A string representing all information about extensions;
364     *         <code>null</code> if there is no information available.
365     */
366    public String printExtensionInformation() {
367        final File extensionsPath =
368                new File(installPath + File.separator + LIB_DIR + File.separator + EXTENSIONS_DIR);
369
370        if (!extensionsPath.exists() || !extensionsPath.isDirectory()) {
371            // no extensions' directory
372            return null;
373        }
374
375        final File[] extensions = extensionsPath.listFiles(new FileFilter() {
376            @Override
377            public boolean accept(final File pathname) {
378                // only files with names ending with ".jar"
379                return pathname.isFile() && pathname.getName().endsWith(".jar");
380            }
381        });
382
383        if (extensions.length == 0) {
384            return null;
385        }
386
387        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
388        final PrintStream ps = new PrintStream(baos);
389        // prints:
390        // --
391        // Name Build number Revision number
392        ps.printf("--%s           %-20s %-20s %-20s%s", EOL, "Name", "Build number",
393                "Revision number", EOL);
394
395        for (final File extension : extensions) {
396            // retrieve MANIFEST entry and display name, build number and
397            // revision number
398            try {
399                final JarFile jarFile = new JarFile(extension);
400                final JarEntry entry = jarFile.getJarEntry(MANIFEST);
401                if (entry == null) {
402                    continue;
403                }
404
405                final String[] information = getBuildInformation(jarFile);
406
407                ps.append("Extension: ");
408                boolean addBlank = false;
409                for (final String name : information) {
410                    if (addBlank) {
411                        ps.append(addBlank ? " " : ""); // add blank if not
412                                                        // first append
413                    } else {
414                        addBlank = true;
415                    }
416
417                    ps.printf("%-20s", name);
418                }
419                ps.append(EOL);
420            } catch (final Exception e) {
421                // ignore extra information for this extension
422            }
423        }
424
425        return baos.toString();
426    }
427
428    /**
429     * Reloads the configuration framework.
430     *
431     * @throws ConfigException
432     *             If the configuration framework could not initialize
433     *             successfully.
434     * @throws IllegalStateException
435     *             If the configuration framework has not yet been initialized.
436     */
437    public synchronized void reload() throws ConfigException {
438        ensureInitialized();
439        loader = null;
440        jarFiles = new HashSet<>();
441        initialize0();
442    }
443
444    /**
445     * Specifies whether or not the configuration framework is being used within
446     * a client application. Client applications will perform less property
447     * value validation than server applications because they do not have
448     * resources available such as the server schema.
449     *
450     * @param isClient
451     *            {@code true} if the configuration framework is being used
452     *            within a client application.
453     * @return The configuration framework.
454     */
455    public ConfigurationFramework setIsClient(final boolean isClient) {
456        this.isClient = isClient;
457        return this;
458    }
459
460    private void addExtension(final File... extensions) throws ConfigException {
461        // First add the Jar files to the class loader.
462        final List<JarFile> jars = new LinkedList<>();
463        for (final File extension : extensions) {
464            if (jarFiles.contains(extension)) {
465                // Skip this file as it is already loaded.
466                continue;
467            }
468
469            // Attempt to load it.
470            jars.add(loadJarFile(extension));
471
472            // Register the Jar file with the class loader.
473            try {
474                loader.addJarFile(extension);
475            } catch (final Exception e) {
476                debugLogger.trace("Unable to register the jar file with the class loader", e);
477                final LocalizableMessage message =
478                        ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get(extension.getName(), extension
479                                .getParent(), stackTraceToSingleLineString(e, true));
480                throw new ConfigException(message);
481            }
482            jarFiles.add(extension);
483        }
484
485        // Now forcefully load the configuration definition classes.
486        for (final JarFile jar : jars) {
487            initializeExtension(jar);
488        }
489    }
490
491    private void ensureInitialized() {
492        if (loader == null) {
493            throw new IllegalStateException("configuration framework is disabled.");
494        }
495    }
496
497    /**
498     * Returns a String array with the following information : <br>
499     * index 0: the name of the extension. <br>
500     * index 1: the build number of the extension. <br>
501     * index 2: the revision number of the extension.
502     *
503     * @param extension
504     *            the jar file of the extension
505     * @return a String array containing the name, the build number and the
506     *         revision number of the extension given in argument
507     * @throws java.io.IOException
508     *             thrown if the jar file has been closed.
509     */
510    private String[] getBuildInformation(final JarFile extension) throws IOException {
511        final String[] result = new String[3];
512
513        // retrieve MANIFEST entry and display name, version and revision
514        final Manifest manifest = extension.getManifest();
515
516        if (manifest != null) {
517            final Attributes attributes = manifest.getMainAttributes();
518
519            int index = 0;
520            for (final String name : BUILD_INFORMATION_ATTRIBUTE_NAMES) {
521                String value = attributes.getValue(name);
522                if (value == null) {
523                    value = "<unknown>";
524                }
525                result[index++] = value;
526            }
527        }
528
529        return result;
530    }
531
532    private void initialize0() throws ConfigException {
533        if (parent != null) {
534            loader = new MyURLClassLoader(parent);
535        } else {
536            loader = new MyURLClassLoader();
537        }
538
539        // Forcefully load all configuration definition classes in
540        // OpenDS.jar.
541        initializeCoreComponents();
542
543        // Put extensions jars into the class loader and load all
544        // configuration definition classes in that they contain.
545        // First load the extension from the install directory, then
546        // from the instance directory.
547        File libDir;
548        File installExtensionsPath;
549        File instanceExtensionsPath;
550
551        // load install dir extension
552        libDir = new File(installPath, LIB_DIR);
553        try {
554            installExtensionsPath = new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
555        } catch (final Exception e) {
556            installExtensionsPath = new File(libDir, EXTENSIONS_DIR);
557        }
558        initializeAllExtensions(installExtensionsPath);
559
560        // load instance dir extension
561        libDir = new File(instancePath, LIB_DIR);
562        try {
563            instanceExtensionsPath = new File(libDir, EXTENSIONS_DIR).getCanonicalFile();
564        } catch (final Exception e) {
565            instanceExtensionsPath = new File(libDir, EXTENSIONS_DIR);
566        }
567        if (!installExtensionsPath.getAbsolutePath().equals(
568                instanceExtensionsPath.getAbsolutePath())) {
569            initializeAllExtensions(instanceExtensionsPath);
570        }
571    }
572
573    /**
574     * Put extensions jars into the class loader and load all configuration
575     * definition classes in that they contain.
576     *
577     * @param extensionsPath
578     *            Indicates where extensions are located.
579     * @throws ConfigException
580     *             If the extensions folder could not be accessed or if a
581     *             extension jar file could not be accessed or if one of the
582     *             configuration definition classes could not be initialized.
583     */
584    private void initializeAllExtensions(final File extensionsPath) throws ConfigException {
585        try {
586            if (!extensionsPath.exists()) {
587                // The extensions directory does not exist. This is not a
588                // critical problem.
589                adminLogger.error(ERR_ADMIN_NO_EXTENSIONS_DIR, String.valueOf(extensionsPath));
590                return;
591            }
592
593            if (!extensionsPath.isDirectory()) {
594                // The extensions directory is not a directory. This is more
595                // critical.
596                final LocalizableMessage message =
597                        ERR_ADMIN_EXTENSIONS_DIR_NOT_DIRECTORY.get(String.valueOf(extensionsPath));
598                throw new ConfigException(message);
599            }
600
601            // Get each extension file name.
602            final FileFilter filter = new FileFilter() {
603
604                /**
605                 * Must be a Jar file.
606                 */
607                @Override
608                public boolean accept(final File pathname) {
609                    if (!pathname.isFile()) {
610                        return false;
611                    }
612
613                    final String name = pathname.getName();
614                    return name.endsWith(".jar");
615                }
616
617            };
618
619            // Add and initialize the extensions.
620            addExtension(extensionsPath.listFiles(filter));
621        } catch (final ConfigException e) {
622            debugLogger.trace("Unable to initialize all extensions", e);
623            throw e;
624        } catch (final Exception e) {
625            debugLogger.trace("Unable to initialize all extensions", e);
626            final LocalizableMessage message =
627                    ERR_ADMIN_EXTENSIONS_CANNOT_LIST_FILES.get(String.valueOf(extensionsPath),
628                            stackTraceToSingleLineString(e, true));
629            throw new ConfigException(message, e);
630        }
631    }
632
633    /**
634     * Make sure all core configuration definitions are loaded.
635     *
636     * @throws ConfigException
637     *             If the core manifest file could not be read or if one of the
638     *             configuration definition classes could not be initialized.
639     */
640    private void initializeCoreComponents() throws ConfigException {
641        final InputStream is = RootCfgDefn.class.getResourceAsStream(MANIFEST);
642        if (is == null) {
643            final LocalizableMessage message = ERR_ADMIN_CANNOT_FIND_CORE_MANIFEST.get(MANIFEST);
644            throw new ConfigException(message);
645        }
646        try {
647            loadDefinitionClasses(is);
648        } catch (final ConfigException e) {
649            debugLogger.trace("Unable to initialize core components", e);
650            final LocalizableMessage message =
651                    ERR_CLASS_LOADER_CANNOT_LOAD_CORE.get(MANIFEST, stackTraceToSingleLineString(e,
652                            true));
653            throw new ConfigException(message);
654        }
655    }
656
657    /**
658     * Make sure all the configuration definition classes in a extension are
659     * loaded.
660     *
661     * @param jarFile
662     *            The extension's Jar file.
663     * @throws ConfigException
664     *             If the extension jar file could not be accessed or if one of
665     *             the configuration definition classes could not be
666     *             initialized.
667     */
668    private void initializeExtension(final JarFile jarFile) throws ConfigException {
669        final JarEntry entry = jarFile.getJarEntry(MANIFEST);
670        if (entry != null) {
671            InputStream is;
672            try {
673                is = jarFile.getInputStream(entry);
674            } catch (final Exception e) {
675                debugLogger.trace("Unable to get input stream from jar", e);
676                final LocalizableMessage message =
677                        ERR_ADMIN_CANNOT_READ_EXTENSION_MANIFEST.get(MANIFEST, jarFile.getName(),
678                                stackTraceToSingleLineString(e, true));
679                throw new ConfigException(message);
680            }
681
682            try {
683                loadDefinitionClasses(is);
684            } catch (final ConfigException e) {
685                debugLogger.trace("Unable to load classes from input stream", e);
686                final LocalizableMessage message =
687                        ERR_CLASS_LOADER_CANNOT_LOAD_EXTENSION.get(jarFile.getName(), MANIFEST,
688                                stackTraceToSingleLineString(e, true));
689                throw new ConfigException(message);
690            }
691            try {
692                // Log build information of extensions in the error log
693                final String[] information = getBuildInformation(jarFile);
694                final LocalizableMessage message =
695                        NOTE_LOG_EXTENSION_INFORMATION.get(jarFile.getName(), information[1],
696                                information[2]);
697                LocalizedLogger.getLocalizedLogger(message.resourceName()).error(message);
698            } catch (final Exception e) {
699                // Do not log information for that extension
700            }
701        }
702    }
703
704    /**
705     * Forcefully load configuration definition classes named in a manifest
706     * file.
707     *
708     * @param is
709     *            The manifest file input stream.
710     * @throws ConfigException
711     *             If the definition classes could not be loaded and
712     *             initialized.
713     */
714    private void loadDefinitionClasses(final InputStream is) throws ConfigException {
715        final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
716        final List<AbstractManagedObjectDefinition<?, ?>> definitions = new LinkedList<>();
717        while (true) {
718            String className;
719            try {
720                className = reader.readLine();
721            } catch (final IOException e) {
722                final LocalizableMessage msg =
723                        ERR_CLASS_LOADER_CANNOT_READ_MANIFEST_FILE.get(String.valueOf(e
724                                .getMessage()));
725                throw new ConfigException(msg, e);
726            }
727
728            // Break out when the end of the manifest is reached.
729            if (className == null) {
730                break;
731            }
732
733            // Skip blank lines.
734            className = className.trim();
735            if (className.length() == 0) {
736                continue;
737            }
738
739            // Skip lines beginning with #.
740            if (className.startsWith("#")) {
741                continue;
742            }
743
744            debugLogger.trace("Loading class " + className);
745
746            // Load the class and get an instance of it if it is a definition.
747            Class<?> theClass;
748            try {
749                theClass = Class.forName(className, true, loader);
750            } catch (final Exception e) {
751                final LocalizableMessage msg =
752                        ERR_CLASS_LOADER_CANNOT_LOAD_CLASS.get(className, String.valueOf(e
753                                .getMessage()));
754                throw new ConfigException(msg, e);
755            }
756            if (AbstractManagedObjectDefinition.class.isAssignableFrom(theClass)) {
757                // We need to instantiate it using its getInstance() static
758                // method.
759                Method method;
760                try {
761                    method = theClass.getMethod("getInstance");
762                } catch (final Exception e) {
763                    final LocalizableMessage msg =
764                            ERR_CLASS_LOADER_CANNOT_FIND_GET_INSTANCE_METHOD.get(className, String
765                                    .valueOf(e.getMessage()));
766                    throw new ConfigException(msg, e);
767                }
768
769                // Get the definition instance.
770                AbstractManagedObjectDefinition<?, ?> d;
771                try {
772                    d = (AbstractManagedObjectDefinition<?, ?>) method.invoke(null);
773                } catch (final Exception e) {
774                    final LocalizableMessage msg =
775                            ERR_CLASS_LOADER_CANNOT_INVOKE_GET_INSTANCE_METHOD.get(className,
776                                    String.valueOf(e.getMessage()));
777                    throw new ConfigException(msg, e);
778                }
779                definitions.add(d);
780            }
781        }
782
783        // Initialize any definitions that were loaded.
784        for (final AbstractManagedObjectDefinition<?, ?> d : definitions) {
785            try {
786                d.initialize();
787            } catch (final Exception e) {
788                final LocalizableMessage msg =
789                        ERR_CLASS_LOADER_CANNOT_INITIALIZE_DEFN.get(d.getName(), d.getClass()
790                                .getName(), String.valueOf(e.getMessage()));
791                throw new ConfigException(msg, e);
792            }
793        }
794    }
795
796    private JarFile loadJarFile(final File jar) throws ConfigException {
797        try {
798            // Load the extension jar file.
799            return new JarFile(jar);
800        } catch (final Exception e) {
801            debugLogger.trace("Unable to load jar file: " + jar, e);
802
803            final LocalizableMessage message =
804                    ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get(jar.getName(), jar.getParent(),
805                            stackTraceToSingleLineString(e, true));
806            throw new ConfigException(message);
807        }
808    }
809
810    /**
811     * Returns the installation path.
812     *
813     * @return The installation path of this instance.
814     */
815    public String getInstallPath() {
816        return installPath;
817    }
818
819    /**
820     * Returns the instance path.
821     *
822     * @return The instance path.
823     */
824    public String getInstancePath() {
825        return instancePath;
826    }
827
828}