io.smartspaces.launcher.bootstrap.SmartSpacesFrameworkBootstrap.java Source code

Java tutorial

Introduction

Here is the source code for io.smartspaces.launcher.bootstrap.SmartSpacesFrameworkBootstrap.java

Source

/*
 * Copyright (C) 2016 Keith M. Hughes
 * Copyright (C) 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package io.smartspaces.launcher.bootstrap;

import io.smartspaces.launcher.base.SmartSpacesReturnCodes;
import io.smartspaces.system.core.configuration.ConfigurationProvider;
import io.smartspaces.system.core.configuration.CoreConfiguration;
import io.smartspaces.system.core.container.ContainerCustomizerProvider;
import io.smartspaces.system.core.container.ContainerFilesystemLayout;
import io.smartspaces.system.core.container.SimpleContainerCustomizerProvider;
import io.smartspaces.system.core.container.SmartSpacesStartLevel;
import io.smartspaces.system.core.container.SmartSpacesSystemControl;
import io.smartspaces.system.core.logging.LoggingProvider;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

/**
 * The boostrapper for Smart Spaces.
 *
 * @author Keith M. Hughes
 */
public class SmartSpacesFrameworkBootstrap {

    /**
     * The system property name for the Felix Gogo shell arguments.
     */
    private static final String SYSTEM_PROPERTY_NAME_GOGO_ARGS = "gosh.args";

    /**
     * The value for the system property for a non-interactive Felix Gogo shell.
     */
    private static final String SYSTEM_PROPERTY_VALUE_GOGO_ARGS_NOINTERACTIVE = "--nointeractive";

    /**
     * Configuration parameter to specify if startup order of bundles should be
     * logged.
     */
    public static final String CONFIG_PROPERTY_STARTUP_LOGGING = "smartspaces.logging.container.startup";

    /**
     * Configuration parameter value to specify if startup order of bundles should
     * be logged.
     */
    public static final String CONFIG_PROPERTY_VALUE_STARTUP_LOGGING = "true";

    /**
     * The argument for saying the container should run with no shell access.
     */
    public static final String ARGS_NOSHELL = "--noshell";

    /**
     * Command line argument prefix for specifying a specific runtime path. This
     * should match the value of
     * {@code smartspacesLauncher.COMMAND_LINE_RUNTIME_PREFIX}, but can't be a
     * shared variable because of package dependency considerations.
     */
    public static final String ARGS_PREFIX_RUNTIME = "--runtime=";

    /**
     * Command line argument prefix for specifying a specific config path. This
     * should match the value of
     * {@code smartspacesLauncher.COMMAND_LINE_CONFIG_PREFIX}, but can't be a
     * shared variable because of package dependency considerations.
     */
    public static final String ARGS_PREFIX_CONFIG = "--config=";

    /**
     * Command line argument prefix for adding in extra bootstrap folders.
     */
    public static final String ARGS_PREFIX_BOOTSTRAP = "--bootstrap=";

    /**
     * The OSGi wild card for exporting nested packages.
     */
    public static final String NESTED_PACKAGE_WILDCARD = ".*";

    /**
     * Comment character for delegations.conf.
     */
    public static final String DELEGATIONS_CONF_FILE_COMMENT = "#";

    /**
     * The comment character used in files found in meta-inf, at least for OSGi
     * framework factory.
     */
    public static final char META_INF_COMMENT_CHARACTER = '#';

    /**
     * The location of the delegations.conf file relative to the SS install.
     */
    public static final String LOCATION_DELEGATIONS_CONF = "lib/system/jvm/delegations.conf";

    /**
     * External packages loaded from the Smart Spaces system folder that must be
     * exposed for things to work.
     *
     * <p>
     * These packages are critical, IS is crippled without them which is why they
     * are here in the code.
     */
    public static final String[] PACKAGES_SYSTEM_EXTERNAL = new String[] {
            "org.apache.commons.logging; version=1.1.1", "org.apache.commons.logging.impl; version=1.1.1",
            "javax.transaction; version=1.1.0", "javax.transaction.xa; version=1.1.0", "javax.transaction",
            "javax.transaction.xa" };

    /**
     * The interface describing the container system control.
     */
    public static final Class<SmartSpacesSystemControl> CONTAINER_SYSTEM_CONTROL_INTERFACE = SmartSpacesSystemControl.class;

