From 929ad4381ffa12bbf72457b9ec5890ee49cd9420 Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Fri, 6 Mar 2026 11:49:48 +0100 Subject: [PATCH 1/2] NIFI-15676 - Add support for custom entries in NAR Manifest --- src/main/java/org/apache/nifi/NarMojo.java | 30 ++++++ .../java/org/apache/nifi/NarMojoTest.java | 92 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/test/java/org/apache/nifi/NarMojoTest.java diff --git a/src/main/java/org/apache/nifi/NarMojo.java b/src/main/java/org/apache/nifi/NarMojo.java index 56f059d..defa7cd 100644 --- a/src/main/java/org/apache/nifi/NarMojo.java +++ b/src/main/java/org/apache/nifi/NarMojo.java @@ -104,6 +104,7 @@ import java.util.TreeSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -122,6 +123,9 @@ public class NarMojo extends AbstractMojo { private static final String DOCUMENTATION_WRITER_CLASS_NAME = "org.apache.nifi.documentation.xml.XmlDocumentationWriter"; private static final String CONNECTOR_DOCUMENTATION_WRITER_CLASS_NAME = "org.apache.nifi.documentation.xml.XmlConnectorDocumentationWriter"; + static final String NAR_CUSTOM_PREFIX = "Nar-Custom-"; + static final Pattern VALID_MANIFEST_KEY_PATTERN = Pattern.compile("[A-Za-z0-9_-]+"); + private static final String[] DEFAULT_EXCLUDES = new String[]{"**/package.html"}; private static final String[] DEFAULT_INCLUDES = new String[]{"**/**"}; @@ -477,6 +481,13 @@ public class NarMojo extends AbstractMojo { @Parameter(property = "skipDocGeneration", defaultValue = "false") protected boolean skipDocGeneration; + /** + * Custom key/value pairs to include in the NAR manifest with a {@code Nar-Custom-} prefix. + * Keys must match the JAR manifest attribute name specification: {@code [A-Za-z0-9_-]+}. + */ + @Parameter(property = "customManifestEntries") + protected Map customManifestEntries; + /** * The {@link RepositorySystemSession} used for obtaining the local and remote artifact repositories. */ @@ -1266,6 +1277,13 @@ private NarResult createArchive() throws MojoExecutionException { archive.addManifestEntry("Clone-During-Instance-Class-Loading", String.valueOf(cloneDuringInstanceClassLoading)); + if (customManifestEntries != null) { + validateCustomManifestEntryKeys(customManifestEntries); + for (final Map.Entry entry : customManifestEntries.entrySet()) { + archive.addManifestEntry(NAR_CUSTOM_PREFIX + entry.getKey(), entry.getValue()); + } + } + archiver.createArchive(session, project, archive); return new NarResult(narFile, extensionDocsFile); } catch (ArchiverException | MojoExecutionException | ManifestException | IOException | DependencyResolutionRequiredException e) { @@ -1273,6 +1291,18 @@ private NarResult createArchive() throws MojoExecutionException { } } + static void validateCustomManifestEntryKeys(final Map entries) throws MojoExecutionException { + for (final String key : entries.keySet()) { + if (key == null || key.isEmpty()) { + throw new MojoExecutionException("Custom manifest entry key must not be null or empty"); + } + if (!VALID_MANIFEST_KEY_PATTERN.matcher(key).matches()) { + throw new MojoExecutionException( + "Custom manifest entry key '%s' contains invalid characters. Keys must match [A-Za-z0-9_-]+".formatted(key)); + } + } + } + private boolean notEmpty(String value) { return value != null && !value.isEmpty(); } diff --git a/src/test/java/org/apache/nifi/NarMojoTest.java b/src/test/java/org/apache/nifi/NarMojoTest.java new file mode 100644 index 0000000..8a74e27 --- /dev/null +++ b/src/test/java/org/apache/nifi/NarMojoTest.java @@ -0,0 +1,92 @@ +/* + * 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.plugin.MojoExecutionException; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class NarMojoTest { + + @Test + void testValidateCustomManifestEntryKeysAcceptsValidKeys() throws MojoExecutionException { + final Map entries = new LinkedHashMap<>(); + entries.put("vendor", "Acme Corp"); + entries.put("support-url", "https://acme.corp/support"); + entries.put("build_number", "42"); + entries.put("Tier-1", "gold"); + entries.put("ABC123", "test"); + + NarMojo.validateCustomManifestEntryKeys(entries); + } + + @Test + void testValidateCustomManifestEntryKeysRejectsEmptyKey() { + final Map entries = new HashMap<>(); + entries.put("", "some-value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateCustomManifestEntryKeys(entries)); + assertEquals("Custom manifest entry key must not be null or empty", exception.getMessage()); + } + + @Test + void testValidateCustomManifestEntryKeysRejectsKeyWithSpaces() { + final Map entries = new HashMap<>(); + entries.put("my key", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateCustomManifestEntryKeys(entries)); + assertEquals("Custom manifest entry key 'my key' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateCustomManifestEntryKeysRejectsKeyWithColon() { + final Map entries = new HashMap<>(); + entries.put("key:name", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateCustomManifestEntryKeys(entries)); + assertEquals("Custom manifest entry key 'key:name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateCustomManifestEntryKeysRejectsKeyWithDot() { + final Map entries = new HashMap<>(); + entries.put("key.name", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateCustomManifestEntryKeys(entries)); + assertEquals("Custom manifest entry key 'key.name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateCustomManifestEntryKeysAcceptsEmptyMap() throws MojoExecutionException { + NarMojo.validateCustomManifestEntryKeys(Map.of()); + } + + @Test + void testNarCustomPrefixValue() { + assertEquals("Nar-Custom-", NarMojo.NAR_CUSTOM_PREFIX); + } +} From 5b87e6d0683b6c5675c98c4274791368af835692 Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Fri, 6 Mar 2026 19:08:40 +0100 Subject: [PATCH 2/2] review --- src/main/java/org/apache/nifi/NarMojo.java | 21 +++++------ .../java/org/apache/nifi/NarMojoTest.java | 37 ++++++++----------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/apache/nifi/NarMojo.java b/src/main/java/org/apache/nifi/NarMojo.java index defa7cd..0533598 100644 --- a/src/main/java/org/apache/nifi/NarMojo.java +++ b/src/main/java/org/apache/nifi/NarMojo.java @@ -123,7 +123,6 @@ public class NarMojo extends AbstractMojo { private static final String DOCUMENTATION_WRITER_CLASS_NAME = "org.apache.nifi.documentation.xml.XmlDocumentationWriter"; private static final String CONNECTOR_DOCUMENTATION_WRITER_CLASS_NAME = "org.apache.nifi.documentation.xml.XmlConnectorDocumentationWriter"; - static final String NAR_CUSTOM_PREFIX = "Nar-Custom-"; static final Pattern VALID_MANIFEST_KEY_PATTERN = Pattern.compile("[A-Za-z0-9_-]+"); private static final String[] DEFAULT_EXCLUDES = new String[]{"**/package.html"}; @@ -482,11 +481,11 @@ public class NarMojo extends AbstractMojo { protected boolean skipDocGeneration; /** - * Custom key/value pairs to include in the NAR manifest with a {@code Nar-Custom-} prefix. + * Additional key/value pairs to include in the NAR manifest. * Keys must match the JAR manifest attribute name specification: {@code [A-Za-z0-9_-]+}. */ - @Parameter(property = "customManifestEntries") - protected Map customManifestEntries; + @Parameter(property = "manifestEntries") + protected Map manifestEntries; /** * The {@link RepositorySystemSession} used for obtaining the local and remote artifact repositories. @@ -1277,10 +1276,10 @@ private NarResult createArchive() throws MojoExecutionException { archive.addManifestEntry("Clone-During-Instance-Class-Loading", String.valueOf(cloneDuringInstanceClassLoading)); - if (customManifestEntries != null) { - validateCustomManifestEntryKeys(customManifestEntries); - for (final Map.Entry entry : customManifestEntries.entrySet()) { - archive.addManifestEntry(NAR_CUSTOM_PREFIX + entry.getKey(), entry.getValue()); + if (manifestEntries != null) { + validateManifestEntryKeys(manifestEntries); + for (final Map.Entry entry : manifestEntries.entrySet()) { + archive.addManifestEntry(entry.getKey(), entry.getValue()); } } @@ -1291,14 +1290,14 @@ private NarResult createArchive() throws MojoExecutionException { } } - static void validateCustomManifestEntryKeys(final Map entries) throws MojoExecutionException { + static void validateManifestEntryKeys(final Map entries) throws MojoExecutionException { for (final String key : entries.keySet()) { if (key == null || key.isEmpty()) { - throw new MojoExecutionException("Custom manifest entry key must not be null or empty"); + throw new MojoExecutionException("Manifest entry key must not be null or empty"); } if (!VALID_MANIFEST_KEY_PATTERN.matcher(key).matches()) { throw new MojoExecutionException( - "Custom manifest entry key '%s' contains invalid characters. Keys must match [A-Za-z0-9_-]+".formatted(key)); + "Manifest entry key '%s' contains invalid characters. Keys must match [A-Za-z0-9_-]+".formatted(key)); } } } diff --git a/src/test/java/org/apache/nifi/NarMojoTest.java b/src/test/java/org/apache/nifi/NarMojoTest.java index 8a74e27..1d20a7f 100644 --- a/src/test/java/org/apache/nifi/NarMojoTest.java +++ b/src/test/java/org/apache/nifi/NarMojoTest.java @@ -29,7 +29,7 @@ class NarMojoTest { @Test - void testValidateCustomManifestEntryKeysAcceptsValidKeys() throws MojoExecutionException { + void testValidateManifestEntryKeysAcceptsValidKeys() throws MojoExecutionException { final Map entries = new LinkedHashMap<>(); entries.put("vendor", "Acme Corp"); entries.put("support-url", "https://acme.corp/support"); @@ -37,56 +37,51 @@ void testValidateCustomManifestEntryKeysAcceptsValidKeys() throws MojoExecutionE entries.put("Tier-1", "gold"); entries.put("ABC123", "test"); - NarMojo.validateCustomManifestEntryKeys(entries); + NarMojo.validateManifestEntryKeys(entries); } @Test - void testValidateCustomManifestEntryKeysRejectsEmptyKey() { + void testValidateManifestEntryKeysRejectsEmptyKey() { final Map entries = new HashMap<>(); entries.put("", "some-value"); final MojoExecutionException exception = assertThrows(MojoExecutionException.class, - () -> NarMojo.validateCustomManifestEntryKeys(entries)); - assertEquals("Custom manifest entry key must not be null or empty", exception.getMessage()); + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key must not be null or empty", exception.getMessage()); } @Test - void testValidateCustomManifestEntryKeysRejectsKeyWithSpaces() { + void testValidateManifestEntryKeysRejectsKeyWithSpaces() { final Map entries = new HashMap<>(); entries.put("my key", "value"); final MojoExecutionException exception = assertThrows(MojoExecutionException.class, - () -> NarMojo.validateCustomManifestEntryKeys(entries)); - assertEquals("Custom manifest entry key 'my key' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'my key' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); } @Test - void testValidateCustomManifestEntryKeysRejectsKeyWithColon() { + void testValidateManifestEntryKeysRejectsKeyWithColon() { final Map entries = new HashMap<>(); entries.put("key:name", "value"); final MojoExecutionException exception = assertThrows(MojoExecutionException.class, - () -> NarMojo.validateCustomManifestEntryKeys(entries)); - assertEquals("Custom manifest entry key 'key:name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'key:name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); } @Test - void testValidateCustomManifestEntryKeysRejectsKeyWithDot() { + void testValidateManifestEntryKeysRejectsKeyWithDot() { final Map entries = new HashMap<>(); entries.put("key.name", "value"); final MojoExecutionException exception = assertThrows(MojoExecutionException.class, - () -> NarMojo.validateCustomManifestEntryKeys(entries)); - assertEquals("Custom manifest entry key 'key.name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'key.name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); } @Test - void testValidateCustomManifestEntryKeysAcceptsEmptyMap() throws MojoExecutionException { - NarMojo.validateCustomManifestEntryKeys(Map.of()); - } - - @Test - void testNarCustomPrefixValue() { - assertEquals("Nar-Custom-", NarMojo.NAR_CUSTOM_PREFIX); + void testValidateManifestEntryKeysAcceptsEmptyMap() throws MojoExecutionException { + NarMojo.validateManifestEntryKeys(Map.of()); } }