org.ops4j.pax.construct.project.ImportBundleMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.construct.project.ImportBundleMojo.java

Source

package org.ops4j.pax.construct.project;

/*
 * Copyright 2007 Stuart McCulloch
 *
 * 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.
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

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.ArtifactResolver;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.plugin.AbstractMojo;
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.ops4j.pax.construct.util.DirUtils;
import org.ops4j.pax.construct.util.ExcludeSystemBundlesFilter;
import org.ops4j.pax.construct.util.PomUtils;
import org.ops4j.pax.construct.util.PomUtils.Pom;

/**
 * Import an OSGi bundle as a project dependency and mark it for deployment
 * 
 * <code><pre>
 *   mvn pax:import-bundle [-DgroupId=...] -DartifactId=... [-Dversion=...]
 * </pre></code>
 * 
 * @goal import-bundle
 * @aggregator true
 * 
 * @requiresProject false
 */
public class ImportBundleMojo extends AbstractMojo {
    /**
     * Component factory for Maven artifacts
     * 
     * @component
     */
    private ArtifactFactory m_factory;

    /**
     * Component for resolving Maven artifacts
     * 
     * @component
     */
    private ArtifactResolver m_resolver;

    /**
     * Component for resolving Maven metadata
     * 
     * @component
     */
    private ArtifactMetadataSource m_source;

    /**
     * Component factory for Maven projects
     * 
     * @component
     */
    private MavenProjectBuilder m_projectBuilder;

    /**
     * List of remote Maven repositories for the containing project.
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List m_remoteRepos;

    /**
     * The local Maven repository for the containing project.
     * 
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository m_localRepo;

    /**
     * The groupId of the bundle to be imported.
     * 
     * @parameter expression="${groupId}"
     */
    private String groupId;

    /**
     * The artifactId of the bundle to be imported.
     * 
     * @parameter expression="${artifactId}"
     * @required
     */
    private String artifactId;

    /**
     * The version of the bundle to be imported.
     * 
     * @parameter expression="${version}"
     */
    private String version;

    /**
     * Comma-separated list of artifacts (use groupId:artifactId) to exclude from importing.
     * 
     * @parameter expression="${exclusions}"
     */
    private String exclusions;

    /**
     * Reference to the project's provision POM (use artifactId or groupId:artifactId).
     * 
     * @parameter expression="${provisionId}" default-value="provision"
     */
    private String provisionId;

    /**
     * Target directory where the bundle should be imported.
     * 
     * @parameter expression="${targetDirectory}" default-value="${project.basedir}"
     */
    private File targetDirectory;

    /**
     * When true, also try to import any provided dependencies of imported bundles.
     * 
     * @parameter expression="${importTransitive}"
     */
    private boolean importTransitive;

    /**
     * When true, also try to import optional dependencies of imported bundles.
     * 
     * @parameter expression="${importOptional}"
     */
    private boolean importOptional;

    /**
     * When true, also consider compile and runtime dependencies as potential bundles.
     * 
     * @parameter expression="${widenScope}"
     */
    private boolean widenScope;

    /**
     * When true, check dependency artifacts for OSGi metadata before wrapping them.
     * 
     * @parameter expression="${testMetadata}" default-value="true"
     */
    private boolean testMetadata;

    /**
     * When false, mark the imported bundle as optional so it won't be provisioned.
     * 
     * @parameter expression="${deploy}" default-value="true"
     */
    private boolean deploy;

    /**
     * When true, overwrite existing entries with the new imports.
     * 
     * @parameter expression="${overwrite}" default-value="true"
     */
    private boolean overwrite;

    /**
     * The local provisioning POM, where imported non-local bundles are recorded.
     */
    private Pom m_provisionPom;

    /**
     * The bundle POM in the target directory.
     */
    private Pom m_localBundlePom;

    /**
     * A list of potential artifacts (groupId:artifactId:version) to be imported
     */
    private List m_candidateIds;

    /**
     * A list of artifacts (groupId:artifactId) that have already been processed.
     */
    private Set m_visitedIds;

