org.orbisgis.core.plugin.BundleTools.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.core.plugin.BundleTools.java

Source

/*
 * OrbisGIS is a GIS application dedicated to scientific spatial simulation.
 * This cross-platform GIS is developed at French IRSTV institute and is able to
 * manipulate and create vector and raster spatial information. 
 * 
 * OrbisGIS is distributed under GPL 3 license. It is produced by the "Atelier SIG"
 * team of the IRSTV Institute <http://www.irstv.fr/> CNRS FR 2488.
 * 
 * Copyright (C) 2007-2012 IRSTV (FR CNRS 2488)
 * 
 * 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.core.plugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
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.FilenameUtils;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
import org.apache.log4j.Logger;
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 Logger LOGGER = Logger.getLogger(BundleTools.class);
    private final static I18n I18N = I18nFactory.getI18n(BundleTools.class);
    private final static String MANIFEST_FILENAME = "MANIFEST.MF";
    private final static String PACKAGE_NAMESPACE = "osgi.wiring.package";
    private final static String BUNDLE_DIRECTORY = "bundle";

    private BundleTools() {
    }

    /**
     * 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;
    }
    /**
     * 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;
    }

    /**
     * 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 static 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>();

            // Keep a reference to bundles in the framework cache
            for (Bundle bundle : hostBundle.getBundles()) {
                String key = bundle.getSymbolicName() + "_" + bundle.getVersion();
                installedBundleMap.put(key, bundle);
            }

            //
            final List<Bundle> installedBundleList = new LinkedList<Bundle>();
            for (File jarFile : jarList) {
                // Extract version and symbolic name of the bundle
                String key = "";
                try {
                    List<PackageDeclaration> packageDeclarations = new ArrayList<PackageDeclaration>();
                    BundleReference jarRef = parseJarManifest(jarFile, packageDeclarations);
                    key = jarRef.getArtifactId() + "_" + jarRef.getVersion();
                } catch (IOException ex) {
                    LOGGER.error(ex.getLocalizedMessage(), ex);
                }
                // Retrieve from the framework cache the bundle at this location
                Bundle b = 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.error(I18N.tr("Could not read bundle manifest"), ex);
                }

                try {
                    if (b != null) {
                        String installedBundleLocation = b.getLocation();
                        if (!installedBundleLocation.equals(jarFile.toURI().toString())) {
                            //if the location is not the same reinstall it
                            b.uninstall();
                            b = null;
                        }
                    }
                    // If the bundle is not in the framework cache install it
                    if ((b == null) && reference.isAutoInstall()) {
                        b = hostBundle.installBundle(jarFile.toURI().toString());
                        if (!isFragment(b) && reference.isAutoStart()) {
                            installedBundleList.add(b);
                        }
                    } else if ((b != null)) {
                        b.update();
                    }
                } catch (BundleException ex) {
                    LOGGER.error("Error while installing bundle in bundle directory", ex);
                }
            }
            // Start new bundles
            for (Bundle bundle : installedBundleList) {
                try {
                    bundle.start();
                } catch (BundleException ex) {
                    LOGGER.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 static List<String> getAvailablePackages() {
        List<String> packages = new ArrayList<String>(getAllPackages());
        Collections.sort(packages);
        return packages;
    }

    private static 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.warn("Unable to retrieve Jar MANIFEST " + url, ex);
                }
            }
        } catch (IOException ex) {
            LOGGER.warn("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 static 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.debug("Unable to fetch packages in " + filePath.getAbsolutePath(), ex);
                }
            } else if (filePath.isDirectory()) {
                try {
                    parseDirectoryManifest(filePath, filePath, packages);
                } catch (SecurityException ex) {
                    LOGGER.debug("Unable to fetch the folder " + filePath.getAbsolutePath(), ex);
                }
            }
        }

        return packages;
    }

    /**
     * Parse a Manifest in order to extract Exported Package
     * @param manifest
     * @param packages
     * @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);
        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 static 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.warn("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.warn("Unable to fetch packages in " + file.getAbsolutePath(), ex);
                    }
                }
            } else {
                parseDirectoryManifest(rootPath, file, packages);
            }
        }
    }

    private static BundleReference parseJarManifest(File jarFilePath, List<PackageDeclaration> packages)
            throws IOException {
        JarFile jar = new JarFile(jarFilePath);
        return parseManifest(jar.getManifest(), packages);
    }

    private static 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.debug("Unable to fetch packages in " + filePath.getAbsolutePath());
                }
            } else if (filePath.isDirectory()) {
                try {
                    parseDirectory(filePath, filePath, packages);
                } catch (SecurityException ex) {
                    LOGGER.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) {
            // 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().endsWith(".class")
                        && file.getParent().length() > rootPath.getAbsolutePath().length()) {
                    String parentPath = file.getParent().substring(rootPath.getAbsolutePath().length() + 1);
                    packages.add(parentPath.replace(File.separator, "."));
                }
            } else {
                parseDirectory(rootPath, file, packages);
            }
        }
    }

    private static void parseJar(File jarFilePath, Set<String> packages) throws IOException {
        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, "."));
                    }
                }
            }
        }
    }
}