org.ops4j.pax.construct.lifecycle.ProvisionMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.construct.lifecycle.ProvisionMojo.java

Source

package org.ops4j.pax.construct.lifecycle;

/*
 * 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.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.installer.ArtifactInstallationException;
import org.apache.maven.artifact.installer.ArtifactInstaller;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Dependency;
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.apache.maven.settings.Mirror;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.util.IOUtil;
import org.ops4j.pax.construct.util.PomUtils;
import org.ops4j.pax.construct.util.StreamFactory;

/**
 * Provision all local and imported bundles onto the selected OSGi framework
 * 
 * <code><pre>
 *   mvn pax:provision [-Dframework=felix|equinox|kf|concierge] [-Dprofiles=log,war,spring,...]
 * </pre></code>
 *
 * If you don't have Pax-Runner in your local Maven repository this command
 * will automatically attempt to download the latest release. It will then
 * continue to use this locally installed version of Pax-Runner unless you
 * add <code>-U</code> to force it to check online for a later release, or
 * <code>-Drunner=version</code> to temporarily use a different version.
 * 
 * @goal provision
 * @aggregator true
 * 
 * @requiresProject false
 * @requiresDependencyResolution test
 */
public class ProvisionMojo extends AbstractMojo {
    /**
     * Maven groupId for the new Pax-Runner
     */
    private static final String PAX_RUNNER_GROUP = "org.ops4j.pax.runner";

    /**
     * Maven artifactId for the new Pax-Runner
     */
    private static final String PAX_RUNNER_ARTIFACT = "pax-runner";

    /**
     * Main entry-point for the new Pax-Runner
     */
    private static final String PAX_RUNNER_METHOD = "org.ops4j.pax.runner.Run";

    /**
     * Accumulated set of bundles to be deployed
     */
    private static List m_bundleIds;

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

    /**
     * Component factory for Maven artifacts
     * 
     * @component
     */
    private ArtifactFactory m_factory;

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

    /**
     * Component for installing Maven artifacts
     * 
     * @component
     */
    private ArtifactInstaller m_installer;

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

    /**
     * The local Maven settings.
     * 
     * @parameter expression="${settings}"
     * @required
     * @readonly
     */
    private Settings m_settings;

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

    /**
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject m_project;

    /**
     * The current Maven reactor.
     * 
     * @parameter expression="${reactorProjects}"
     * @required
     * @readonly
     */
    private List m_reactorProjects;

    /**
     * Name of the OSGi framework to deploy onto.
     * 
     * @parameter expression="${framework}"
     */
    private String framework;

    /**
     * When true, start the OSGi framework and deploy the provisioned bundles.
     * 
     * @parameter expression="${deploy}" default-value="true"
     */
    private boolean deploy;

    /**
     * Comma separated list of additional Pax-Runner profiles to deploy.
     * 
     * @parameter expression="${profiles}"
     */
    private String profiles;

    /**
     * URL of file containing additional Pax-Runner arguments.
     * 
     * @parameter expression="${args}"
     */
    private String args;

    /**
     * Ignore bundle dependencies when deploying project.
     * 
     * @parameter expression="${noDeps}"
     */
    private boolean noDependencies;

    /**
     * Comma separated list of additional POMs with bundles as dependencies.
     * 
     * @parameter expression="${deployPoms}"
     */
    private String deployPoms;

    /**
     * Comma separated list of additional bundle URLs to deploy.
     * 
     * @parameter expression="${deployURLs}"
     */
    private String deployURLs;

    /**
     * The version of Pax-Runner to use for provisioning.
     * 
     * @parameter expression="${runner}" default-value="RELEASE"
     */
    private String runner;

    /**
     * A set of provision commands for Pax-Runner.
     * 
     * @parameter expression="${provision}"
     */
    private String[] provision;

    /**
     * Component factory for Maven repositories.
     * 
     * @component
     */
    private ArtifactRepositoryFactory m_repoFactory;

    /**
     * @component roleHint="default"
     */
    private ArtifactRepositoryLayout m_defaultLayout;

    /**
     * Component for calculating mirror details.
     * 
     * @component
     */
    private WagonManager m_wagonManager;

    /**
     * Runtime helper available on Maven 2.0.9 and above.
     */
    private Method m_getMirrorRepository;