    /**
     * {@inheritDoc}
     */
    public void execute() throws MojoExecutionException {
        populateMissingFields();

        // Find host POMs which will receive the imported dependencies
        m_provisionPom = DirUtils.findPom(targetDirectory, provisionId);
        m_localBundlePom = readBundlePom(targetDirectory);

        if (null == m_provisionPom && null == m_localBundlePom) {
            throw new MojoExecutionException("Cannot execute command."
                    + " It requires a project with an existing pom.xml, but the build is not using one.");
        }

        String rootId = groupId + ':' + artifactId + ':' + version;

        m_candidateIds = new ArrayList();
        m_visitedIds = new HashSet();

        // kickstart the import
        excludeCandidates(exclusions);
        scheduleCandidate(rootId);
        importBundles(rootId);

        // save any dependency updates
        writeUpdatedPom(m_localBundlePom);
        writeUpdatedPom(m_provisionPom);
    }

    /**
     * @param rootId initial import
     */
    private void importBundles(String rootId) {
        while (!m_candidateIds.isEmpty()) {
            String id = (String) m_candidateIds.remove(0);
            String[] fields = id.split(":");

            MavenProject p = buildMavenProject(fields[0], fields[1], fields[2]);
            if (null == p) {
                continue;
            }

            if ("pom".equals(p.getPackaging())) {
                // support 'dependency' POMs
                processDependencies(p);
            } else if (rootId.equals(id) /* user knows best: assume given artifact is a bundle */
                    || PomUtils.isBundleProject(p, m_resolver, m_remoteRepos, m_localRepo, testMetadata)) {
                importBundle(p);

                // stop at first bundle
                if (!importTransitive) {
                    break;
                }

                processDependencies(p);
            } else {
                getLog().info("Ignoring non-bundle dependency " + p.getId());
            }
        }
    }

    /**
     * Populate missing fields with information from the local project and Maven repository
     * 
     * @throws MojoExecutionException
     */
    private void populateMissingFields() throws MojoExecutionException {
        if (PomUtils.isEmpty(groupId)) {
            Pom localPom = DirUtils.findPom(targetDirectory, artifactId);
            if (localPom != null) {
                // use the groupId from the POM
                groupId = localPom.getGroupId();

                if (PomUtils.needReleaseVersion(version)) {
                    // also grab version if missing
                    version = localPom.getVersion();
                }
            } else {
                // this is a common assumption
                groupId = artifactId;
            }
        }

        if (PomUtils.needReleaseVersion(version)) {
            Artifact artifact = m_factory.createBuildArtifact(groupId, artifactId, "RELEASE", "jar");
            version = PomUtils.getReleaseVersion(artifact, m_source, m_remoteRepos, m_localRepo, null);
        }
    }

    /**
     * @param pom the Maven POM to write
     */
    private void writeUpdatedPom(Pom pom) {
        if (pom != null) {
            try {
                pom.write();
            } catch (IOException e) {
                getLog().warn("Unable to update " + pom);
            }
        }
    }

