blob: 0e3290208ef361c4d9c5ffcb0d2ba7c9cd9ef947 [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.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.installer.ArtifactInstaller;
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.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.dependency.utils.DependencyStatusSets;
import org.apache.maven.plugin.dependency.utils.DependencyUtil;
import org.apache.maven.plugin.dependency.utils.filters.DestFileFilter;
import org.apache.maven.plugin.dependency.utils.resolvers.ArtifactsResolver;
import org.apache.maven.plugin.dependency.utils.resolvers.DefaultArtifactsResolver;
import org.apache.maven.plugin.dependency.utils.translators.ArtifactTranslator;
import org.apache.maven.plugin.dependency.utils.translators.ClassifierTypeTranslator;
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.execution.MavenSession;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
import org.apache.maven.shared.artifact.filter.collection.ClassifierFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
/**
* Packages the current project as an Apache NiFi Archive (NAR).
*
* The following code is derived from maven-dependencies-plugin and
* maven-jar-plugin. The functionality of CopyDependenciesMojo and JarMojo was
* simplified to the use case of NarMojo.
*
*/
@Mojo(name = "nar", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = false, requiresDependencyResolution = ResolutionScope.RUNTIME)
public class NarMojo extends AbstractMojo {
private static final String[] DEFAULT_EXCLUDES = new String[]{"**/package.html"};
private static final String[] DEFAULT_INCLUDES = new String[]{"**/**"};
/**
* POM
*
*/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;
@Parameter(defaultValue = "${session}", readonly = true, required = true)
protected MavenSession session;
/**
* List of files to include. Specified as fileset patterns.
*/
@Parameter(property = "includes")
protected String[] includes;
/**
* List of files to exclude. Specified as fileset patterns.
*/
@Parameter(property = "excludes")
protected String[] excludes;
/**
* Name of the generated NAR.
*
*/
@Parameter(alias = "narName", property = "nar.finalName", defaultValue = "${project.build.finalName}", required = true)
protected String finalName;
/**
* The Jar archiver.
*
* \@\component role="org.codehaus.plexus.archiver.Archiver" roleHint="jar"
*/
@Component(role = org.codehaus.plexus.archiver.Archiver.class, hint = "jar")
private JarArchiver jarArchiver;
/**
* The archive configuration to use.
*
* See <a
* href="http://maven.apache.org/shared/maven-archiver/index.html">the
* documentation for Maven Archiver</a>.
*
*/
@Parameter(property = "archive")
protected final MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
/**
* Path to the default MANIFEST file to use. It will be used if
* <code>useDefaultManifestFile</code> is set to <code>true</code>.
*
*/
@Parameter(property = "defaultManifestFiles", defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF", readonly = true, required = true)
protected File defaultManifestFile;
/**
* Set this to <code>true</code> to enable the use of the
* <code>defaultManifestFile</code>.
*
* @since 2.2
*/
@Parameter(property = "nar.useDefaultManifestFile", defaultValue = "false")
protected boolean useDefaultManifestFile;
@Component
protected MavenProjectHelper projectHelper;
/**
* Whether creating the archive should be forced.
*
*/
@Parameter(property = "nar.forceCreation", defaultValue = "false")
protected boolean forceCreation;
/**
* Classifier to add to the artifact generated. If given, the artifact will
* be an attachment instead.
*
*/
@Parameter(property = "classifier")
protected String classifier;
@Component
protected ArtifactInstaller installer;
@Component
protected ArtifactRepositoryFactory repositoryFactory;
/**
* This only applies if the classifier parameter is used.
*
*/
@Parameter(property = "mdep.failOnMissingClassifierArtifact", defaultValue = "true", required = false)
protected boolean failOnMissingClassifierArtifact = true;
/**
* Comma Separated list of Types to include. Empty String indicates include
* everything (default).
*
*/
@Parameter(property = "includeTypes", required = false)
protected String includeTypes;
/**
* Comma Separated list of Types to exclude. Empty String indicates don't
* exclude anything (default).
*
*/
@Parameter(property = "excludeTypes", required = false)
protected String excludeTypes;
/**
* Scope to include. An Empty string indicates all scopes (default).
*
*/
@Parameter(property = "includeScope", required = false)
protected String includeScope;
/**
* Scope to exclude. An Empty string indicates no scopes (default).
*
*/
@Parameter(property = "excludeScope", required = false)
protected String excludeScope;
/**
* Comma Separated list of Classifiers to include. Empty String indicates
* include everything (default).
*
*/
@Parameter(property = "includeClassifiers", required = false)
protected String includeClassifiers;
/**
* Comma Separated list of Classifiers to exclude. Empty String indicates
* don't exclude anything (default).
*
*/
@Parameter(property = "excludeClassifiers", required = false)
protected String excludeClassifiers;
/**
* Specify classifier to look for. Example: sources
*
*/
@Parameter(property = "classifier", required = false)
protected String copyDepClassifier;
/**
* Specify type to look for when constructing artifact based on classifier.
* Example: java-source,jar,war, nar
*
*/
@Parameter(property = "type", required = false, defaultValue = "nar")
protected String type;
/**
* Comma separated list of Artifact names too exclude.
*
*/
@Parameter(property = "excludeArtifacts", required = false)
protected String excludeArtifactIds;
/**
* Comma separated list of Artifact names to include.
*
*/
@Parameter(property = "includeArtifacts", required = false)
protected String includeArtifactIds;
/**
* Comma separated list of GroupId Names to exclude.
*
*/
@Parameter(property = "excludeArtifacts", required = false)
protected String excludeGroupIds;
/**
* Comma separated list of GroupIds to include.
*
*/
@Parameter(property = "includeGroupIds", required = false)
protected String includeGroupIds;
/**
* Directory to store flag files
*
*/
@Parameter(property = "markersDirectory", required = false, defaultValue = "${project.build.directory}/dependency-maven-plugin-markers")
protected File markersDirectory;
/**
* Overwrite release artifacts
*
*/
@Parameter(property = "overWriteReleases", required = false)
protected boolean overWriteReleases;
/**
* Overwrite snapshot artifacts
*
*/
@Parameter(property = "overWriteSnapshots", required = false)
protected boolean overWriteSnapshots;
/**
* Overwrite artifacts that don't exist or are older than the source.
*
*/
@Parameter(property = "overWriteIfNewer", required = false, defaultValue = "true")
protected boolean overWriteIfNewer;
@Parameter(property = "projectBuildDirectory", required = false, defaultValue = "${project.build.directory}")
protected File projectBuildDirectory;
/**
* Used to look up Artifacts in the remote repository.
*/
@Component
protected ArtifactFactory factory;
/**
* Used to look up Artifacts in the remote repository.
*
*/
@Component
protected ArtifactResolver resolver;
/**
* Artifact collector, needed to resolve dependencies.
*
*/
@Component(role = org.apache.maven.artifact.resolver.ArtifactCollector.class)
protected ArtifactCollector artifactCollector;
@Component(role = org.apache.maven.artifact.metadata.ArtifactMetadataSource.class)
protected ArtifactMetadataSource artifactMetadataSource;
/**
* Location of the local repository.
*
*/
@Parameter(property = "localRepository", required = true, readonly = true)
protected ArtifactRepository local;
/**
* List of Remote Repositories used by the resolver
*
*/
@Parameter(property = "project.remoteArtifactRepositories", required = true, readonly = true)
protected List remoteRepos;
/**
* To look up Archiver/UnArchiver implementations
*
*/
@Component
protected ArchiverManager archiverManager;
/**
* Contains the full list of projects in the reactor.
*
*/
@Parameter(property = "reactorProjects", required = true, readonly = true)
protected List reactorProjects;
/**
* If the plugin should be silent.
*
*/
@Parameter(property = "silent", required = false, defaultValue = "false")
public boolean silent;
/**
* Output absolute filename for resolved artifacts
*
*/
@Parameter(property = "outputAbsoluteArtifactFilename", defaultValue = "false", required = false)
protected boolean outputAbsoluteArtifactFilename;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
copyDependencies();
makeNar();
}
private void copyDependencies() throws MojoExecutionException {
DependencyStatusSets dss = getDependencySets(this.failOnMissingClassifierArtifact);
Set artifacts = dss.getResolvedDependencies();
for (Object artifactObj : artifacts) {
copyArtifact((Artifact) artifactObj);
}
artifacts = dss.getSkippedDependencies();
for (Object artifactOjb : artifacts) {
Artifact artifact = (Artifact) artifactOjb;
getLog().info(artifact.getFile().getName() + " already exists in destination.");
}
}
protected void copyArtifact(Artifact artifact) throws MojoExecutionException {
String destFileName = DependencyUtil.getFormattedFileName(artifact, false);
final File destDir = DependencyUtil.getFormattedOutputDirectory(false, false, false, false, false, getDependenciesDirectory(), artifact);
final File destFile = new File(destDir, destFileName);
copyFile(artifact.getFile(), destFile);
}
protected Artifact getResolvedPomArtifact(Artifact artifact) {
Artifact pomArtifact = this.factory.createArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), "", "pom");
// Resolve the pom artifact using repos
try {
this.resolver.resolve(pomArtifact, this.remoteRepos, this.local);
} catch (ArtifactResolutionException | ArtifactNotFoundException e) {
getLog().info(e.getMessage());
}
return pomArtifact;
}
protected ArtifactsFilter getMarkedArtifactFilter() {
return new DestFileFilter(this.overWriteReleases, this.overWriteSnapshots, this.overWriteIfNewer, false, false, false, false, false, getDependenciesDirectory());
}
protected DependencyStatusSets getDependencySets(boolean stopOnFailure) throws MojoExecutionException {
// add filters in well known order, least specific to most specific
FilterArtifacts filter = new FilterArtifacts();
filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), false));
filter.addFilter(new ScopeFilter(this.includeScope, this.excludeScope));
filter.addFilter(new TypeFilter(this.includeTypes, this.excludeTypes));
filter.addFilter(new ClassifierFilter(this.includeClassifiers, this.excludeClassifiers));
filter.addFilter(new GroupIdFilter(this.includeGroupIds, this.excludeGroupIds));
filter.addFilter(new ArtifactIdFilter(this.includeArtifactIds, this.excludeArtifactIds));
// explicitly filter our nar dependencies
filter.addFilter(new TypeFilter("", "nar"));
// start with all artifacts.
Set artifacts = project.getArtifacts();
// perform filtering
try {
artifacts = filter.filter(artifacts);
} catch (ArtifactFilterException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
// transform artifacts if classifier is set
final DependencyStatusSets status;
if (StringUtils.isNotEmpty(copyDepClassifier)) {
status = getClassifierTranslatedDependencies(artifacts, stopOnFailure);
} else {
status = filterMarkedDependencies(artifacts);
}
return status;
}
protected DependencyStatusSets getClassifierTranslatedDependencies(Set artifacts, boolean stopOnFailure) throws MojoExecutionException {
Set unResolvedArtifacts = new HashSet();
Set resolvedArtifacts = artifacts;
DependencyStatusSets status = new DependencyStatusSets();
// possibly translate artifacts into a new set of artifacts based on the
// classifier and type
// if this did something, we need to resolve the new artifacts
if (StringUtils.isNotEmpty(copyDepClassifier)) {
ArtifactTranslator translator = new ClassifierTypeTranslator(this.copyDepClassifier, this.type, this.factory);
artifacts = translator.translate(artifacts, getLog());
status = filterMarkedDependencies(artifacts);
// the unskipped artifacts are in the resolved set.
artifacts = status.getResolvedDependencies();
// resolve the rest of the artifacts
ArtifactsResolver artifactsResolver = new DefaultArtifactsResolver(this.resolver, this.local,
this.remoteRepos, stopOnFailure);
resolvedArtifacts = artifactsResolver.resolve(artifacts, getLog());
// calculate the artifacts not resolved.
unResolvedArtifacts.addAll(artifacts);
unResolvedArtifacts.removeAll(resolvedArtifacts);
}
// return a bean of all 3 sets.
status.setResolvedDependencies(resolvedArtifacts);
status.setUnResolvedDependencies(unResolvedArtifacts);
return status;
}
protected DependencyStatusSets filterMarkedDependencies(Set artifacts) throws MojoExecutionException {
// remove files that have markers already
FilterArtifacts filter = new FilterArtifacts();
filter.clearFilters();
filter.addFilter(getMarkedArtifactFilter());
Set unMarkedArtifacts;
try {
unMarkedArtifacts = filter.filter(artifacts);
} catch (ArtifactFilterException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
// calculate the skipped artifacts
Set skippedArtifacts = new HashSet();
skippedArtifacts.addAll(artifacts);
skippedArtifacts.removeAll(unMarkedArtifacts);
return new DependencyStatusSets(unMarkedArtifacts, null, skippedArtifacts);
}
protected void copyFile(File artifact, File destFile) throws MojoExecutionException {
try {
getLog().info("Copying " + (this.outputAbsoluteArtifactFilename ? artifact.getAbsolutePath() : artifact.getName()) + " to " + destFile);
FileUtils.copyFile(artifact, destFile);
} catch (Exception e) {
throw new MojoExecutionException("Error copying artifact from " + artifact + " to " + destFile, e);
}
}
private File getClassesDirectory() {
final File outputDirectory = projectBuildDirectory;
return new File(outputDirectory, "classes");
}
private File getDependenciesDirectory() {
return new File(getClassesDirectory(), "META-INF/bundled-dependencies");
}
private void makeNar() throws MojoExecutionException {
File narFile = createArchive();
if (classifier != null) {
projectHelper.attachArtifact(project, "nar", classifier, narFile);
} else {
project.getArtifact().setFile(narFile);
}
}
public File createArchive() throws MojoExecutionException {
final File outputDirectory = projectBuildDirectory;
File narFile = getNarFile(outputDirectory, finalName, classifier);
MavenArchiver archiver = new MavenArchiver();
archiver.setArchiver(jarArchiver);
archiver.setOutputFile(narFile);
archive.setForced(forceCreation);
try {
File contentDirectory = getClassesDirectory();
if (!contentDirectory.exists()) {
getLog().warn("NAR will be empty - no content was marked for inclusion!");
} else {
archiver.getArchiver().addDirectory(contentDirectory, getIncludes(), getExcludes());
}
File existingManifest = defaultManifestFile;
if (useDefaultManifestFile && existingManifest.exists() && archive.getManifestFile() == null) {
getLog().info("Adding existing MANIFEST to archive. Found under: " + existingManifest.getPath());
archive.setManifestFile(existingManifest);
}
// automatically add the artifact id to the manifest
archive.addManifestEntry("Nar-Id", project.getArtifactId());
// look for a nar dependency
String narDependency = getNarDependency();
if (narDependency != null) {
archive.addManifestEntry("Nar-Dependency-Id", narDependency);
}
archiver.createArchive(session, project, archive);
return narFile;
} catch (ArchiverException | MojoExecutionException | ManifestException | IOException | DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Error assembling NAR", e);
}
}
private String[] getIncludes() {
if (includes != null && includes.length > 0) {
return includes;
}
return DEFAULT_INCLUDES;
}
private String[] getExcludes() {
if (excludes != null && excludes.length > 0) {
return excludes;
}
return DEFAULT_EXCLUDES;
}
protected File getNarFile(File basedir, String finalName, String classifier) {
if (classifier == null) {
classifier = "";
} else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) {
classifier = "-" + classifier;
}
return new File(basedir, finalName + classifier + ".nar");
}
private String getNarDependency() throws MojoExecutionException {
String narDependency = null;
// get nar dependencies
FilterArtifacts filter = new FilterArtifacts();
filter.addFilter(new TypeFilter("nar", ""));
// start with all artifacts.
Set artifacts = project.getArtifacts();
// perform filtering
try {
artifacts = filter.filter(artifacts);
} catch (ArtifactFilterException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
// ensure there is a single nar dependency
if (artifacts.size() > 1) {
throw new MojoExecutionException("Each NAR represents a ClassLoader. A NAR dependency allows that NAR's ClassLoader to be "
+ "used as the parent of this NAR's ClassLoader. As a result, only a single NAR dependency is allowed.");
} else if (artifacts.size() == 1) {
final Artifact artifact = (Artifact) artifacts.iterator().next();
narDependency = artifact.getArtifactId();
}
return narDependency;
}
}