org.jahia.osgi.BundleStarter.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.osgi.BundleStarter.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.osgi;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.karaf.main.ConfigProperties;
import org.apache.karaf.main.Main;
import org.apache.karaf.main.util.ArtifactResolver;
import org.apache.karaf.main.util.SimpleMavenResolver;
import org.apache.karaf.util.config.PropertiesLoader;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.codehaus.plexus.util.dag.DAG;
import org.codehaus.plexus.util.dag.TopologicalSorter;
import org.jahia.data.templates.JahiaTemplatesPackage;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.SpringContextSingleton;
import org.jahia.services.modulemanager.BundleInfo;
import org.jahia.services.modulemanager.ModuleManagementException;
import org.jahia.services.modulemanager.ModuleManager;
import org.jahia.services.modulemanager.util.ModuleUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper class, responsible for starting bundles on demand.
 *
 * @author Sergiy Shyrkov
 */
class BundleStarter {

    private static final Logger logger = LoggerFactory.getLogger(BundleStarter.class);

    private static final String MARKER_INITIAL_BUNDLES = "[initial-bundles].dostart";

    private static final String MARKER_MIGRATE_BUNDLES = "[migrate-bundles].dostart";

    private static Collection<Bundle> getSortedModules(Map<Bundle, JahiaTemplatesPackage> modulesByBundle) {
        long startTime = System.currentTimeMillis();
        try {

            // we build a Directed Acyclic Graph of dependencies (only those, which are present in the package)
            DAG dag = new DAG();

            Map<String, Bundle> bundlesByModuleId = new HashMap<>();
            for (Map.Entry<Bundle, JahiaTemplatesPackage> entry : modulesByBundle.entrySet()) {

                JahiaTemplatesPackage pkg = entry.getValue();
                String pkgId = pkg.getId();
                bundlesByModuleId.put(pkgId, entry.getKey());

                dag.addVertex(pkgId);
                for (String depPkg : pkg.getDepends()) {
                    dag.addEdge(pkgId, depPkg);
                }
                if (!pkg.getDepends().contains(JahiaTemplatesPackage.ID_DEFAULT)
                        && !pkg.getDepends().contains(JahiaTemplatesPackage.NAME_DEFAULT)
                        && !ServicesRegistry.getInstance().getJahiaTemplateManagerService()
                                .getModulesWithNoDefaultDependency().contains(pkg.getId())) {
                    dag.addEdge(pkgId, JahiaTemplatesPackage.ID_DEFAULT);
                }
            }

            List<Bundle> sortedBundles = new LinkedList<>();

            // use topological sort (Depth First Search) on the created graph
            @SuppressWarnings("unchecked")
            List<String> vertexes = TopologicalSorter.sort(dag);
            for (String v : vertexes) {
                Bundle b = bundlesByModuleId.get(v);
                if (b != null) {
                    sortedBundles.add(b);
                }
            }

            logger.info("Sorted bundles in {} ms", System.currentTimeMillis() - startTime);
            return sortedBundles;
        } catch (CycleDetectedException e) {
            logger.error("A cyclic dependency detected in the modules to be started", e);
            // will start bundles in non-sorted order; the OSGi framework will handle the startup correctly if all the dependencies are available
            return modulesByBundle.keySet();
        }
    }

