NIFI-376: - Adding a new maven dependency for printing the dependencies provided...
authorMatt Gilman <matt.c.gilman@gmail.com>
Wed, 19 Aug 2015 17:35:07 +0000 (13:35 -0400)
committerjoewitt <joewitt@apache.org>
Wed, 19 Aug 2015 20:42:09 +0000 (16:42 -0400)
Signed-off-by: joewitt <joewitt@apache.org>
README.md
pom.xml
src/main/java/org/apache/nifi/NarProvidedDependenciesMojo.java [new file with mode: 0644]

index 71772ac..a9c0f23 100644 (file)
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Apache NiFi NAR Maven Plugin helps to build NiFi Archive bundles to support the
 
 ## Requirements
 * JDK 1.7 or higher
-* Apache Maven 3.0.5 or higher
+* Apache Maven 3.1.0 or higher
 
 ## Getting Started
 
diff --git a/pom.xml b/pom.xml
index 5d1778c..431d87c 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     </parent>
     <groupId>org.apache.nifi</groupId>
     <artifactId>nifi-nar-maven-plugin</artifactId>
-    <version>1.0.2-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <packaging>maven-plugin</packaging>
     <description>Apache NiFi Nar Maven Plugin</description>
     <url>http://nifi.apache.org</url>
@@ -76,7 +76,7 @@
     <properties>
         <maven.compiler.source>1.7</maven.compiler.source>
         <maven.compiler.target>1.7</maven.compiler.target>
-        <maven.min-version>3.0.5</maven.min-version>
+        <maven.min-version>3.1.0</maven.min-version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <inceptionYear>2014</inceptionYear>
         <dependency>          
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-plugin-api</artifactId>
-            <version>2.2.1</version>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>          
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-artifact</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>          
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-compat</artifactId>
+            <version>3.1.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.maven.plugins</groupId>
             <type>maven-plugin</type>
             <version>2.9</version>
         </dependency>
+        <dependency>          
+            <groupId>org.apache.maven.shared</groupId>
+            <artifactId>maven-dependency-tree</artifactId>
+            <version>2.2</version>
+        </dependency>
         <dependency>
             <!-- No code from maven-jar-plugin is actually used; it's included
             just to simplify the dependencies list.                     -->
