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