org.apache.felix.bundleplugin.BundleAllPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.felix.bundleplugin.BundleAllPlugin.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.felix.bundleplugin;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.codehaus.plexus.util.FileUtils;

import aQute.lib.osgi.Analyzer;
import aQute.lib.osgi.Jar;

/**
 * Create OSGi bundles from all dependencies in the Maven project
 * 
 * @goal bundleall
 * @phase package
 * @requiresDependencyResolution test
 * @description build an OSGi bundle jar for all transitive dependencies
 */
public class BundleAllPlugin extends ManifestPlugin {
    private static final String LS = System.getProperty("line.separator");

    private static final Pattern SNAPSHOT_VERSION_PATTERN = Pattern.compile("[0-9]{8}_[0-9]{6}_[0-9]+");

    /**
     * Local repository.
     *
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;

    /**
     * Remote repositories.
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List remoteRepositories;

    /**
     * Import-Package to be used when wrapping dependencies.
     *
     * @parameter expression="${wrapImportPackage}" default-value="*"
     */
    private String wrapImportPackage;

    /**
     * @component
     */
    private ArtifactFactory m_factory;

    /**
     * @component
     */
    private ArtifactMetadataSource m_artifactMetadataSource;

    /**
     * @component
     */
    private ArtifactCollector m_collector;

    /**
     * Artifact resolver, needed to download jars.
     * 
     * @component
     */
    private ArtifactResolver m_artifactResolver;

    /**
     * @component
     */
    private DependencyTreeBuilder m_dependencyTreeBuilder;

    /**
     * @component
     */
    private MavenProjectBuilder m_mavenProjectBuilder;

    /**
     * Ignore missing artifacts that are not required by current project but are required by the
     * transitive dependencies.
     * 
     * @parameter
     */
    private boolean ignoreMissingArtifacts;

    private Set m_artifactsBeingProcessed = new HashSet();

    /**
     * Process up to some depth 
     * 
     * @parameter
     */
    private int depth = Integer.MAX_VALUE;

    public void execute() throws MojoExecutionException {
        BundleInfo bundleInfo = bundleAll(getProject());
        logDuplicatedPackages(bundleInfo);
    }

    /**
     * Bundle a project and all its dependencies
     * 
     * @param project
     * @throws MojoExecutionException
     */
    private BundleInfo bundleAll(MavenProject project) throws MojoExecutionException {
        return bundleAll(project, depth);
    }

    /**
     * Bundle a project and its transitive dependencies up to some depth level
     * 
     * @param project
     * @param maxDepth how deep to process the dependency tree
     * @throws MojoExecutionException
     */
    protected BundleInfo bundleAll(MavenProject project, int maxDepth) throws MojoExecutionException {

        if (alreadyBundled(project.getArtifact())) {
            getLog().debug("Ignoring project already processed " + project.getArtifact());
            return null;
        }

        if (m_artifactsBeingProcessed.contains(project.getArtifact())) {
            getLog().warn("Ignoring artifact due to dependency cycle " + project.getArtifact());
            return null;
        }
        m_artifactsBeingProcessed.add(project.getArtifact());

        DependencyNode dependencyTree;

        try {
            dependencyTree = m_dependencyTreeBuilder.buildDependencyTree(project, localRepository, m_factory,
                    m_artifactMetadataSource, null, m_collector);
        } catch (DependencyTreeBuilderException e) {
            throw new MojoExecutionException("Unable to build dependency tree", e);
        }

        BundleInfo bundleInfo = new BundleInfo();

        if (!dependencyTree.hasChildren()) {
            /* no need to traverse the tree */
            return bundleRoot(project, bundleInfo);
        }

        getLog().debug("Will bundle the following dependency tree" + LS + dependencyTree);

        for (Iterator it = dependencyTree.inverseIterator(); it.hasNext();) {
            DependencyNode node = (DependencyNode) it.next();
            if (!it.hasNext()) {
                /* this is the root, current project */
                break;
            }

            if (node.getState() != DependencyNode.INCLUDED) {
                continue;
            }

            if (Artifact.SCOPE_SYSTEM.equals(node.getArtifact().getScope())) {
                getLog().debug("Ignoring system scoped artifact " + node.getArtifact());
                continue;
            }

            Artifact artifact;
            try {
                artifact = resolveArtifact(node.getArtifact());
            } catch (ArtifactNotFoundException e) {
                if (ignoreMissingArtifacts) {
                    continue;
                }

                throw new MojoExecutionException("Artifact was not found in the repo" + node.getArtifact(), e);
            }

            node.getArtifact().setFile(artifact.getFile());

            int nodeDepth = node.getDepth();
            if (nodeDepth > maxDepth) {
                /* node is deeper than we want */
                getLog().debug(
                        "Ignoring " + node.getArtifact() + ", depth is " + nodeDepth + ", bigger than " + maxDepth);
                continue;
            }

            MavenProject childProject;
            try {
                childProject = m_mavenProjectBuilder.buildFromRepository(artifact, remoteRepositories,
                        localRepository, true);
                if (childProject.getDependencyArtifacts() == null) {
                    childProject.setDependencyArtifacts(childProject.createArtifacts(m_factory, null, null));
                }
            } catch (ProjectBuildingException e) {
                throw new MojoExecutionException("Unable to build project object for artifact " + artifact, e);
            } catch (InvalidDependencyVersionException e) {
                throw new MojoExecutionException("Invalid dependency version for artifact " + artifact);
            }

            childProject.setArtifact(artifact);
            getLog().debug("Child project artifact location: " + childProject.getArtifact().getFile());

            if ((Artifact.SCOPE_COMPILE.equals(artifact.getScope()))
                    || (Artifact.SCOPE_RUNTIME.equals(artifact.getScope()))) {
                BundleInfo subBundleInfo = bundleAll(childProject, maxDepth - 1);
                if (subBundleInfo != null) {
                    bundleInfo.merge(subBundleInfo);
                }
            } else {
                getLog().debug("Not processing due to scope (" + childProject.getArtifact().getScope() + "): "
                        + childProject.getArtifact());
            }
        }

        return bundleRoot(project, bundleInfo);
    }

