org.apache.nifi.NarMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nifi-nar-maven-plugin Show documentation
Show all versions of nifi-nar-maven-plugin Show documentation
Apache NiFi Nar Maven Plugin
/*
* 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 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.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
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.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
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.Component;
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.project.MavenProjectHelper;
import org.apache.maven.project.ProjectBuilder;
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.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.nifi.extension.definition.ExtensionDefinition;
import org.apache.nifi.extension.definition.ExtensionType;
import org.apache.nifi.extension.definition.ServiceAPIDefinition;
import org.apache.nifi.extension.definition.extraction.ExtensionClassLoader;
import org.apache.nifi.extension.definition.extraction.ExtensionClassLoaderFactory;
import org.apache.nifi.extension.definition.extraction.ExtensionDefinitionFactory;
import org.apache.nifi.extension.definition.extraction.StandardServiceAPIDefinition;
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;
import org.eclipse.aether.RepositorySystemSession;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
/**
* 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 = true, requiresDependencyResolution = ResolutionScope.RUNTIME)
public class NarMojo extends AbstractMojo {
private static final String CONTROLLER_SERVICE_CLASS_NAME = "org.apache.nifi.controller.ControllerService";
private static final String DOCUMENTATION_WRITER_CLASS_NAME = "org.apache.nifi.documentation.xml.XmlDocumentationWriter";
private static final String[] DEFAULT_EXCLUDES = new String[]{"**/package.html"};
private static final String[] DEFAULT_INCLUDES = new String[]{"**/**"};
private static final String BUILD_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
/**
* 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 the
* documentation for Maven Archiver.
*
*/
@Parameter(property = "archive")
protected final MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
/**
* Path to the default MANIFEST file to use. It will be used if
* useDefaultManifestFile
is set to true
.
*
*/
@Parameter(property = "defaultManifestFiles", defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF", readonly = true, required = true)
protected File defaultManifestFile;
/**
* Set this to true
to enable the use of the
* defaultManifestFile
.
*
* @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;
/**
* The dependency tree builder to use for verbose output.
*/
@Component
private DependencyGraphBuilder dependencyGraphBuilder;
/**
* *
* The {@link ArtifactHandlerManager} into which any extension {@link ArtifactHandler} instances should have been injected when the extensions were loaded.
*/
@Component
private ArtifactHandlerManager artifactHandlerManager;
/**
* Output absolute filename for resolved artifacts
*
*/
@Parameter(property = "outputAbsoluteArtifactFilename", defaultValue = "false", required = false)
protected boolean outputAbsoluteArtifactFilename;
/* The values to use for populating the Nar-Group, Nar-Id, and Nar-Version in the MANIFEST file. By default
* these values will be set to the standard Maven project equivalents, but they may be overridden through properties.
*
* For example if the pom.xml for the nifi-test-nar contained the following:
*
* org.apache.nifi
* nifi-test-nar
* 1.0
*
*
* org.apache.nifi.overridden
* nifi-overridden-test-nar
* 2.0
*
*
* It would produce a MANIFEST with:
*
* Nar-Id: nifi-overridden-test-nar
* Nar-Group: org.apache.nifi.overridden
* Nar-Version: 2.0
*
*/
@Parameter(property = "narGroup", defaultValue = "${project.groupId}", required = true)
protected String narGroup;
@Parameter(property = "narId", defaultValue = "${project.artifactId}", required = true)
protected String narId;
@Parameter(property = "narVersion", defaultValue = "${project.version}", required = true)
protected String narVersion;
@Parameter(property = "narDependencyGroup", required = false)
protected String narDependencyGroup = null;
@Parameter(property = "narDependencyId", required = false)
protected String narDependencyId = null;
@Parameter(property = "narDependencyVersion", required = false)
protected String narDependencyVersion = null;
/**
* Build info to be populated in MANIFEST.
*/
@Parameter(property = "buildTag", defaultValue = "${project.scm.tag}", required = false)
protected String buildTag;
@Parameter(property = "buildBranch", defaultValue = "${buildBranch}", required = false)
protected String buildBranch;
@Parameter(property = "buildRevision", defaultValue = "${buildRevision}", required = false)
protected String buildRevision;
/**
* Allows a NAR to specify if it's resources should be cloned when a component that depends on this NAR
* is performing class loader isolation.
*/
@Parameter(property = "cloneDuringInstanceClassLoading", defaultValue = "false", required = false)
protected boolean cloneDuringInstanceClassLoading;
@Parameter(property = "enforceDocGeneration", defaultValue = "false", required = false)
protected boolean enforceDocGeneration;
/**
* The {@link RepositorySystemSession} used for obtaining the local and remote artifact repositories.
*/
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
private RepositorySystemSession repoSession;
/**
* The {@link ProjectBuilder} used to generate the {@link MavenProject} for the nar artifact the dependency tree is being generated for.
*/
@Component
private ProjectBuilder projectBuilder;
/**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
* yyyy-MM-dd'T'HH:mm:ssXXX
or as an int representing seconds since the epoch (like
* SOURCE_DATE_EPOCH).
*
* @since 3.2.3
*/
@Parameter( defaultValue = "${project.build.outputTimestamp}" )
private String outputTimestamp;
@Override
public void execute() throws MojoExecutionException {
copyDependencies();
try {
generateDocumentation();
} catch (final Throwable t) { // Catch Throwable in case a linkage error such as NoClassDefFoundError occurs
if (enforceDocGeneration) {
getLog().error("Could not generate extensions' documentation", t);
throw t;
} else {
getLog().warn("Could not generate extensions' documentation", t);
}
}
makeNar();
}
private File getExtensionsDocumentationFile() {
final File directory = new File(projectBuildDirectory, "META-INF/docs");
return new File(directory, "extension-manifest.xml");
}
private void generateDocumentation() throws MojoExecutionException {
getLog().info("Generating documentation for NiFi extensions in the NAR...");
// Create the ClassLoader for the NAR
final ExtensionClassLoaderFactory classLoaderFactory = createClassLoaderFactory();
final ExtensionClassLoader extensionClassLoader;
try {
extensionClassLoader = classLoaderFactory.createExtensionClassLoader();
} catch (final Exception e) {
if (enforceDocGeneration) {
throw new MojoExecutionException("Failed to create Extension Documentation", e);
} else {
if (getLog().isDebugEnabled()) {
getLog().debug("Unable to create a ClassLoader for documenting extensions. If this NAR contains any NiFi Extensions, those extensions will not be documented.", e);
} else {
getLog().warn("Unable to create a ClassLoader for documenting extensions. If this NAR contains any NiFi Extensions, those extensions will not be documented. " +
"Enable mvn DEBUG output for more information (mvn -X).");
}
return;
}
}
final File docsFile = getExtensionsDocumentationFile();
createDirectory(docsFile.getParentFile());
final File additionalDetailsDir = new File(docsFile.getParentFile(), "additional-details");
createDirectory(additionalDetailsDir);
try (final OutputStream out = new FileOutputStream(docsFile)) {
final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(out, "UTF-8");
try {
xmlWriter.writeStartElement("extensionManifest");
// Write current NAR information
writeXmlTag(xmlWriter, "groupId", narGroup);
writeXmlTag(xmlWriter, "artifactId", narId);
writeXmlTag(xmlWriter, "version", narVersion);
// Write parent NAR information
final NarDependency narDependency = getNarDependency();
if (narDependency != null) {
xmlWriter.writeStartElement("parentNar");
writeXmlTag(xmlWriter, "groupId", notEmpty(this.narDependencyGroup) ? this.narDependencyGroup : narDependency.getGroupId());
writeXmlTag(xmlWriter, "artifactId", notEmpty(this.narDependencyId) ? this.narDependencyId : narDependency.getArtifactId());
writeXmlTag(xmlWriter, "version", notEmpty(this.narDependencyVersion) ? this.narDependencyVersion : narDependency.getVersion());
xmlWriter.writeEndElement();
}
// Write system API version
final String nifiApiVersion = extensionClassLoader.getNiFiApiVersion();
xmlWriter.writeStartElement("systemApiVersion");
xmlWriter.writeCharacters(nifiApiVersion);
xmlWriter.writeEndElement();
// Write build info
xmlWriter.writeStartElement("buildInfo");
if (notEmpty(buildTag)) {
writeXmlTag(xmlWriter, "tag", buildTag);
}
if (notEmpty(buildBranch)) {
writeXmlTag(xmlWriter, "branch", buildBranch);
}
if (notEmpty(buildRevision)) {
writeXmlTag(xmlWriter, "revision", buildRevision);
}
xmlWriter.writeEndElement();
// Write extensions
xmlWriter.writeStartElement("extensions");
final Class> docWriterClass;
try {
docWriterClass = Class.forName(DOCUMENTATION_WRITER_CLASS_NAME, false, extensionClassLoader);
} catch (ClassNotFoundException e) {
getLog().warn("Cannot locate class " + DOCUMENTATION_WRITER_CLASS_NAME + ", so no documentation will be generated for the extensions in this NAR");
return;
}
getLog().debug("Creating Extension Definition Factory for NiFi API version " + nifiApiVersion);
final ExtensionDefinitionFactory extensionDefinitionFactory = new ExtensionDefinitionFactory(extensionClassLoader);
final ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(extensionClassLoader);
final Set processorDefinitions = extensionDefinitionFactory.discoverExtensions(ExtensionType.PROCESSOR);
writeDocumentation(processorDefinitions, extensionClassLoader, docWriterClass, xmlWriter, additionalDetailsDir);
final Set controllerServiceDefinitions = extensionDefinitionFactory.discoverExtensions(ExtensionType.CONTROLLER_SERVICE);
writeDocumentation(controllerServiceDefinitions, extensionClassLoader, docWriterClass, xmlWriter, additionalDetailsDir);
final Set reportingTaskDefinitions = extensionDefinitionFactory.discoverExtensions(ExtensionType.REPORTING_TASK);
writeDocumentation(reportingTaskDefinitions, extensionClassLoader, docWriterClass, xmlWriter, additionalDetailsDir);
} finally {
if (currentContextClassLoader != null) {
Thread.currentThread().setContextClassLoader(currentContextClassLoader);
}
}
xmlWriter.writeEndElement();
xmlWriter.writeEndElement();
} finally {
xmlWriter.close();
}
} catch (final Exception ioe) {
throw new MojoExecutionException("Failed to create Extension Documentation", ioe);
}
}
private void writeXmlTag(final XMLStreamWriter xmlWriter, final String tagName, final String value) throws XMLStreamException {
xmlWriter.writeStartElement(tagName);
xmlWriter.writeCharacters(value);
xmlWriter.writeEndElement();
}
private void writeDocumentation(final Set extensionDefinitions, final ExtensionClassLoader classLoader,
final Class> docWriterClass, final XMLStreamWriter xmlWriter, final File additionalDetailsDir)
throws InvocationTargetException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
final Set sorted = new TreeSet<>(new Comparator() {
public int compare(ExtensionDefinition e1, ExtensionDefinition e2) {
return e1.getExtensionName().compareTo(e2.getExtensionName());
}
});
sorted.addAll(extensionDefinitions);
for (final ExtensionDefinition definition : sorted) {
writeDocumentation(definition, classLoader, docWriterClass, xmlWriter);
}
final Set extensionNames = sorted.stream()
.map(ExtensionDefinition::getExtensionName)
.collect(Collectors.toSet());
try {
writeAdditionalDetails(classLoader, extensionNames, additionalDetailsDir);
} catch (final Exception e) {
throw new IOException("Unable to extract Additional Details", e);
}
}
private void writeDocumentation(final ExtensionDefinition extensionDefinition, final ExtensionClassLoader classLoader,
final Class> docWriterClass, final XMLStreamWriter xmlWriter)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, IOException {
getLog().debug("Generating documentation for " + extensionDefinition.getExtensionName() + " using ClassLoader:\n" + classLoader.toTree());
final Object docWriter = docWriterClass.getConstructor(XMLStreamWriter.class).newInstance(xmlWriter);
final Class> configurableComponentClass = Class.forName("org.apache.nifi.components.ConfigurableComponent", false, classLoader);
final Class> extensionClass = Class.forName(extensionDefinition.getExtensionName(), false, classLoader);
final Object extensionInstance = extensionClass.newInstance();
final Method initMethod = docWriterClass.getMethod("initialize", configurableComponentClass);
initMethod.invoke(docWriter, extensionInstance);
final Map propertyServiceDefinitions = getRequiredServiceDefinitions(extensionClass, extensionInstance);
final Set providedServiceDefinitions = extensionDefinition.getProvidedServiceAPIs();
if ((providedServiceDefinitions == null || providedServiceDefinitions.isEmpty())
&& (propertyServiceDefinitions == null || propertyServiceDefinitions.isEmpty())) {
final Method writeMethod = docWriterClass.getMethod("write", configurableComponentClass);
writeMethod.invoke(docWriter, extensionInstance);
} else {
final Class> serviceApiClass = Class.forName("org.apache.nifi.documentation.StandardServiceAPI", false, classLoader);
final List