diff --git a/src/main/java/org/apache/nifi/NarProvidedDependenciesMojo.java b/src/main/java/org/apache/nifi/NarProvidedDependenciesMojo.java
new file mode 100644 (file)
index 0000000..63b2abd
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * 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.nifi;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.project.ProjectBuilder;
+import org.apache.maven.project.ProjectBuildingException;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.project.ProjectBuildingResult;
+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.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
+import org.eclipse.aether.RepositorySystemSession;
+
+/**
+ * Generates the listing of dependencies that is provided by the NAR dependency of the current NAR. This is important as artifacts that bundle dependencies will
+ * not project those dependences using the traditional maven dependency plugin. This plugin will override that setting in order to print the dependencies being
+ * inherited at runtime.
+ */
+@Mojo(name = "provided-nar-dependencies", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = false, requiresDependencyResolution = ResolutionScope.RUNTIME)
+public class NarProvidedDependenciesMojo extends AbstractMojo {
+
+    private static final String NAR = "nar";
+
+    /**
+     * The Maven project.
+     */
+    @Parameter(defaultValue = "${project}", readonly = true, required = true)
+    private MavenProject project;
+
+    /**
+     * The local artifact repository.
+     */
+    @Parameter(defaultValue = "${localRepository}", readonly = true)
+    private ArtifactRepository localRepository;
+
+    /**
+     * The {@link RepositorySystemSession} used for obtaining the local and remote artifact repositories.
+     */
+    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
+    private RepositorySystemSession repoSession;
+
+    /**
+     * If specified, this parameter will cause the dependency tree to be written using the specified format. Currently supported format are: <code>tree</code>
+     * or <code>pom</code>.
+     */
+    @Parameter(property = "mode", defaultValue = "tree")
+    private String mode;
+
+    /**
+     * The dependency tree builder to use for verbose output.
+     */
+    @Component
+    private DependencyTreeBuilder dependencyTreeBuilder;
+
+    /**
+     * *
+     * The {@link ArtifactHandlerManager} into which any extension {@link ArtifactHandler} instances should have been injected when the extensions were loaded.
+     */
+    @Component
+    private ArtifactHandlerManager artifactHandlerManager;
+
+    /**
+     * The {@link ProjectBuilder} used to generate the {@link MavenProject} for the nar artifact the dependency tree is being generated for.
+     */
+    @Component
+    private ProjectBuilder projectBuilder;
+
+    /*
+     * @see org.apache.maven.plugin.Mojo#execute()
+     */
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        try {
+            // find the nar dependency
+            Artifact narArtifact = null;
+            for (final Artifact artifact : project.getDependencyArtifacts()) {
+                if (NAR.equals(artifact.getType())) {
+                    // ensure the project doesn't have two nar dependencies
+                    if (narArtifact != null) {
+                        throw new MojoExecutionException("Project can only have one NAR dependency.");
+                    }
+
+                    // record the nar dependency
+                    narArtifact = artifact;
+                }
+            }
+
+            // ensure there is a nar dependency
+            if (narArtifact == null) {
+                throw new MojoExecutionException("Project does not have any NAR dependencies.");
+            }
+
+            // build the project for the nar artifact
+            final ProjectBuildingRequest narRequest = new DefaultProjectBuildingRequest();
+            narRequest.setRepositorySession(repoSession);
+            final ProjectBuildingResult narResult = projectBuilder.build(narArtifact, narRequest);
+
+            // get the artifact handler for excluding dependencies
+            final ArtifactHandler narHandler = excludesDependencies(narArtifact);
+            narArtifact.setArtifactHandler(narHandler);
+
+            // nar artifacts by nature includes dependencies, however this prevents the
+            // transitive dependencies from printing using tools like dependency:tree.
+            // here we are overriding the artifact handler for all nars so the
+            // dependencies can be listed. this is important because nar dependencies
+            // will be used as the parent classloader for this nar and seeing what
+            // dependencies are provided is critical.
+            final Map<String, ArtifactHandler> narHandlerMap = new HashMap<>();
+            narHandlerMap.put(NAR, narHandler);
+            artifactHandlerManager.addHandlers(narHandlerMap);
+
+            // get the dependency tree
+            final DependencyNode root = dependencyTreeBuilder.buildDependencyTree(narResult.getProject(), localRepository, null);
+
+            // write the appropriate output
+            DependencyNodeVisitor visitor = null;
+            if ("tree".equals(mode)) {
+                visitor = new TreeWriter();
+            } else if ("pom".equals(mode)) {
+                visitor = new PomWriter();
+            }
+
+            // ensure the mode was specified correctly
+            if (visitor == null) {
+                throw new MojoExecutionException("The specified mode is invalid. Supported options are 'tree' and 'pom'.");
+            }
+
+            // visit and print the results
+            root.accept(visitor);
+            getLog().info("--- Provided NAR Dependencies ---\n\n" + visitor.toString());
+        } catch (DependencyTreeBuilderException | ProjectBuildingException e) {
+            throw new MojoExecutionException("Cannot build project dependency tree", e);
+        }
+    }
+
+    /**
+     * Gets the Maven project used by this mojo.
+     *
+     * @return the Maven project
+     */
+    public MavenProject getProject() {
+        return project;
+    }
+
+    /**
+     * Creates a new ArtifactHandler for the specified Artifact that overrides the includeDependencies flag. When set, this flag prevents transitive
+     * dependencies from being printed in dependencies plugin.
+     *
+     * @param artifact  The artifact
+     * @return          The handler for the artifact
+     */
+    private ArtifactHandler excludesDependencies(final Artifact artifact) {
+        final ArtifactHandler orig = artifact.getArtifactHandler();
+
+        return new ArtifactHandler() {
+            @Override
+            public String getExtension() {
+                return orig.getExtension();
+            }
+
+            @Override
+            public String getDirectory() {
+                return orig.getDirectory();
+            }
+
+            @Override
+            public String getClassifier() {
+                return orig.getClassifier();
+            }
+
+            @Override
+            public String getPackaging() {
+                return orig.getPackaging();
+            }
+
+            // mark dependencies has excluded so they will appear in tree listing
+            @Override
+            public boolean isIncludesDependencies() {
+                return false;
+            }
+
+            @Override
+            public String getLanguage() {
+                return orig.getLanguage();
+            }
+
+            @Override
+            public boolean isAddedToClasspath() {
+                return orig.isAddedToClasspath();
+            }
+        };
+    }
+
+    /**
+     * Returns whether the specified dependency has test scope.
+     *
+     * @param node  The dependency
+     * @return      What the dependency is a test scoped dep
+     */
+    private boolean isTest(final DependencyNode node) {
+        return "test".equals(node.getArtifact().getScope());
+    }
+
+    /**
+     * A dependency visitor that builds a dependency tree.
+     */
+    private class TreeWriter implements DependencyNodeVisitor {
+
+        private final StringBuilder output = new StringBuilder();
+        private final Deque<DependencyNode> hierarchy = new ArrayDeque<>();
+
+        @Override
+        public boolean visit(DependencyNode node) {
+            // add this node
+            hierarchy.push(node);
+
+            // don't print test deps, but still add to hierarchy as they will
+            // be removed in endVisit below
+            if (isTest(node)) {
+                return false;
+            }
+
+            // build the padding
+            final StringBuilder pad = new StringBuilder();
+            for (int i = 0; i < hierarchy.size() - 1; i++) {
+                pad.append("   ");
+            }
+            pad.append("+- ");
+
+            // log it
+            output.append(pad).append(node.toNodeString()).append("\n");
+
+            return true;
+        }
+
+        @Override
+        public boolean endVisit(DependencyNode node) {
+            hierarchy.pop();
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return output.toString();
+        }
+    }
+
+    /**
+     * A dependency visitor that generates output that can be copied into a pom's dependency management section.
+     */
+    private class PomWriter implements DependencyNodeVisitor {
+
+        private final StringBuilder output = new StringBuilder();
+
+        @Override
+        public boolean visit(DependencyNode node) {
+            if (isTest(node)) {
+                return false;
+            }
+
+            final Artifact artifact = node.getArtifact();
+            if (!NAR.equals(artifact.getType())) {
+                output.append("<dependency>\n");
+                output.append("    <groupId>").append(artifact.getGroupId()).append("</groupId>\n");
+                output.append("    <artifactId>").append(artifact.getArtifactId()).append("</artifactId>\n");
+                output.append("    <version>").append(artifact.getVersion()).append("</version>\n");
+                output.append("    <scope>provided</scope>\n");
+                output.append("</dependency>\n");
+            }
+
+            return true;
+        }
+
+        @Override
+        public boolean endVisit(DependencyNode node) {
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return output.toString();
+        }
+    }
+}