    /**
     * Bundle the root of a dependency tree after all its children have been bundled
     * 
     * @param project
     * @param bundleInfo
     * @return
     * @throws MojoExecutionException
     */
    private BundleInfo bundleRoot(MavenProject project, BundleInfo bundleInfo) throws MojoExecutionException {
        /* do not bundle the project the mojo was called on */
        if (getProject() != project) {
            getLog().debug("Project artifact location: " + project.getArtifact().getFile());

            BundleInfo subBundleInfo = bundle(project);
            if (subBundleInfo != null) {
                bundleInfo.merge(subBundleInfo);
            }
        }
        return bundleInfo;
    }

    /**
     * Bundle one project only without building its childre
     * 
     * @param project
     * @throws MojoExecutionException
     */
    protected BundleInfo bundle(MavenProject project) throws MojoExecutionException {
        Artifact artifact = project.getArtifact();
        getLog().info("Bundling " + artifact);

        try {
            Map instructions = new LinkedHashMap();
            instructions.put(Analyzer.IMPORT_PACKAGE, wrapImportPackage);

            project.getArtifact().setFile(getFile(artifact));
            File outputFile = getOutputFile(artifact);

            if (project.getArtifact().getFile().equals(outputFile)) {
                /* TODO find the cause why it's getting here */
                return null;
                //                getLog().error(
                //                                "Trying to read and write " + artifact + " to the same file, try cleaning: "
                //                                    + outputFile );
                //                throw new IllegalStateException( "Trying to read and write " + artifact
                //                    + " to the same file, try cleaning: " + outputFile );
            }

            Analyzer analyzer = getAnalyzer(project, instructions, new Properties(), getClasspath(project));

            Jar osgiJar = new Jar(project.getArtifactId(), project.getArtifact().getFile());

            outputFile.getAbsoluteFile().getParentFile().mkdirs();

            Collection exportedPackages;
            if (isOsgi(osgiJar)) {
                /* if it is already an OSGi jar copy it as is */
                getLog().info("Using existing OSGi bundle for " + project.getGroupId() + ":"
                        + project.getArtifactId() + ":" + project.getVersion());
                String exportHeader = osgiJar.getManifest().getMainAttributes().getValue(Analyzer.EXPORT_PACKAGE);
                exportedPackages = analyzer.parseHeader(exportHeader).keySet();
                FileUtils.copyFile(project.getArtifact().getFile(), outputFile);
            } else {
                /* else generate the manifest from the packages */
                exportedPackages = analyzer.getExports().keySet();
                Manifest manifest = analyzer.getJar().getManifest();
                osgiJar.setManifest(manifest);
                osgiJar.write(outputFile);
            }

            BundleInfo bundleInfo = addExportedPackages(project, exportedPackages);

            // cleanup...
            analyzer.close();
            osgiJar.close();

            return bundleInfo;
        }
        /* too bad Jar.write throws Exception */
        catch (Exception e) {
            throw new MojoExecutionException(
                    "Error generating OSGi bundle for project " + getArtifactKey(project.getArtifact()), e);
        }
    }

    private boolean isOsgi(Jar jar) throws IOException {
        if (jar.getManifest() != null) {
            return jar.getManifest().getMainAttributes().getValue(Analyzer.BUNDLE_NAME) != null;
        }
        return false;
    }