    /**
     * @param here a Maven POM, or a directory containing a file named 'pom.xml'
     * @return the POM, null if the POM is not a bundle project or doesn't exist
     */
    private static Pom readBundlePom(File here) {
        try {
            Pom bundlePom = PomUtils.readPom(here);
            if (null != bundlePom && bundlePom.isBundleProject()) {
                return bundlePom;
            }

            return null;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * Resolve the Maven project for the given artifact, handling when a POM cannot be found in the repository
     * 
     * @param pomGroupId project group id
     * @param pomArtifactId project artifact id
     * @param pomVersion project version
     * @return resolved Maven project
     */
    private MavenProject buildMavenProject(String pomGroupId, String pomArtifactId, String pomVersion) {
        Artifact pomArtifact = m_factory.createProjectArtifact(pomGroupId, pomArtifactId, pomVersion);
        MavenProject project;
        try {
            project = m_projectBuilder.buildFromRepository(pomArtifact, m_remoteRepos, m_localRepo);
        } catch (ProjectBuildingException e) {
            getLog().warn("Problem resolving project " + pomArtifact.getId());
            return null;
        }

        /*
         * look to see if this is a local project (if so then set the POM location)
         */
        Pom localPom = DirUtils.findPom(targetDirectory, pomGroupId + ':' + pomArtifactId);
        if (localPom != null) {
            project.setFile(localPom.getFile());
        }

        /*
         * Repair stubs (ie. when a POM couldn't be found in the various repositories)
         */
        DistributionManagement dm = project.getDistributionManagement();
        if (dm != null && "generated".equals(dm.getStatus())) {
            if (localPom != null) {
                // local project, use values from the local POM
                project.setPackaging(localPom.getPackaging());
                project.setName(localPom.getId());
            } else {
                // remote project - assume it creates a jarfile (so we can test later for OSGi metadata)
                Artifact jar = m_factory.createBuildArtifact(pomGroupId, pomArtifactId, pomVersion, "jar");
                project.setArtifact(jar);

                project.setPackaging("jar");
                project.setName(jar.getId());
            }
        }

        return project;
    }

    /**
     * Search direct dependencies for more import candidates
     * 
     * @param project the Maven project being imported
     */
    private void processDependencies(MavenProject project) {
        try {
            /*
             * exclude common OSGi system bundles, as they don't need to be imported or provisioned
             */
            Set artifacts = project.createArtifacts(m_factory, null, new ExcludeSystemBundlesFilter());
            for (Iterator i = artifacts.iterator(); i.hasNext();) {
                Artifact artifact = (Artifact) i.next();
                String candidateId = getCandidateId(artifact);
                String scope = artifact.getScope();

                scope = adjustDependencyScope(scope);

                if (!importOptional && artifact.isOptional()) {
                    getLog().info("Skipping optional dependency " + artifact);
                } else if (Artifact.SCOPE_PROVIDED.equals(scope)) {
                    scheduleCandidate(candidateId);
                } else {
                    getLog().info("Skipping dependency " + artifact);
                }
            }
        } catch (InvalidDependencyVersionException e) {
            getLog().warn("Problem resolving dependencies for " + project.getId());
        }
    }

    /**
     * @param candidateId potential new candidate
     */
    private void scheduleCandidate(String candidateId) {
        int versionIndex = candidateId.lastIndexOf(':');
        if (m_visitedIds.add(candidateId.substring(0, versionIndex))) {
            m_candidateIds.add(candidateId);
        }
    }

    /**
     * Support widening of scopes to treat compile and runtime dependencies as provided dependencies
     * 
     * @param scope original dependency scope
     * @return potentially widened scope
     */
    private String adjustDependencyScope(String scope) {
        if (widenScope && !Artifact.SCOPE_SYSTEM.equals(scope) && !Artifact.SCOPE_TEST.equals(scope)) {
            return Artifact.SCOPE_PROVIDED;
        }

        return scope;
    }

    /**
     * @param artifact candidate artifact
     * @return simple unique id
     */
    private static String getCandidateId(Artifact artifact) {
        return artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + PomUtils.getMetaVersion(artifact);
    }

    /**
     * Add bundle as a dependency to the provisioning POM and the local bundle POM, as appropriate
     * 
     * @param project bundle project
     */
    private void importBundle(MavenProject project) {
        Dependency dependency = new Dependency();

        dependency.setGroupId(project.getGroupId());
        dependency.setArtifactId(project.getArtifactId());
        dependency.setVersion(project.getVersion());
        dependency.setOptional(!deploy);

        // only add non-local bundles to the provisioning POM
        if (m_provisionPom != null && project.getFile() == null) {
            getLog().info("Importing " + project.getName() + " to " + m_provisionPom);
            m_provisionPom.addDependency(dependency, overwrite);
        }

        if (m_localBundlePom != null) {
            // use provided scope when adding to bundle pom
            dependency.setScope(Artifact.SCOPE_PROVIDED);

            getLog().info("Adding " + project.getName() + " as dependency to " + m_localBundlePom);
            m_localBundlePom.addDependency(dependency, overwrite);
        }
    }

    /**
     * Explicitly exclude artifacts from the import process
     * 
     * @param artifacts comma-separated list of artifacts to exclude from importing
     */
    private void excludeCandidates(String artifacts) {
        if (PomUtils.isEmpty(artifacts)) {
            return;
        }

        String[] exclusionIds = artifacts.split(",");
        for (int i = 0; i < exclusionIds.length; i++) {
            String id = exclusionIds[i].trim();
            String[] fields = id.split(":");
            if (fields.length > 1) {
                // handle groupId:artifactId:other:stuff
                m_visitedIds.add(fields[0] + ':' + fields[1]);
            } else {
                // assume groupId same as artifactId
                m_visitedIds.add(id + ':' + id);
            }
        }
    }
}