    /**
     * {@inheritDoc}
     */
    public void execute() throws MojoExecutionException {
        m_bundleIds = new ArrayList();

        if (deployPoms != null) {
            addAdditionalPoms();
        }

        if (m_project.getFile() != null) {
            for (Iterator i = m_reactorProjects.iterator(); i.hasNext();) {
                addProjectBundles((MavenProject) i.next(), false == noDependencies);
            }
        }

        setupRuntimeHelpers();

        deployBundles();
    }

    /**
     * Use reflection to find if certain runtime helper methods are available.
     */
    private void setupRuntimeHelpers() {
        try {
            m_getMirrorRepository = m_wagonManager.getClass().getMethod("getMirrorRepository",
                    new Class[] { ArtifactRepository.class });
        } catch (RuntimeException e) {
            m_getMirrorRepository = null;
        } catch (NoSuchMethodException e) {
            m_getMirrorRepository = null;
        }
    }

    /**
     * Does this look like a provisioning POM? ie. artifactId of 'provision', packaging type 'pom', with dependencies
     * 
     * @param project a Maven project
     * @return true if this looks like a provisioning POM, otherwise false
     */
    public static boolean isProvisioningPom(MavenProject project) {
        // ignore POMs which don't have provision as their artifactId
        if (!"provision".equals(project.getArtifactId())) {
            return false;
        }

        // ignore POMs that produce actual artifacts
        if (!"pom".equals(project.getPackaging())) {
            return false;
        }

        // ignore POMs with no dependencies at all
        List dependencies = project.getDependencies();
        if (dependencies == null || dependencies.size() == 0) {
            return false;
        }

        return true;
    }

    /**
     * Adds project artifact (if it's a bundle) to the deploy list as well as any non-optional bundle dependencies
     * 
     * @param project a Maven project
     * @param checkDependencies when true, check project dependencies for other bundles to provision
     */
    private void addProjectBundles(MavenProject project, boolean checkDependencies) {
        if (PomUtils.isBundleProject(project, m_resolver, m_remoteRepos, m_localRepo, true)) {
            provisionBundle(project.getArtifact());
        }

        if (checkDependencies || isProvisioningPom(project)) {
            addProjectDependencies(project);
        }
    }

    /**
     * Adds any non-optional bundle dependencies to the deploy list
     * 
     * @param project a Maven project
     */
    private void addProjectDependencies(MavenProject project) {
        Set artifacts = project.getArtifacts();
        for (Iterator i = artifacts.iterator(); i.hasNext();) {
            Artifact artifact = (Artifact) i.next();
            if (!artifact.isOptional() && !Artifact.SCOPE_TEST.equals(artifact.getScope())) {
                provisionBundle(artifact);
            }
        }
    }

    /**
     * @param bundle potential bundle artifact
     */
    private void provisionBundle(Artifact bundle) {
        if ("pom".equals(bundle.getType())) {
            return;
        }

        // force download here, as next check tries to avoid downloading where possible
        if (!PomUtils.downloadFile(bundle, m_resolver, m_remoteRepos, m_localRepo)) {
            getLog().warn("Skipping missing artifact " + bundle);
            return;
        }

        if (PomUtils.isBundleArtifact(bundle, m_resolver, m_remoteRepos, m_localRepo, true)) {
            String version = PomUtils.getMetaVersion(bundle);
            String id = bundle.getGroupId() + ':' + bundle.getArtifactId() + ':' + version + ':' + bundle.getType();
            if (!m_bundleIds.contains(id)) {
                m_bundleIds.add(id);
            }
        } else {
            getLog().warn("Skipping non-bundle artifact " + bundle);
        }
    }

    /**
     * Add user supplied POMs as if they were in the Maven reactor
     */
    private void addAdditionalPoms() {
        String[] pomPaths = deployPoms.split(",");
        for (int i = 0; i < pomPaths.length; i++) {
            File pomFile = new File(pomPaths[i].trim());
            if (pomFile.exists()) {
                try {
                    addProjectBundles(m_projectBuilder.build(pomFile, m_localRepo, null), true);
                } catch (ProjectBuildingException e) {
                    getLog().warn("Unable to build Maven project for " + pomFile);
                }
            }
        }
    }

