diff --git a/src/main/java/org/apache/nifi/NarMojo.java b/src/main/java/org/apache/nifi/NarMojo.java index 56f059d..0533598 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,8 @@ 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 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 +480,13 @@ public class NarMojo extends AbstractMojo { @Parameter(property = "skipDocGeneration", defaultValue = "false") protected boolean skipDocGeneration; + /** + * 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 = "manifestEntries") + protected Map manifestEntries; + /** * The {@link RepositorySystemSession} used for obtaining the local and remote artifact repositories. */ @@ -1266,6 +1276,13 @@ private NarResult createArchive() throws MojoExecutionException { archive.addManifestEntry("Clone-During-Instance-Class-Loading", String.valueOf(cloneDuringInstanceClassLoading)); + if (manifestEntries != null) { + validateManifestEntryKeys(manifestEntries); + for (final Map.Entry entry : manifestEntries.entrySet()) { + archive.addManifestEntry(entry.getKey(), entry.getValue()); + } + } + archiver.createArchive(session, project, archive); return new NarResult(narFile, extensionDocsFile); } catch (ArchiverException | MojoExecutionException | ManifestException | IOException | DependencyResolutionRequiredException e) { @@ -1273,6 +1290,18 @@ private NarResult createArchive() throws MojoExecutionException { } } + static void validateManifestEntryKeys(final Map entries) throws MojoExecutionException { + for (final String key : entries.keySet()) { + if (key == null || key.isEmpty()) { + throw new MojoExecutionException("Manifest entry key must not be null or empty"); + } + if (!VALID_MANIFEST_KEY_PATTERN.matcher(key).matches()) { + throw new MojoExecutionException( + "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..1d20a7f --- /dev/null +++ b/src/test/java/org/apache/nifi/NarMojoTest.java @@ -0,0 +1,87 @@ +/* + * 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 testValidateManifestEntryKeysAcceptsValidKeys() 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.validateManifestEntryKeys(entries); + } + + @Test + void testValidateManifestEntryKeysRejectsEmptyKey() { + final Map entries = new HashMap<>(); + entries.put("", "some-value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key must not be null or empty", exception.getMessage()); + } + + @Test + void testValidateManifestEntryKeysRejectsKeyWithSpaces() { + final Map entries = new HashMap<>(); + entries.put("my key", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'my key' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateManifestEntryKeysRejectsKeyWithColon() { + final Map entries = new HashMap<>(); + entries.put("key:name", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'key:name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateManifestEntryKeysRejectsKeyWithDot() { + final Map entries = new HashMap<>(); + entries.put("key.name", "value"); + + final MojoExecutionException exception = assertThrows(MojoExecutionException.class, + () -> NarMojo.validateManifestEntryKeys(entries)); + assertEquals("Manifest entry key 'key.name' contains invalid characters. Keys must match [A-Za-z0-9_-]+", exception.getMessage()); + } + + @Test + void testValidateManifestEntryKeysAcceptsEmptyMap() throws MojoExecutionException { + NarMojo.validateManifestEntryKeys(Map.of()); + } +}