org.orbisgis.framework.BundleTools.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.framework.BundleTools.java

Source

/**
 * OrbisGIS is a java GIS application dedicated to research in GIScience.
 * OrbisGIS is developed by the GIS group of the DECIDE team of the 
 * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
 *
 * The GIS group of the DECIDE team is located at :
 *
 * Laboratoire Lab-STICC  CNRS UMR 6285
 * Equipe DECIDE
 * UNIVERSIT DE BRETAGNE-SUD
 * Institut Universitaire de Technologie de Vannes
 * 8, Rue Montaigne - BP 561 56017 Vannes Cedex
 * 
 * OrbisGIS is distributed under GPL 3 license.
 *
 * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
 * Copyright (C) 2015-2016 CNRS (Lab-STICC UMR CNRS 6285)
 *
 * This file is part of OrbisGIS.
 *
 * OrbisGIS 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.
 *
 * OrbisGIS 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
 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, please consult: <http://www.orbisgis.org/>
 * or contact directly:
 * info_at_ orbisgis.org
 */
package org.orbisgis.framework;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.framework.wiring.BundleCapability;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

/**
 * Functions used by Bundle Host. As long as OrbisGIS is not entirely converted into OSGi bundles,
 * the host need to export packages using this class.
 * @author Nicolas Fortin
 */
public class BundleTools {
    private final static I18n I18N = I18nFactory.getI18n(BundleTools.class);
    public final static String MANIFEST_FILENAME = "MANIFEST.MF";
    private final static String PACKAGE_NAMESPACE = "osgi.wiring.package";
    public final static String BUNDLE_DIRECTORY = "bundle";
    private final Logger LOGGER;
    private static final int DELETE_TRY_COUNT = 3;
    private static final long WAIT_RETRY_DELETE = 250;

    public BundleTools(Logger LOGGER) {
        this.LOGGER = LOGGER;
    }

    /**
     * Find if the bundle already exists and retrieve it.
     * @param hostBundle
     * @param symbolicName
     * @param version
     * @return
     */
    private static Bundle getBundle(BundleContext hostBundle, String symbolicName, Version version) {
        Bundle[] bundles = hostBundle.getBundles();
        for (int i = 0; (bundles != null) && (i < bundles.length); i++) {
            String sym = bundles[i].getSymbolicName();
            Version ver = bundles[i].getVersion();
            if ((symbolicName != null) && (sym != null) && symbolicName.equals(sym) && version.equals(ver)) {
                return bundles[i];
            }
        }
        return null;
    }

    /**
     * String version of bundle state index
     * @param i Bundle state like {@link Bundle#ACTIVE}
     * @return Bundle state string version
     */
    public static String getStateString(int i) {
        switch (i) {
        case Bundle.ACTIVE:
            return "Active   ";
        case Bundle.INSTALLED:
            return "Installed";
        case Bundle.RESOLVED:
            return "Resolved ";
        case Bundle.STARTING:
            return "Starting ";
        case Bundle.STOPPING:
            return "Stopping ";
        default:
            return "Unknown  ";
        }
    }

    /**
     * Register in the host bundle the provided list of bundle reference
     * @param hostBundle Host BundleContext
     * @param bundleToInstall Bundle Reference array
     */
    /*
    public static void installBundles(BundleContext hostBundle,BundleReference[] bundleToInstall) {
        for(BundleReference bundleRef : bundleToInstall) {
                if(bundleRef.getBundleJarContent()==null) {
                        LOGGER.warn(I18N.tr("OrbisGIS package does not contain the {0} bundle plugin",bundleRef.getArtifactId()));
                } else {
                    try {
                            Bundle builtInBundle = hostBundle.installBundle(bundleRef.getBundleUri(),
                            bundleRef.getBundleJarContent());
                            //Do not start if bundle is a fragment bundle (no Activator in fragment)
                            if(bundleRef.isAutoStart() && ((
                                    builtInBundle.adapt(BundleRevision.class).getTypes() &
                                    BundleRevision.TYPE_FRAGMENT) == 0)) {
                                    LOGGER.debug("Starting "+bundleRef.getArtifactId()+"..");
                                    builtInBundle.start();
                                    LOGGER.debug("Started");
                            }
                    } catch(BundleException ex) {
                            LOGGER.error(ex.getLocalizedMessage(), ex);
                    }
                }
        }
    }
    */