    /**
     * Create deployment POM and pass it onto Pax-Runner for provisioning
     * 
     * @throws MojoExecutionException
     */
    private void deployBundles() throws MojoExecutionException {
        if (m_bundleIds.size() == 0) {
            getLog().info("~~~~~~~~~~~~~~~~~~~");
            getLog().info(" No bundles found! ");
            getLog().info("~~~~~~~~~~~~~~~~~~~");
        }

        List bundles = resolveProvisionedBundles();
        MavenProject deployProject = createDeploymentProject(bundles);
        installDeploymentPom(deployProject);

        if (!deploy) {
            getLog().info("Skipping OSGi deployment");
            return;
        }

        m_remoteRepos.add(getOps4jRepository()); // can remove this once runner is on central

        String delim = "";
        StringBuffer repoListBuilder = new StringBuffer("+");
        for (Iterator i = m_remoteRepos.iterator(); i.hasNext();) {
            repoListBuilder.append(delim);

            ArtifactRepository repo = (ArtifactRepository) i.next();
            repoListBuilder.append(getRepositoryURL(repo));

            if (repo.getSnapshots().isEnabled()) {
                repoListBuilder.append("@snapshots");
            }

            if (false == repo.getReleases().isEnabled()) {
                repoListBuilder.append("@noreleases");
            }

            delim = ",";
        }

        if (PomUtils.needReleaseVersion(runner)) {
            // find the latest release of Pax-Runner by querying the local and remote repos...
            Artifact runnerProject = m_factory.createProjectArtifact(PAX_RUNNER_GROUP, PAX_RUNNER_ARTIFACT, runner);
            runner = PomUtils.getReleaseVersion(runnerProject, m_source, m_remoteRepos, m_localRepo, null);
        }

        /*
         * Dynamically load the correct Pax-Runner code
         */
        Pattern classicVersion = Pattern.compile("0\\.[1-4]\\.\\d");
        if (classicVersion.matcher(runner).matches()) {
            Class clazz = loadRunnerClass("org.ops4j.pax", "runner", PAX_RUNNER_METHOD, false);
            deployRunnerClassic(clazz, deployProject, repoListBuilder.toString());
        } else {
            Class clazz = loadRunnerClass(PAX_RUNNER_GROUP, PAX_RUNNER_ARTIFACT, PAX_RUNNER_METHOD, true);
            deployRunnerNG(clazz, deployProject, repoListBuilder.toString());
        }
    }

    /**
     * @param repo remote Maven repository
     * @return repository (or mirror) URL
     */
    private String getRepositoryURL(ArtifactRepository repo) {
        if (null != m_getMirrorRepository) {
            try {
                ArtifactRepository mirror = (ArtifactRepository) m_getMirrorRepository.invoke(m_wagonManager,
                        new Object[] { repo });

                if (null != mirror) {
                    return mirror.getUrl();
                }
            } catch (RuntimeException e) {
                getLog().warn(e);
            } catch (IllegalAccessException e) {
                getLog().warn(e);
            } catch (InvocationTargetException e) {
                getLog().warn(e);
            }
        }

        Mirror mirror = m_settings.getMirrorOf(repo.getId());
        if (null != mirror) {
            return mirror.getUrl();
        }

        mirror = m_settings.getMirrorOf("*");
        if (null != mirror) {
            return mirror.getUrl();
        }

        return repo.getUrl();
    }

    /**
     * Attempt to resolve each provisioned bundle, and warn about any we can't find
     * 
     * @return list of bundles to be deployed (as Maven dependencies)
     */
    private List resolveProvisionedBundles() {
        List dependencies = new ArrayList();
        for (Iterator i = m_bundleIds.iterator(); i.hasNext();) {
            String id = (String) i.next();
            String[] fields = id.split(":");

            Dependency dep = new Dependency();
            dep.setGroupId(fields[0]);
            dep.setArtifactId(fields[1]);
            dep.setVersion(fields[2]);
            dep.setType(fields[3]);
            dep.setScope(Artifact.SCOPE_PROVIDED);

            dependencies.add(dep);
        }
        return dependencies;
    }