    private static void startBundles(Map<Bundle, JahiaTemplatesPackage> toBeStarted, boolean useModuleManagerApi) {
        logger.info("Will start {} bundle(s)", toBeStarted.size());
        Collection<Bundle> sortedBundles = getSortedModules(toBeStarted);

        try {
            for (Bundle bundle : sortedBundles) {
                try {
                    logger.info("Triggering start for bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion());
                    if (useModuleManagerApi) {
                        ModuleUtils.getModuleManager().start(BundleInfo.fromBundle(bundle).getKey(), null);
                    } else {
                        bundle.start();
                    }
                } catch (Exception e) {
                    BundleException be = null;
                    if (e instanceof BundleException) {
                        be = (BundleException) e;
                    } else if (e instanceof ModuleManagementException && e.getCause() instanceof BundleException) {
                        be = (BundleException) e.getCause();
                    }
                    if (be != null && BundleException.RESOLVE_ERROR == be.getType()) {
                        // log warning for the resolution (dependencies) error
                        logger.warn(be.getMessage());
                    } else {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        } finally {
            logger.info("Finished starting {} bundle(s)", toBeStarted.size());
        }
    }

    /**
     * Start the specified module bundles in the order which tries to consider the dependencies between them.
     * 
     * @param moduleBundles the bundles to be started
     * @param useModuleManagerApi should we use {@link ModuleManager} or call OSGi API directly?
     */
    static void startModules(List<Bundle> moduleBundles, boolean useModuleManagerApi) {
        Map<Bundle, JahiaTemplatesPackage> toBeStarted = new HashMap<>();
        for (Bundle bundle : moduleBundles) {
            JahiaTemplatesPackage pkg = BundleUtils.getModule(bundle);
            if (pkg != null) {
                toBeStarted.put(bundle, pkg);
            } else {
                logger.warn("Unable to retrieve module package for bundle {}/{}. Skip starting it.",
                        bundle.getSymbolicName(), bundle.getVersion());
            }
        }

        startBundles(toBeStarted, useModuleManagerApi);
    }

    private BundleContext bundleContext;

    private File deployedBundlesDir;

    private boolean firstStartup;

    BundleStarter() {
        deployedBundlesDir = new File(System.getProperty("org.osgi.framework.storage"));
        firstStartup = !new File(deployedBundlesDir, "bundle0").exists();
    }

    /**
     * Notifies the service that the FileInstall watcher has been started and processed the found modules.
     */
    void afterFileInstallStarted() {
        if (firstStartup) {
            // this is a first framework startup
            firstStartup = false;
            if (!isFileinstallStartsNewBundles()) {
                // as the bundles are not started automatically by Fileinstall we need to start them manually
                startAllModules();
            }
        }
        startMigrateBundlesIfNeeded();
    }

    private BundleContext getBundleContext() {
        if (bundleContext == null) {
            bundleContext = FrameworkService.getBundleContext();
        }

        return bundleContext;
    }

    private List<File> getBundleRepos() {
        // currently we consider only karaf/system repo
        List<File> bundleDirs = new ArrayList<File>();
        File baseSystemRepo = new File(System.getProperty(ConfigProperties.PROP_KARAF_HOME),
                System.getProperty("karaf.default.repository", "system"));
        if (!baseSystemRepo.exists() && baseSystemRepo.isDirectory()) {
            throw new RuntimeException("system repo folder not found: " + baseSystemRepo.getAbsolutePath());
        }
        bundleDirs.add(baseSystemRepo);

        return bundleDirs;
    }

    private boolean isFileinstallStartsNewBundles() {
        return Boolean.valueOf(((Properties) SpringContextSingleton.getBean("felixFileInstallConfig"))
                .getProperty("felix.fileinstall.bundles.new.start", "true"));
    }

    private void startAllModules() {
        List<Bundle> toBeStarted = new LinkedList<>();
        for (Bundle bundle : getBundleContext().getBundles()) {
            if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.UNINSTALLED
                    && !BundleUtils.isFragment(bundle) && BundleUtils.isJahiaModuleBundle(bundle)) {
                toBeStarted.add(bundle);
            }
        }

        startModules(toBeStarted, false);
    }

    void startInitialBundlesIfNeeded() {
        File marker = new File(deployedBundlesDir, MARKER_INITIAL_BUNDLES);
        if (!marker.exists()) {
            return;
        }
        logger.info("Installing and starting initial bundles");

        // there is a timing issue somewhere in the Karaf code, sleep for 5 seconds
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }

        BundleContext ctx = getBundleContext();
        File startupPropsFile = new File(System.getProperty(ConfigProperties.PROP_KARAF_ETC),
                Main.STARTUP_PROPERTIES_FILE_NAME);
        org.apache.felix.utils.properties.Properties startupProps = PropertiesLoader
                .loadPropertiesOrFail(startupPropsFile);

        List<File> bundleDirs = getBundleRepos();
        ArtifactResolver resolver = new SimpleMavenResolver(bundleDirs);

        List<Bundle> bundlesToStart = new LinkedList<>();
        for (String key : startupProps.keySet()) {
            Integer startLevel = new Integer(startupProps.getProperty(key).trim());
            try {
                URI resolvedURI = resolver.resolve(new URI(key));
                Bundle b = ctx.installBundle(key, resolvedURI.toURL().openStream());
                b.adapt(BundleStartLevel.class).setStartLevel(startLevel);
                if (!BundleUtils.isFragment(b)) {
                    bundlesToStart.add(b);
                }
            } catch (Exception e) {
                throw new RuntimeException("Error installing bundle listed in " + startupPropsFile + " with url: "
                        + key + " and startlevel: " + startLevel, e);
            }
        }
        for (Bundle b : bundlesToStart) {
            try {
                b.start();
            } catch (Exception e) {
                throw new RuntimeException("Error starting bundle " + b.getSymbolicName() + "/" + b.getVersion(),
                        e);
            }
        }
        logger.info("All initial bundles installed and set to start");
        FileUtils.deleteQuietly(marker);
    }

    private void startMigrateBundlesIfNeeded() {
        // as the bundles are not started automatically after a migration whe should do it manually
        File marker = new File(deployedBundlesDir, MARKER_MIGRATE_BUNDLES);
        if (!marker.exists()) {
            return;
        }

        logger.info("Starting migrated bundles");

        try {
            List<Bundle> toBeStarted = new LinkedList<>();
            List<String> lines = FileUtils.readLines(marker);
            for (String line : lines) {
                String[] lineParts = line.split(",");
                String bundleSymbolicName = lineParts[0].trim();
                String bundleVersion = lineParts[1].trim();
                logger.info("Found entry for bundle {}/{}", bundleSymbolicName, bundleVersion);
                Bundle bundle = BundleUtils.getBundleBySymbolicName(bundleSymbolicName, bundleVersion);
                if (bundle != null) {
                    if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.UNINSTALLED
                            && !BundleUtils.isFragment(bundle)
                            && !bundle.adapt(BundleStartLevel.class).isPersistentlyStarted()) {
                        toBeStarted.add(bundle);
                    } else {
                        logger.info("No need to start bundle {}/{}. Skipping it.", bundleSymbolicName,
                                bundleVersion);
                    }
                } else {
                    logger.warn("Cannot find bundle {}/{}. Skip starting it.", bundleSymbolicName, bundleVersion);
                }
            }
            if (!toBeStarted.isEmpty()) {
                startModules(toBeStarted, false);
            }
            logger.info("Finished starting migrated bundles");
            FileUtils.deleteQuietly(marker);
        } catch (IOException e) {
            logger.error("Error reading [migrate-bundles].dostart Cause: " + e.getMessage(), e);
        }
    }

}