    /**
     * The interface describing the container customizer provider.
     */
    public static final Class<ContainerCustomizerProvider> CONTAINER_COSTUMIZER_PROVIDER_INTERFACE = ContainerCustomizerProvider.class;

    /**
     * The interface describing the configuration provider.
     */
    public static final Class<ConfigurationProvider> CONFIGURATION_PROVIDER_INTERFACE = ConfigurationProvider.class;

    /**
     * The interface describing the logging provider.
     */
    public static final Class<LoggingProvider> LOGGING_PROVIDER_INTERFACE = LoggingProvider.class;

    /**
     * Packages loaded from the Smart Spaces system folder that are part of Smart
     * Spaces.
     */
    public static final String[] PACKAGES_SYSTEM_SMARTSPACES = new String[] {
            LOGGING_PROVIDER_INTERFACE.getPackage().getName(),
            CONFIGURATION_PROVIDER_INTERFACE.getPackage().getName(),
            CONTAINER_COSTUMIZER_PROVIDER_INTERFACE.getPackage().getName() };

    /**
     * The Jar Manifest property that gives the Smart Spaces version.
     */
    public static final String MANIFEST_PROPERTY_SMARTSPACES_VERSION = "Bundle-Version";

    /**
     * The folder where Smart Spaces will cache OSGi plugins. This is relative to
     * the run folder.
     */
    public static final String FOLDER_PLUGINS_CACHE = "plugins-cache";

    /**
     * Where the OSGi framework launcher can be found.
     */
    public static final String OSGI_FRAMEWORK_LAUNCH_FRAMEWORK_FACTORY = "META-INF/services/org.osgi.framework.launch.FrameworkFactory";

    /**
     * Bundle manifest header indicating the start level to use.
     */
    public static final String BUNDLE_MANIFEST_START_LEVEL_HEADER = "SmartSpaces-StartLevel";

    /**
     * Command line argument prefix string for configuration value definitions.
     */
    public static final String COMMAND_LINE_VALUE_DEFINITION_PREFIX = "-D";

    /**
     * Command line configuration value key/value split specification.
     */
    public static final String COMMAND_LINE_VALUE_DEFINITION_SPLIT = "=";

    /**
     * Environment variable that indicates the home install directory for Smart
     * Spaces.
     */
    private static final String SMARTSPACES_HOME_ENVIRONMENT_KEY = "SMARTSPACES_HOME";

    /**
     * Default directory for the home directory relative to install of this
     * component.
     */
    private static final String SMARTSPACES_HOME_DEFAULT_DIR = "..";

    /**
     * The name of the System property for the current operating system.
     */
    private static final String SYSTEM_PROPERTY_OS_NAME = "os.name";

    /**
     * The value of the System property value for the current operating system
     * when it is Linux.
     */
    private static final String SYSTEM_PROPERTY_OS_VALUE_LINUX = "Linux";

    /**
     * The value of the System property value for the current operating system
     * when it is OSX (Mac).
     */
    private static final String SYSTEM_PROPERTY_OS_VALUE_OSX = "Mac OS X";

    /**
     * The beginning of the System property value for the current operating system
     * when it is Windows.
     */
    private static final String SYSTEM_PROPERTY_OS_PREFIX_WINDOWS = "Windows";

    /**
     * The OSGI framework which has been started.
     */
    private Framework framework;

    /**
     * A map of bundle symbolic names to start levels for those bundles.
     */
    private Map<String, SmartSpacesStartLevel> bundleStartLevels = new HashMap<String, SmartSpacesStartLevel>();

    /**
     * Extra folders to be added to the bootstrap from the commandline.
     */
    private final List<File> extraBootstrapFolders = new ArrayList<>();

    /**
     * All bundles installed.
     */
    private final Set<Bundle> installedBundles = new HashSet<>();

    /**
     * The initial set of bundles to load.
     */
    private List<File> initialBundles;

    /**
     * Whether or not the OSGi shell is needed.
     */
    private boolean needShell = true;

    /**oshell
     * Logging provider for the container.
     */
    private Log4jLoggingProvider loggingProvider;

    /**
     * The configuration provider for the container.
     */
    private FileConfigurationProvider configurationProvider;

    /**
     * The container customizer provider for the container.
     */
    private SimpleContainerCustomizerProvider containerCustomizerProvider;