    /**
     * Create new POM (based on the root POM) which lists the deployed bundles as dependencies
     * 
     * @param bundles list of bundles to be deployed
     * @return deployment project
     * @throws MojoExecutionException
     */
    private MavenProject createDeploymentProject(List bundles) throws MojoExecutionException {
        MavenProject deployProject;

        if (null == m_project.getFile()) {
            deployProject = new MavenProject();
            deployProject.setGroupId("examples");
            deployProject.setArtifactId("pax-provision");
            deployProject.setVersion("1.0-SNAPSHOT");
        } else {
            deployProject = new MavenProject(m_project);
        }

        String internalId = PomUtils.getCompoundId(deployProject.getGroupId(), deployProject.getArtifactId());
        deployProject.setGroupId(internalId + ".build");
        deployProject.setArtifactId("deployment");

        // remove unnecessary cruft
        deployProject.setPackaging("pom");
        deployProject.getModel().setModules(null);
        deployProject.getModel().setDependencies(bundles);
        deployProject.getModel().setPluginRepositories(null);
        deployProject.getModel().setReporting(null);
        deployProject.setBuild(null);

        File deployFile = new File(deployProject.getBasedir(), "runner/deploy-pom.xml");

        deployFile.getParentFile().mkdirs();
        deployProject.setFile(deployFile);

        try {
            Writer writer = StreamFactory.newXmlWriter(deployFile);
            deployProject.writeModel(writer);
            IOUtil.close(writer);
        } catch (IOException e) {
            throw new MojoExecutionException("Unable to write deployment POM " + deployFile);
        }

        return deployProject;
    }

    /**
     * Install deployment POM in the local Maven repository
     * 
     * @param project deployment project
     * @throws MojoExecutionException
     */
    private void installDeploymentPom(MavenProject project) throws MojoExecutionException {
        String groupId = project.getGroupId();
        String artifactId = project.getArtifactId();
        String version = project.getVersion();

        Artifact pomArtifact = m_factory.createProjectArtifact(groupId, artifactId, version);

        try {
            m_installer.install(project.getFile(), pomArtifact, m_localRepo);
        } catch (ArtifactInstallationException e) {
            throw new MojoExecutionException("Unable to install deployment POM " + pomArtifact);
        }
    }

    /**
     * Dynamically resolve and load the Pax-Runner class
     * 
     * @param groupId pax-runner group id
     * @param artifactId pax-runner artifact id
     * @param mainClass main pax-runner classname
     * @param needClassifier classify pax-runner artifact according to current JVM
     * @return main pax-runner class
     * @throws MojoExecutionException
     */
    private Class loadRunnerClass(String groupId, String artifactId, String mainClass, boolean needClassifier)
            throws MojoExecutionException {
        String jdk = null;
        if (needClassifier && System.getProperty("java.class.version").compareTo("49.0") < 0) {
            jdk = "jdk14";
        }

        Artifact jarArtifact = m_factory.createArtifactWithClassifier(groupId, artifactId, runner, "jar", jdk);
        if (!PomUtils.downloadFile(jarArtifact, m_resolver, m_remoteRepos, m_localRepo)) {
            throw new MojoExecutionException("Unable to find Pax-Runner " + jarArtifact);
        }

        URL[] urls = new URL[1];
        try {
            urls[0] = jarArtifact.getFile().toURI().toURL();
        } catch (MalformedURLException e) {
            throw new MojoExecutionException("Bad Jar location " + jarArtifact.getFile());
        }

        try {
            ClassLoader loader = new URLClassLoader(urls);
            Thread.currentThread().setContextClassLoader(loader);
            return Class.forName(mainClass, true, loader);
        } catch (ClassNotFoundException e) {
            throw new MojoExecutionException("Unable to find entry point " + mainClass + " in " + urls[0]);
        }
    }

    /**
     * Deploy bundles using the 'classic' Pax-Runner
     * 
     * @param mainClass main Pax-Runner class
     * @param project deployment project
     * @param repositories comma separated list of Maven repositories
     * @throws MojoExecutionException
     */
    private void deployRunnerClassic(Class mainClass, MavenProject project, String repositories)
            throws MojoExecutionException {
        String workDir = project.getBasedir() + "/runner";

        String cachedPomName = project.getArtifactId() + '_' + project.getVersion() + ".pom";
        File cachedPomFile = new File(workDir + "/lib/" + cachedPomName);

        // Force reload of pom
        cachedPomFile.delete();

        if (PomUtils.isEmpty(framework)) {
            framework = "felix";
        }

        String[] deployAppCmds = { "--dir=" + workDir, "--no-md5", "--platform=" + framework, "--profile=default",
                "--repository=" + repositories, "--localRepository=" + m_localRepo.getBasedir(),
                project.getGroupId(), project.getArtifactId(), project.getVersion() };

        invokePaxRunner(mainClass, deployAppCmds);
    }