    private BundleInfo addExportedPackages(MavenProject project, Collection packages) {
        BundleInfo bundleInfo = new BundleInfo();
        for (Iterator it = packages.iterator(); it.hasNext();) {
            String packageName = (String) it.next();
            bundleInfo.addExportedPackage(packageName, project.getArtifact());
        }
        return bundleInfo;
    }

    private String getArtifactKey(Artifact artifact) {
        return artifact.getGroupId() + ":" + artifact.getArtifactId();
    }

    private String getBundleName(Artifact artifact) {
        return getMaven2OsgiConverter().getBundleFileName(artifact);
    }

    private boolean alreadyBundled(Artifact artifact) {
        return getBuiltFile(artifact) != null;
    }

    /**
     * Use previously built bundles when available.
     * 
     * @param artifact
     */
    protected File getFile(final Artifact artifact) {
        File bundle = getBuiltFile(artifact);

        if (bundle != null) {
            getLog().debug("Using previously built OSGi bundle for " + artifact + " in " + bundle);
            return bundle;
        }
        return super.getFile(artifact);
    }

    private File getBuiltFile(final Artifact artifact) {
        File bundle = null;

        /* if bundle was already built use it instead of jar from repo */
        File outputFile = getOutputFile(artifact);
        if (outputFile.exists()) {
            bundle = outputFile;
        }

        /*
         * Find snapshots in output folder, eg. 2.1-SNAPSHOT will match 2.1.0.20070207_193904_2
         * TODO there has to be another way to do this using Maven libs 
         */
        if ((bundle == null) && artifact.isSnapshot()) {
            final File buildDirectory = new File(getBuildDirectory());
            if (!buildDirectory.exists()) {
                buildDirectory.mkdirs();
            }
            File[] files = buildDirectory.listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    if (dir.equals(buildDirectory) && snapshotMatch(artifact, name)) {
                        return true;
                    }
                    return false;
                }
            });
            if (files.length > 1) {
                throw new RuntimeException("More than one previously built bundle matches for artifact " + artifact
                        + " : " + Arrays.asList(files));
            }
            if (files.length == 1) {
                bundle = files[0];
            }
        }

        return bundle;
    }

    /**
     * Check that the bundleName provided correspond to the artifact provided.
     * Used to determine when the bundle name is a timestamped snapshot and the artifact is a snapshot not timestamped.
     * 
     * @param artifact artifact with snapshot version
     * @param bundleName bundle file name 
     * @return if both represent the same artifact and version, forgetting about the snapshot timestamp
     */
    protected boolean snapshotMatch(Artifact artifact, String bundleName) {
        String artifactBundleName = getBundleName(artifact);
        int i = artifactBundleName.indexOf("SNAPSHOT");
        if (i < 0) {
            return false;
        }
        artifactBundleName = artifactBundleName.substring(0, i);

        if (bundleName.startsWith(artifactBundleName)) {
            /* it's the same artifact groupId and artifactId */
            String timestamp = bundleName.substring(artifactBundleName.length(), bundleName.lastIndexOf(".jar"));
            Matcher m = SNAPSHOT_VERSION_PATTERN.matcher(timestamp);
            return m.matches();
        }
        return false;
    }

    protected File getOutputFile(Artifact artifact) {
        return new File(getOutputDirectory(), getBundleName(artifact));
    }

    private Artifact resolveArtifact(Artifact artifact) throws MojoExecutionException, ArtifactNotFoundException {
        VersionRange versionRange;
        if (artifact.getVersion() != null) {
            versionRange = VersionRange.createFromVersion(artifact.getVersion());
        } else {
            versionRange = artifact.getVersionRange();
        }

        /*
         * there's a bug with ArtifactFactory#createDependencyArtifact(String, String, VersionRange,
         * String, String, String) that ignores the scope parameter, that's why we use the one with
         * the extra null parameter
         */
        Artifact resolvedArtifact = m_factory.createDependencyArtifact(artifact.getGroupId(),
                artifact.getArtifactId(), versionRange, artifact.getType(), artifact.getClassifier(),
                artifact.getScope(), null);

        try {
            m_artifactResolver.resolve(resolvedArtifact, remoteRepositories, localRepository);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Error resolving artifact " + resolvedArtifact, e);
        }

        return resolvedArtifact;
    }

    /**
     * Log what packages are exported in more than one bundle
     */
    protected void logDuplicatedPackages(BundleInfo bundleInfo) {
        Map duplicatedExports = bundleInfo.getDuplicatedExports();

        for (Iterator it = duplicatedExports.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            String packageName = (String) entry.getKey();
            Collection artifacts = (Collection) entry.getValue();

            getLog().warn("Package " + packageName + " is exported in more than a bundle: ");
            for (Iterator it2 = artifacts.iterator(); it2.hasNext();) {
                Artifact artifact = (Artifact) it2.next();
                getLog().warn("  " + artifact);
            }

        }
    }
}