    /**
     * The base install folder for Smart Spaces.
     */
    private File baseInstallFolder;

    /**
     * The root runtime directory for this container. May be the same as the base
     * install folder, but can be independently controlled to allow for multiple
     * runtime instances.
     */
    private File runtimeFolder;

    /**
     * The root config directory for this container.
     */
    private File configFolder;

    /**
     * The system control for the container.
     */
    private SmartSpacesSystemControl systemControl;

    /**
     * The OSGi bundle context for the OSGi framework bundle.
     */
    private BundleContext rootBundleContext;

    /**
     * The start level for the OSGi framework.
     */
    private FrameworkStartLevel frameworkStartLevel;

    /**
     * The shutdown option for the container.
     * 
     * <p>
     * Set to the default value in case the container is exited from the OSGi
     * shell.
     */
    private int shutdownReturnCode = SmartSpacesReturnCodes.RETURN_CODE_SUCCESS;

    /**
     * Boot the framework.
     *
     * @param args
     *          the arguments to be passed to the bootstrap
     * 
     * @return the return code for the Java process, see
     *         {@link SmartSpacesReturnCodes}
     */
    public int boot(List<String> args) {

        baseInstallFolder = new File(".").getAbsoluteFile().getParentFile();

        // Set default values for various directories.
        runtimeFolder = baseInstallFolder;
        configFolder = new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_DEFAULT_CONFIG);

        processCommandLineArgs(args);

        if (!needShell) {
            System.setProperty(SYSTEM_PROPERTY_NAME_GOGO_ARGS, SYSTEM_PROPERTY_VALUE_GOGO_ARGS_NOINTERACTIVE);
        }

        initialBundles = new ArrayList<File>();

        initializeBundleStartLevels();

        systemControl = new SmartSpacesSystemControl() {
            @Override
            public void softRestart() {
                shutdownReturnCode = SmartSpacesReturnCodes.RETURN_CODE_RESTART_SOFT;
                stopContainer();
            }

            @Override
            public void shutdown() {
                shutdownReturnCode = SmartSpacesReturnCodes.RETURN_CODE_SUCCESS;
                stopContainer();
            }

            @Override
            public void hardRestart() {
                shutdownReturnCode = SmartSpacesReturnCodes.RETURN_CODE_RESTART_HARD;
                stopContainer();
            }

            /**
             * Fully stop the container.
             */
            private void stopContainer() {
                try {
                    framework.stop();
                } catch (BundleException e) {
                    e.printStackTrace();
                }
            }
        };

        setupExceptionHandler();

