blob: 63b2abdd411b1af1903e13db924897bd6aa9eab7 [file] [log] [blame]
/*
* 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();
}
}
}