    /**
     * @param bundle The bundle instance
     * @return True if the bundle has no Activator and cannot be started
     */
    private static boolean isFragment(Bundle bundle) {
        return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null;
    }

    /**
     * @param bundle The bundle instance
     * @return True if the bundle has no Activator and cannot be started
     */
    private static String getFragmentHost(Bundle bundle) {
        return bundle.getHeaders().get(Constants.FRAGMENT_HOST);
    }

    /**
     * Delete OSGi fragment bundles that are both in OSGi cache and in bundle sub-dir
     * @param bundleCache OSGi bundle cache  ex: ~/.Orbisgis/4.X/cache/
     */
    public void deleteFragmentInCache(File bundleCache) {
        if (bundleCache.exists()) {
            // List bundles in the /bundle subdirectory
            File bundleFolder = new File(BUNDLE_DIRECTORY);
            if (!bundleFolder.exists()) {
                return;
            }
            File[] files = bundleFolder.listFiles();
            if (files != null) {
                List<String> fragmentBundlesArtifacts = new ArrayList<>(files.length);
                // Search for Fragment in /bundle/ subdir
                for (File file : files) {
                    if (FilenameUtils.isExtension(file.getName(), "jar")) {
                        // Read Manifest
                        try (JarFile jar = new JarFile(file)) {
                            Manifest manifest = jar.getManifest();
                            if (manifest != null && manifest.getMainAttributes() != null) {
                                String artifact = manifest.getMainAttributes().getValue(Constants.FRAGMENT_HOST);
                                if (artifact != null) {
                                    fragmentBundlesArtifacts.add(parseManifest(manifest, null).getArtifactId());
                                }
                            }
                        } catch (IOException ex) {
                            LOGGER.log(Logger.LOG_ERROR, "Error while reading Jar manifest:\n" + file.getPath());
                        }
                    }
                }
                // Remove folders in bundle cache that contain a fragment cache
                File[] cacheFolders = bundleCache.listFiles((FileFilter) DirectoryFileFilter.DIRECTORY);
                if (cacheFolders != null) {
                    for (File folder : cacheFolders) {
                        try {
                            // Get the first folder, may contain only one ex:"version0.0"
                            File[] cacheBundleFolder = folder.listFiles((FileFilter) DirectoryFileFilter.DIRECTORY);
                            if (cacheBundleFolder != null && cacheBundleFolder.length > 0) {
                                // Read Jar manifest
                                File jarBundle = new File(cacheBundleFolder[0], "bundle.jar");
                                if (jarBundle.exists()) {
                                    // Read artifact
                                    String artifact = parseJarManifest(jarBundle, null).getArtifactId();
                                    if (fragmentBundlesArtifacts.contains(artifact)) {
                                        // Delete the cache folder
                                        int tryCount = 0;
                                        while (tryCount < DELETE_TRY_COUNT) {
                                            try {
                                                if (jarBundle.delete()) {
                                                    FileUtils.deleteDirectory(folder);
                                                } else {
                                                    throw new IOException("Could not delete jar file");
                                                }
                                                break;
                                            } catch (IOException ex) {
                                                tryCount++;
                                                try {
                                                    Thread.sleep(WAIT_RETRY_DELETE);
                                                } catch (InterruptedException iex) {
                                                    break;
                                                }
                                            }
                                        }
                                        if (folder.exists()) {
                                            LOGGER.log(Logger.LOG_ERROR,
                                                    "Cannot delete a bundle cache folder, library may not be up to"
                                                            + " date, please delete the following folder and restart OrbisGIS:"
                                                            + "\n" + folder.getPath());
                                        } else {
                                            LOGGER.log(Logger.LOG_INFO,
                                                    I18N.tr("Delete fragment bundle {0} in " + "cache directory",
                                                            artifact));
                                        }
                                    }
                                }
                            }
                        } catch (IOException ex) {
                            LOGGER.log(Logger.LOG_ERROR, "Error while reading Jar manifest:\n" + folder.getPath(),
                                    ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Register in the host bundle the provided list of bundle reference
     * @param hostBundle Host BundleContext
     * @param nonDefaultBundleDeploying Bundle Reference array to deploy bundles in a non default way (install&start)
     */
    public void installBundles(BundleContext hostBundle, BundleReference[] nonDefaultBundleDeploying) {
        //Create a Map of nonDefaultBundleDeploying by their artifactId
        Map<String, BundleReference> customDeployBundles = new HashMap<String, BundleReference>(
                nonDefaultBundleDeploying.length);
        for (BundleReference ref : nonDefaultBundleDeploying) {
            customDeployBundles.put(ref.getArtifactId(), ref);
        }

        // List bundles in the /bundle subdirectory
        File bundleFolder = new File(BUNDLE_DIRECTORY);
        if (!bundleFolder.exists()) {
            return;
        }
        File[] files = bundleFolder.listFiles();
        List<File> jarList = new ArrayList<File>();
        if (files != null) {
            for (File file : files) {
                if (FilenameUtils.isExtension(file.getName(), "jar")) {
                    jarList.add(file);
                }
            }
        }
        if (!jarList.isEmpty()) {
            Map<String, Bundle> installedBundleMap = new HashMap<String, Bundle>();
            Set<String> fragmentHosts = new HashSet<>();

            // Keep a reference to bundles in the framework cache
            for (Bundle bundle : hostBundle.getBundles()) {
                String key = bundle.getSymbolicName();
                installedBundleMap.put(key, bundle);
                String fragmentHost = getFragmentHost(bundle);
                if (fragmentHost != null) {
                    fragmentHosts.add(fragmentHost);
                }
            }

            //
            final List<Bundle> installedBundleList = new LinkedList<Bundle>();
            for (File jarFile : jarList) {
                // Extract version and symbolic name of the bundle
                String key = "";
                BundleReference jarRef;
                try {
                    List<PackageDeclaration> packageDeclarations = new ArrayList<PackageDeclaration>();
                    jarRef = parseJarManifest(jarFile, packageDeclarations);
                    key = jarRef.getArtifactId();
                } catch (IOException ex) {
                    LOGGER.log(Logger.LOG_ERROR, ex.getLocalizedMessage(), ex);
                    // Do not install this jar
                    continue;
                }
                // Retrieve from the framework cache the bundle at this location
                Bundle installedBundle = installedBundleMap.remove(key);

                // Read Jar manifest without installing it
                BundleReference reference = new BundleReference(""); // Default deploy
                try (JarFile jar = new JarFile(jarFile)) {
                    Manifest manifest = jar.getManifest();
                    if (manifest != null && manifest.getMainAttributes() != null) {
                        String artifact = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
                        BundleReference customRef = customDeployBundles.get(artifact);
                        if (customRef != null) {
                            reference = customRef;
                        }
                    }

                } catch (Exception ex) {
                    LOGGER.log(Logger.LOG_ERROR, I18N.tr("Could not read bundle manifest"), ex);
                }

                try {
                    if (installedBundle != null) {
                        if (getFragmentHost(installedBundle) != null) {
                            // Fragment cannot be reinstalled
                            continue;
                        } else {
                            String installedBundleLocation = installedBundle.getLocation();
                            int verDiff = -1;
                            if (installedBundle.getVersion() != null && jarRef.getVersion() != null) {
                                verDiff = installedBundle.getVersion().compareTo(jarRef.getVersion());
                            }
                            if (verDiff == 0) {
                                // If the same version or SNAPSHOT that is not used by fragments
                                if (!fragmentHosts.contains(installedBundle.getSymbolicName())
                                        && (!installedBundleLocation.equals(jarFile.toURI().toString())
                                                || (installedBundle.getVersion() != null && "SNAPSHOT"
                                                        .equals(installedBundle.getVersion().getQualifier())))) {
                                    //if the location is not the same reinstall it
                                    LOGGER.log(Logger.LOG_INFO,
                                            "Uninstall bundle " + installedBundle.getSymbolicName());
                                    installedBundle.uninstall();
                                    installedBundle = null;
                                }
                            } else if (verDiff < 0) {
                                // Installed version is older than the bundle version
                                LOGGER.log(Logger.LOG_INFO, "Uninstall bundle " + installedBundle.getLocation());
                                installedBundle.uninstall();
                                installedBundle = null;
                            } else {
                                // Installed version is more recent than the bundle version
                                // Do not install this jar
                                continue;
                            }
                        }
                    }
                    // If the bundle is not in the framework cache install it
                    if ((installedBundle == null) && reference.isAutoInstall()) {
                        installedBundle = hostBundle.installBundle(jarFile.toURI().toString());
                        LOGGER.log(Logger.LOG_INFO, "Install bundle " + installedBundle.getSymbolicName());
                        if (!isFragment(installedBundle) && reference.isAutoStart()) {
                            installedBundleList.add(installedBundle);
                        }
                    }
                } catch (BundleException ex) {
                    LOGGER.log(Logger.LOG_ERROR, "Error while installing bundle in bundle directory", ex);
                }
            }
            // Start new bundles
            for (Bundle bundle : installedBundleList) {
                try {
                    bundle.start();
                } catch (BundleException ex) {
                    LOGGER.log(Logger.LOG_ERROR, "Error while starting bundle in bundle directory", ex);
                }
            }
        }
    }

    /**
     * Read the class path, open all Jars and folders, retrieve the package list.
     * This kind of package list does not contain versions and are useful for non-OSGi packages only.
     * @return List of package name
     */
    public List<String> getAvailablePackages() {
        List<String> packages = new ArrayList<String>(getAllPackages());
        Collections.sort(packages);
        return packages;
    }

    private List<String> getClassPath() {
        List<String> classPath = new LinkedList<String>();
        // Read declared class in the manifest
        try {
            Enumeration<URL> resources = BundleTools.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                try {
                    Manifest manifest = new Manifest(url.openStream());
                    String value = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
                    if (value != null) {
                        String[] pathElements = value.split(" ");
                        if (pathElements == null) {
                            pathElements = new String[] { value };
                        }
                        classPath.addAll(Arrays.asList(pathElements));
                    }
                } catch (IOException ex) {
                    LOGGER.log(Logger.LOG_WARNING, "Unable to retrieve Jar MANIFEST " + url, ex);
                }
            }
        } catch (IOException ex) {
            LOGGER.log(Logger.LOG_WARNING, "Unable to retrieve Jar MANIFEST", ex);
        }
        // Read packages in the class path
        String javaClasspath = System.getProperty("java.class.path");
        String[] pathElements = javaClasspath.split(":");
        if (pathElements == null) {
            pathElements = new String[] { javaClasspath };
        }
        classPath.addAll(Arrays.asList(pathElements));
        return classPath;
    }

    /**
     * Read the class path, search for OSGi manifest declaration.
     * Reading MANIFEST is useful to read package versions of OSGi compliant Jars.
     * If a package version is not properly exported then an OSGi bundle that depends on this package with a specified
     * version could not be Resolved.
     * @return
     */
    public Collection<PackageDeclaration> fetchManifests() {
        List<PackageDeclaration> packages = new LinkedList<PackageDeclaration>();
        List<String> pathElements = getClassPath();
        // Fetch
        for (String element : pathElements) {
            File filePath = new File(element);
            if (FilenameUtils.getExtension(element).equals("jar") && filePath.exists()) {
                try {
                    parseJarManifest(filePath, packages);
                } catch (IOException ex) {
                    LOGGER.log(Logger.LOG_DEBUG, "Unable to fetch packages in " + filePath.getAbsolutePath(), ex);
                }
            } else if (filePath.isDirectory()) {
                try {
                    parseDirectoryManifest(filePath, filePath, packages);
                } catch (SecurityException ex) {
                    LOGGER.log(Logger.LOG_DEBUG, "Unable to fetch the folder " + filePath.getAbsolutePath(), ex);
                }
            }
        }

        return packages;
    }

    /**
     * Parse a Manifest in order to extract Exported Package
     * @param manifest Jar Manifest
     * @param packages package array or null
     * @throws IOException
     */
    public static BundleReference parseManifest(Manifest manifest, List<PackageDeclaration> packages)
            throws IOException {
        Attributes attributes = manifest.getMainAttributes();
        String exports = attributes.getValue(Constants.EXPORT_PACKAGE);
        String versionProperty = attributes.getValue(Constants.BUNDLE_VERSION_ATTRIBUTE);
        Version version = null;
        if (versionProperty != null) {
            version = new Version(versionProperty);
        }
        String symbolicName = attributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
        if (packages != null) {
            org.apache.felix.framework.Logger logger = new org.apache.felix.framework.Logger();
            // Use Apache Felix to parse the Manifest Export header
            List<BundleCapability> exportsCapability = ManifestParser.parseExportHeader(logger, null, exports, "0",
                    new Version(0, 0, 0));
            for (BundleCapability bc : exportsCapability) {
                Map<String, Object> attr = bc.getAttributes();
                // If the package contain a package name and a package version
                if (attr.containsKey(PACKAGE_NAMESPACE)) {
                    Version packageVersion = new Version(0, 0, 0);
                    if (attr.containsKey(Constants.VERSION_ATTRIBUTE)) {
                        packageVersion = (Version) attr.get(Constants.VERSION_ATTRIBUTE);
                    }
                    if (packageVersion.getMajor() != 0 || packageVersion.getMinor() != 0
                            || packageVersion.getMicro() != 0) {
                        packages.add(new PackageDeclaration((String) attr.get(PACKAGE_NAMESPACE), packageVersion));
                    } else {
                        // No version, take the bundle version
                        packages.add(new PackageDeclaration((String) attr.get(PACKAGE_NAMESPACE), version));
                    }
                }
            }
        }
        return new BundleReference(symbolicName, version);
    }

    private void parseDirectoryManifest(File rootPath, File path, List<PackageDeclaration> packages)
            throws SecurityException {
        File[] files = path.listFiles();
        for (File file : files) {
            // TODO Java7 check for non-symlink,
            // without this check it might generate an infinite loop
            // @link http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#isSymbolicLink(java.nio.file.Path)
            if (!file.isDirectory()) {
                if (file.getName().equals(MANIFEST_FILENAME)) {
                    try {
                        Manifest manifest = new Manifest(new FileInputStream(file));
                        parseManifest(manifest, packages);
                    } catch (Exception ex) {
                        LOGGER.log(Logger.LOG_WARNING, "Unable to read manifest in " + file.getAbsolutePath(), ex);
                    }
                }
                if (FilenameUtils.getExtension(file.getName()).equals("jar") && file.exists()) {
                    try {
                        parseJarManifest(file, packages);
                    } catch (IOException ex) {
                        LOGGER.log(Logger.LOG_WARNING, "Unable to fetch packages in " + file.getAbsolutePath(), ex);
                    }
                }
            } else {
                parseDirectoryManifest(rootPath, file, packages);
            }
        }
    }

    /**
     * Parse a Manifest in order to extract Exported Package
     * @param jarFilePath Jar file
     * @param packages package array or null (out)
     * @throws IOException
     */
    public static BundleReference parseJarManifest(File jarFilePath, List<PackageDeclaration> packages)
            throws IOException {
        try (JarFile jar = new JarFile(jarFilePath)) {
            return parseManifest(jar.getManifest(), packages);
        }
    }

    private Set<String> getAllPackages() {
        Set<String> packages = new HashSet<String>();
        List<String> pathElements = getClassPath();
        for (String element : pathElements) {
            File filePath = new File(element);
            if (element.endsWith("jar")) {
                try {
                    parseJar(filePath, packages);
                } catch (IOException ex) {
                    LOGGER.log(Logger.LOG_DEBUG, "Unable to fetch packages in " + filePath.getAbsolutePath());
                }
            } else if (filePath.isDirectory()) {
                try {
                    parseDirectory(filePath, filePath, packages);
                } catch (SecurityException ex) {
                    LOGGER.log(Logger.LOG_DEBUG, "Unable to fetch the folder " + filePath.getAbsolutePath());
                }
            }
        }
        return packages;
    }

    private static void parseDirectory(File rootPath, File path, Set<String> packages) throws SecurityException {
        File[] files = path.listFiles();
        for (File file : files) {
            if (!file.isDirectory()) {
                if (file.getName().endsWith(".class")
                        && file.getParent().length() > rootPath.getAbsolutePath().length()) {
                    String parentPath = file.getParent().substring(rootPath.getAbsolutePath().length() + 1);
                    packages.add(parentPath.replace(File.separator, "."));
                }
            } else {
                if (!Files.isSymbolicLink(file.toPath())) {
                    parseDirectory(rootPath, file, packages);
                }
            }
        }
    }

    private static void parseJar(File jarFilePath, Set<String> packages) throws IOException {
        try (JarFile jar = new JarFile(jarFilePath)) {
            Enumeration<? extends JarEntry> entryEnum = jar.entries();
            while (entryEnum.hasMoreElements()) {
                JarEntry entry = entryEnum.nextElement();
                if (!entry.isDirectory()) {
                    final String path = entry.getName();
                    if (path.endsWith(".class")) {
                        // Extract folder
                        String parentPath = (new File(path)).getParent();
                        if (parentPath != null) {
                            packages.add(parentPath.replace(File.separator, "."));
                        }
                    }
                }
            }
        }
    }
}