        File bootstrapBundleFolder = new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_SYSTEM_BOOTSTRAP);

        try {

            bootAndRunFramework(args, bootstrapBundleFolder);

            return shutdownReturnCode;
        } catch (Throwable ex) {
            if (loggingProvider != null && loggingProvider.getLog() != null) {
                loggingProvider.getLog().error("Error starting framework", ex);
            } else {
                System.err.println("Error starting framework");
                ex.printStackTrace();
            }
            return SmartSpacesReturnCodes.RETURN_CODE_FAILURE;
        }
    }

    /**
     * Boot and run the framework.
     * 
     * <p>
     * This method will return when the OSGi framework shuts down.
     * 
     * @param args
     *          the command line args
     * @param bootstrapBundleFolder
     *          the folder that contains the bootstrap bundles
     * 
     * @throws Exception
     *           something bad happened while the framework was setting up
     */
    private void bootAndRunFramework(List<String> args, File bootstrapBundleFolder) throws Exception {
        getBootstrapBundleJars(bootstrapBundleFolder);
        setupShutdownHandler();

        loadStartupFolder();
        loadExtraBootstrapFolders();

        createCoreServices(args);

        if (initialBundles.isEmpty()) {
            throw new RuntimeException("No bootstrap bundles to install.");
        }

        File environmentFolder = new File(configFolder, ContainerFilesystemLayout.FOLDER_CONFIG_ENVIRONMENT);
        ExtensionsReader extensionsReader = new ExtensionsReader(loggingProvider.getLog());
        extensionsReader.processExtensionFiles(environmentFolder);

        createFramework(extensionsReader);

        registerCoreServices();

        loadLibraries(extensionsReader.getLoadLibraries());
        loadClasses(extensionsReader.getLoadClasses());

        addContainerPathBundles(extensionsReader.getContainerPath());

        framework.start();

        startBundles();
        frameworkStartLevel.setStartLevel(SmartSpacesStartLevel.STARTUP_LEVEL_LAST.getStartLevel());

        framework.waitForStop(0);
    }

    /**
     * Populate the map for bundle start levels.
     */
    private void initializeBundleStartLevels() {
        // TODO(keith): Once Spring is removed, see if this is even necessary.
        bundleStartLevels.put("smartspaces.master.webapp", SmartSpacesStartLevel.STARTUP_LEVEL_LAST);
        bundleStartLevels.put("smartspaces.master", SmartSpacesStartLevel.STARTUP_LEVEL_PENULTIMATE);

        // TODO(keith): Get bundle dependencies so that this isn't necessary.
        bundleStartLevels.put("smartspaces.spacecontroller", SmartSpacesStartLevel.STARTUP_LEVEL_LAST);
    }

    /**
     * Process the command line arguments for this container.
     *
     * @param args
     *          the list of command line arguments
     */
    private void processCommandLineArgs(List<String> args) {
        for (String arg : args) {
            if (arg.equals(ARGS_NOSHELL)) {
                needShell = false;
            } else if (arg.startsWith(ARGS_PREFIX_RUNTIME)) {
                runtimeFolder = new File(arg.substring(ARGS_PREFIX_RUNTIME.length()));
            } else if (arg.startsWith(ARGS_PREFIX_CONFIG)) {
                configFolder = new File(arg.substring(ARGS_PREFIX_CONFIG.length()));
            } else if (arg.startsWith(ARGS_PREFIX_BOOTSTRAP)) {
                extraBootstrapFolders.add(new File(arg.substring(ARGS_PREFIX_BOOTSTRAP.length())));
            }
        }
    }

    /**
     * Load the contents of the startup folder, which contains additional
     * resources for the container.
     *
     * @throws Exception
     *           could not create the startup folder or load it
     */
    private void loadStartupFolder() throws Exception {
        File startupFolder = new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_USER_BOOTSTRAP);
        if (startupFolder.exists()) {
            if (startupFolder.isFile()) {
                throw new Exception(String.format("User bootstrap folder %s is a file not a folder.",
                        startupFolder.getAbsolutePath()));
            }
        } else if (!startupFolder.mkdirs()) {
            throw new Exception(
                    String.format("Cannot create user bootstrap folder %s.", startupFolder.getAbsolutePath()));
        }

        getBootstrapBundleJars(startupFolder);
    }

    /**
     * Add in all extra bootstrap folders.
     */
    private void loadExtraBootstrapFolders() {
        for (File extraBootstrapFolder : extraBootstrapFolders) {
            // Only add ones that are directories. This also means they exist.
            if (extraBootstrapFolder.isDirectory()) {
                getBootstrapBundleJars(extraBootstrapFolder);
            }
        }
    }

    /**
     * Set up the default exception handler.
     */
    private void setupExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                loggingProvider.getLog()
                        .error(String.format("Caught previously uncaught exception from thread %s", t), e);
            }
        });
    }

    /**
     * Add configuration items that can be automatically determined to the given
     * FileConfigurationProvider.
     *
     * @param configurationProvider
     *          the FileConfigurationProvider to inject key, value pairs of config
     *          data
     */
    private void addAutomaticConfiguration(FileConfigurationProvider configurationProvider) {
        // Calculate the proper home directory for this install of Smart
        // Spaces.
        String isHomeEnvPath = System.getenv(SMARTSPACES_HOME_ENVIRONMENT_KEY);
        File isHomeDir = isHomeEnvPath != null ? new File(isHomeEnvPath)
                : new File(baseInstallFolder, SMARTSPACES_HOME_DEFAULT_DIR);
        configurationProvider.put(CoreConfiguration.CONFIGURATION_NAME_SMARTSPACES_HOME,
                isHomeDir.getAbsolutePath());

        String platformOs = System.getProperty(SYSTEM_PROPERTY_OS_NAME);
        // Convert the Os string into the format Smart Spaces is expecting.
        if (platformOs.equals(SYSTEM_PROPERTY_OS_VALUE_LINUX)) {
            platformOs = CoreConfiguration.CONFIGURATION_VALUE_PLATFORM_OS_LINUX;
        } else if (platformOs.equals(SYSTEM_PROPERTY_OS_VALUE_OSX)) {
            platformOs = CoreConfiguration.CONFIGURATION_VALUE_PLATFORM_OS_OSX;
        } else if (platformOs.startsWith(SYSTEM_PROPERTY_OS_PREFIX_WINDOWS)) {
            platformOs = CoreConfiguration.CONFIGURATION_VALUE_PLATFORM_OS_WINDOWS;
        } else {
            loggingProvider.getLog()
                    .warn(String.format(
                            "Unsupported operating system: %s. Native applications will not function properly.",
                            platformOs));
            platformOs = CoreConfiguration.CONFIGURATION_VALUE_PLATFORM_OS_UNKNOWN;
        }

        configurationProvider.put(CoreConfiguration.CONFIGURATION_NAME_SMARTSPACES_PLATFORM_OS, platformOs);
        configurationProvider.put(CoreConfiguration.CONFIGURATION_NAME_PLATFORM_FILE_SEPARATOR, File.separator);
    }

    /**
     * Add command line configuration parameters to the given system
     * configuration.
     *
     * @param configurationProvider
     *          the FileConfigurationProvider which will have the command line
     *          configurations added
     * @param args
     *          the command line arguments
     *
     * @return the command line arguments minus the configuration parameters
     */
    private List<String> addCommandLineConfigurationParameters(FileConfigurationProvider configurationProvider,
            List<String> args) {
        List<String> newArgs = new ArrayList<String>();

        for (String arg : args) {
            if (arg.startsWith(COMMAND_LINE_VALUE_DEFINITION_PREFIX)) {
                String[] parts = arg.split(COMMAND_LINE_VALUE_DEFINITION_SPLIT, 2);
                configurationProvider.put(parts[0].substring(COMMAND_LINE_VALUE_DEFINITION_PREFIX.length()),
                        parts.length > 1 ? parts[1] : "");
            } else {
                newArgs.add(arg);
            }
        }

        return newArgs;
    }

    /**
     * Create a FileConfigurationProvider, assign it to configurationProvider,
     * inject automatic configuration, and then load configuration from config
     * files.
     *
     * @param args
     *          the command line arguments
     *
     * @return the command line arguments minus configuration parameters
     */
    private List<String> initializeFileConfigurationProvider(List<String> args) {
        configurationProvider = new FileConfigurationProvider(baseInstallFolder, configFolder,
                loggingProvider.getLog());

        // The order is important, as it determines which source of
        // configuration
        // parameters overrides the other sources.
        // Configuration sources that are invoked later override those that are
        // done
        // sooner. The order is:
        // 1) automatic configuration
        // 2) file configuration
        // 3) command-line configuration
        addAutomaticConfiguration(configurationProvider);
        configurationProvider.load();
        args = addCommandLineConfigurationParameters(configurationProvider, args);

        return args;
    }

    /**
     * Create the core services to the base bundle which are platform dependent.
     *
     * @param args
     *          the list of command line arguments
     */
    private void createCoreServices(List<String> args) {
        loggingProvider = new Log4jLoggingProvider();
        loggingProvider.configure(runtimeFolder, configFolder);

        args = initializeFileConfigurationProvider(args);

        containerCustomizerProvider = new SimpleContainerCustomizerProvider(args, true);
    }

    /**
     * Register all bootstrap core services with the container.
     */
    public void registerCoreServices() {
        rootBundleContext.registerService(LOGGING_PROVIDER_INTERFACE.getName(), loggingProvider, null);
        rootBundleContext.registerService(CONFIGURATION_PROVIDER_INTERFACE.getName(), configurationProvider, null);
        rootBundleContext.registerService(CONTAINER_COSTUMIZER_PROVIDER_INTERFACE.getName(),
                containerCustomizerProvider, null);
        rootBundleContext.registerService(CONTAINER_SYSTEM_CONTROL_INTERFACE, systemControl, null);
    }

    /**
     * Start all bundles.
     *
     * @throws BundleException
     *           something happened while starting bundles that could not be
     *           recovered from
     */
    private void startBundles() throws BundleException {
        for (File bundleFile : initialBundles) {
            String bundleUri = bundleFile.getAbsoluteFile().toURI().toString();

            try {
                Bundle bundle = rootBundleContext.installBundle(bundleUri);

                String symbolicName = bundle.getSymbolicName();
                if (symbolicName != null) {
                    SmartSpacesStartLevel startLevel = bundleStartLevels.get(symbolicName);
                    if (startLevel == null) {
                        String smartspacesStartLevel = bundle.getHeaders().get(BUNDLE_MANIFEST_START_LEVEL_HEADER);
                        if (smartspacesStartLevel != null) {
                            startLevel = SmartSpacesStartLevel.valueOf(smartspacesStartLevel);
                        } else {
                            startLevel = SmartSpacesStartLevel.STARTUP_LEVEL_DEFAULT;
                        }
                    }

                    if (startLevel != SmartSpacesStartLevel.STARTUP_LEVEL_DEFAULT) {
                        bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel.getStartLevel());
                    }

                    installedBundles.add(bundle);
                } else {
                    logBadBundle(bundleUri, new Exception("No symbolic name in bundle"));
                }
            } catch (Exception e) {
                logBadBundle(bundleUri, e);
            }
        }

        // Start all installed non-fragment bundles.
        for (Bundle bundle : installedBundles) {
            if (isFragment(bundle)) {
                continue;
            }

            startBundle(bundle);
        }
    }

    /**
     * Log that we had a bad bundle.
     *
     * @param bundleUri
     *          URI for the bundle
     * @param e
     *          triggering exception
     */
    private void logBadBundle(String bundleUri, Exception e) {
        loggingProvider.getLog().error(
                String.format("Bundle %s is not an OSGi bundle, skipping during Smart Spaces startup", bundleUri),
                e);
    }

    /**
     * Start a particular bundle.
     *
     * @param bundle
     *          the bundle to start
     */
    private void startBundle(Bundle bundle) {
        try {
            bundle.start();
        } catch (Exception e) {
            loggingProvider.getLog().error(String.format("Error while starting bundle %s", bundle.getLocation()),
                    e);
        }
    }

    /**
     * Create, configure, and start the OSGi framework instance.
     *
     * @param extensionsReader
     *          the reader for extensions files
     *
     * @throws Exception
     *           unable to create and/or start the framework
     */
    private void createFramework(ExtensionsReader extensionsReader) throws Exception {
        Map<String, String> frameworkConfig = new HashMap<String, String>();

        frameworkConfig.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);

        // The bootloader delegation loads all classes in the java install. This
        // covers things like the javax classes which
        // are not automatically exposed through the OSGi bundle classloaders.
        List<String> bootDelegationPackages = new ArrayList<String>();

        // Extra system packages are not found in the boot classloader but need
        // to
        // be exported by the system bundle
        // classloader.
        List<String> extraSystemPackages = new ArrayList<String>();

        bootDelegationPackages.addAll(extensionsReader.getBootPackages());

        processDelegationsFile(bootDelegationPackages, extraSystemPackages);
        configureBootDelegationPackages(frameworkConfig, bootDelegationPackages);

        // Get the initial packages into the extra system packages.
        Collections.addAll(extraSystemPackages, PACKAGES_SYSTEM_EXTERNAL);
        Collections.addAll(extraSystemPackages, PACKAGES_SYSTEM_SMARTSPACES);
        extraSystemPackages.addAll(extensionsReader.getPackages());
        configureExtraSystemPackages(frameworkConfig, extraSystemPackages);

        frameworkConfig.put(CoreConfiguration.CONFIGURATION_NAME_SMARTSPACES_BASE_INSTALL_DIR,
                baseInstallFolder.getAbsolutePath());

        frameworkConfig.put(CoreConfiguration.CONFIGURATION_NAME_SMARTSPACES_RUNTIME_DIR,
                runtimeFolder.getAbsolutePath());

        frameworkConfig.put(CoreConfiguration.CONFIGURATION_NAME_SMARTSPACES_VERSION, getSmartSpacesVersion());

        frameworkConfig.putAll(configurationProvider.getInitialConfiguration());

        File pluginsCacheFolder = new File(
                new File(runtimeFolder, ContainerFilesystemLayout.FOLDER_SMARTSPACES_RUN), FOLDER_PLUGINS_CACHE);
        frameworkConfig.put(Constants.FRAMEWORK_STORAGE, pluginsCacheFolder.getCanonicalPath());

        framework = getFrameworkFactory().newFramework(frameworkConfig);
        frameworkStartLevel = framework.adapt(FrameworkStartLevel.class);

        framework.init();
        rootBundleContext = framework.getBundleContext();

        if (CONFIG_PROPERTY_VALUE_STARTUP_LOGGING.equals(frameworkConfig.get(CONFIG_PROPERTY_STARTUP_LOGGING))) {
            rootBundleContext.addBundleListener(new SynchronousBundleListener() {
                @Override
                public void bundleChanged(BundleEvent event) {
                    try {
                        if (event.getType() == BundleEvent.STARTED) {
                            Bundle bundle = event.getBundle();
                            loggingProvider.getLog()
                                    .info(String.format("Bundle %s:%s started with start level %d",
                                            bundle.getSymbolicName(), bundle.getVersion(),
                                            bundle.adapt(BundleStartLevel.class).getStartLevel()));
                        }
                    } catch (Exception e) {
                        loggingProvider.getLog().error("Exception while responding to bundle change events", e);
                    }
                }
            });
        }
    }

    /**
     * Configure the extra system packages for the framework.
     *
     * @param frameworkConfig
     *          the framework configuration
     * @param extraPackages
     *          the list of packages to be used as extra system packages
     */
    private void configureExtraSystemPackages(Map<String, String> frameworkConfig, List<String> extraPackages) {
        StringBuilder packages = new StringBuilder();
        String separator = "";
        for (String extraPackage : extraPackages) {
            packages.append(separator).append(extraPackage);
            separator = ", ";
        }

        loggingProvider.getLog().debug(String.format("Extra packages: %s", packages));
        frameworkConfig.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, packages.toString());
    }

    /**
     * Configure the extra system packages for the framework.
     *
     * @param frameworkConfig
     *          the framework configuration
     * @param bootPackages
     *          the list of packages to be used as bootloader packages
     */
    private void configureBootDelegationPackages(Map<String, String> frameworkConfig, List<String> bootPackages) {
        if (bootPackages.isEmpty()) {
            return;
        }

        StringBuilder packages = new StringBuilder();
        String separator = "";
        for (String bootPackage : bootPackages) {
            packages.append(separator).append(bootPackage);
            if (!bootPackage.endsWith(NESTED_PACKAGE_WILDCARD)) {
                packages.append(NESTED_PACKAGE_WILDCARD);
            }
            separator = ", ";
        }

        loggingProvider.getLog().debug(String.format("Boot delegations: %s", packages));
        frameworkConfig.put(Constants.FRAMEWORK_BOOTDELEGATION, packages.toString());
    }

    /**
     * Load a collection of libraries.
     *
     * @param libraries
     *          the libraries to load
     */
    private void loadLibraries(List<String> libraries) {
        for (String library : libraries) {
            loggingProvider.getLog().debug(String.format("Loading system library %s", library));
            System.loadLibrary(library);
        }
    }

    /**
     * Load a collection of classes.
     *
     * @param classes
     *          the classes to load
     */
    private void loadClasses(List<String> classes) {
        for (String className : classes) {
            loggingProvider.getLog().debug(String.format("Loading class %s", className));
            try {
                Class<?> clazz = SmartSpacesFrameworkBootstrap.class.getClassLoader().loadClass(className);
                Object obj = clazz.newInstance();
                rootBundleContext.registerService(obj.getClass().getName(), obj, null);
            } catch (Exception e) {
                loggingProvider.getLog().error(String.format("Error while creating class %s", className), e);
            }
        }
    }

    /**
     * Add in all container path entries from the extensions files to the initial
     * bundles list as long as the files actually exist.
     *
     * @param containerPath
     *          the elements to be on the container classpath.
     */
    private void addContainerPathBundles(List<String> containerPath) {
        for (String containerBundlePath : containerPath) {
            File bundleFile = new File(containerBundlePath);
            if (bundleFile.isFile()) {
                initialBundles.add(bundleFile);
            } else {
                loggingProvider.getLog()
                        .warn(String.format("Container path file %s is not a file", containerBundlePath));
            }
        }
    }

    /**
     * Set up a shutdown hook which will stop the framework when the VM shuts
     * down.
     */
    private void setupShutdownHandler() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    if (framework != null) {
                        systemControl.shutdown();
                    }
                } catch (Exception ex) {
                    loggingProvider.getLog().error("Error stopping framework", ex);
                }
            }
        });
    }

    /**
     * Get all jars from the bootstrap folder.
     *
     * @param folder
     *          the folder containing the bootstrap bundles
     */
    private void getBootstrapBundleJars(File folder) {
        // Look in the specified bundle directory to create a list
        // of all JAR files to install.
        File[] files = folder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                String filename = name.toLowerCase();
                return filename.endsWith(".jar") || filename.endsWith(".war");
            }
        });

        for (File f : files) {
            initialBundles.add(f.getAbsoluteFile());
        }
    }

    /**
     * Is the bundle a fragment host?
     *
     * @param bundle
     *          the bundle to check
     *
     * @return {@code true} if the bundle is a fragment host
     */
    private boolean isFragment(Bundle bundle) {
        return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null;
    }

    /**
     * Simple method to parse META-INF/services file for framework factory.
     * Currently, it assumes the first non-commented line is the class nodeName of
     * the framework factory implementation.
     *
     * @return the created <tt>FrameworkFactory</tt> instance
     *
     * @throws Exception
     *           if any errors occur.
     **/
    private FrameworkFactory getFrameworkFactory() throws Exception {
        // using the ServiceLoader to get a factory.
        ClassLoader classLoader = SmartSpacesFrameworkBootstrap.class.getClassLoader();
        URL url = classLoader.getResource(OSGI_FRAMEWORK_LAUNCH_FRAMEWORK_FACTORY);
        if (url != null) {
            BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
            try {
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    // Try to load first non-empty, non-commented line.
                    s = s.trim();
                    if (!s.isEmpty() && s.charAt(0) != META_INF_COMMENT_CHARACTER) {
                        return (FrameworkFactory) classLoader.loadClass(s).newInstance();
                    }
                }
            } finally {
                if (br != null) {
                    br.close();
                }
            }
        }

        throw new Exception("Could not find framework factory.");
    }

    /**
     * Get the list of boot delegation packages and extra system packages from the
     * delegations file.
     *
     * <p>
     * The bootloader loads all classes in the java install. This covers things
     * like the javax classes which are not automatically exposed through the OSGi
     * bundle classloaders.
     *
     * @param bootDelegationPackages
     *          packages to be exported in the boot delegation
     * @param extraSystemPackages
     *          packages to be exported in the exta system classes
     */
    private void processDelegationsFile(List<String> bootDelegationPackages, List<String> extraSystemPackages) {
        File delegationFile = new File(baseInstallFolder, LOCATION_DELEGATIONS_CONF);
        if (!delegationFile.exists()) {
            return;
        }
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(delegationFile));

            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    if (line.startsWith(DELEGATIONS_CONF_FILE_COMMENT)) {
                        continue;
                    } else if (line.startsWith(ExtensionsReader.EXTENSION_FILE_KEYWORD_PACKAGE)) {
                        extraSystemPackages.add(
                                line.substring(ExtensionsReader.EXTENSION_FILE_KEYWORD_PACKAGE.length()).trim());
                    } else if (line.startsWith(ExtensionsReader.EXTENSION_FILE_KEYWORD_PACKAGE_BOOT)) {
                        bootDelegationPackages.add(line
                                .substring(ExtensionsReader.EXTENSION_FILE_KEYWORD_PACKAGE_BOOT.length()).trim());
                    } else {
                        // The default is to be a boot delegation package
                        bootDelegationPackages.add(line);
                    }
                }
            }

        } catch (Exception e) {
            loggingProvider.getLog().error(
                    String.format("Error file processing delegations file %s", delegationFile.getAbsolutePath()),
                    e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Don't care. Closing.
                }
            }
        }
    }

    /**
     * Get the Smart Spaces version from the JAR manifest.
     *
     * @return The smart spaces version
     */
    private String getSmartSpacesVersion() {
        // This little lovely line gives us the name of the jar that gave the
        // class
        // we are looking at.
        String classContainer = getClass().getProtectionDomain().getCodeSource().getLocation().toString();

        InputStream in = null;
        try {
            URL manifestUrl = new URL("jar:" + classContainer + "!/META-INF/MANIFEST.MF");
            in = manifestUrl.openStream();
            Manifest manifest = new Manifest(in);
            Attributes attributes = manifest.getMainAttributes();

            return attributes.getValue(MANIFEST_PROPERTY_SMARTSPACES_VERSION);
        } catch (IOException ex) {
            return null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // Don't care
                }
            }
        }
    }
}