    /**
     * This method allows subclasses to specify additional
     * commands for provisioning.  By default provide no initial 
     * deploy commands. 
     * @return A List of Strings that represent the deploy commands.
     */
    protected List getDeployCommands() {
        return new ArrayList();
    }

    /**
     * Deploy bundles using the new Pax-Runner codebase
     * 
     * @param mainClass main Pax-Runner class
     * @param project deployment project
     * @param repositories comma separated list of Maven repositories
     * @throws MojoExecutionException
     */
    private void deployRunnerNG(Class mainClass, MavenProject project, String repositories)
            throws MojoExecutionException {
        List deployAppCmds = getDeployCommands();

        // only apply if explicitly configured
        if (PomUtils.isNotEmpty(framework)) {
            deployAppCmds.add("--platform=" + framework);
        }
        if (PomUtils.isNotEmpty(profiles)) {
            deployAppCmds.add("--profiles=" + profiles);
        }

        if (PomUtils.isNotEmpty(args)) {
            try {
                new URL(args); // check syntax
            } catch (MalformedURLException e) {
                // assume it's a local filename
                File argsFile = new File(args);
                args = argsFile.toURI().toString();
            }

            // custom Pax-Runner arguments file
            deployAppCmds.add("--args=" + args);
        }

        // apply project provision settings before defaults
        deployAppCmds.addAll(Arrays.asList(provision));

        // main deployment pom with project bundles as dependencies
        deployAppCmds.add(project.getFile().getAbsolutePath());

        if (PomUtils.isNotEmpty(deployURLs)) {
            // additional (external) bundle URLs
            String[] urls = deployURLs.split(",");
            for (int i = 0; i < urls.length; i++) {
                deployAppCmds.add(urls[i].trim());
            }
        }

        // use project settings to access remote/local repositories
        deployAppCmds.add("--localRepository=" + m_localRepo.getBasedir());
        deployAppCmds.add("--repositories=" + repositories);
        deployAppCmds.add("--overwriteUserBundles");

        getLog().debug("Starting Pax-Runner " + runner + " with: " + deployAppCmds.toString());
        invokePaxRunner(mainClass, (String[]) deployAppCmds.toArray(new String[deployAppCmds.size()]));
    }

    /**
     * Invoke Pax-Runner in-process
     * 
     * @param mainClass main Pax-Runner class
     * @param commands array of command-line options
     * @throws MojoExecutionException
     */
    private void invokePaxRunner(Class mainClass, String[] commands) throws MojoExecutionException {
        Class[] paramTypes = new Class[1];
        paramTypes[0] = String[].class;

        Object[] paramValues = new Object[1];
        paramValues[0] = commands;

        try {
            Method entryPoint = mainClass.getMethod("main", paramTypes);
            entryPoint.invoke(null, paramValues);
        } catch (NoSuchMethodException e) {
            throw new MojoExecutionException("Unable to find Pax-Runner entry point");
        } catch (IllegalAccessException e) {
            throw new MojoExecutionException("Unable to access Pax-Runner entry point");
        } catch (InvocationTargetException e) {
            throw new MojoExecutionException("Pax-Runner exception", e);
        }
    }

    /**
     * @return backup OPS4J remote repository
     */
    ArtifactRepository getOps4jRepository() {
        ArtifactRepositoryPolicy noSnapshots = new ArtifactRepositoryPolicy(false,
                ArtifactRepositoryPolicy.UPDATE_POLICY_DAILY, null);
        ArtifactRepositoryPolicy releases = new ArtifactRepositoryPolicy(true,
                ArtifactRepositoryPolicy.UPDATE_POLICY_NEVER, null);

        return m_repoFactory.createArtifactRepository("ops4j.releases", "http://repository.ops4j.org/maven2/",
                m_defaultLayout, noSnapshots, releases);
    }
}