NIFI-12392 Corrected Component Documentation NAR Unpacking

- Corrected component documentation directory comparison for finding bundled documentation in a JAR
- Added unit test for unpacking of component documentation from JAR included in NAR

Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com>

This closes #8052.
This commit is contained in:
exceptionfactory 2023-11-19 22:25:48 -06:00 committed by Pierre Villard
parent e81d7254ec
commit d5c7fcb5dd
No known key found for this signature in database
GPG Key ID: F92A93B30C07C6D5
2 changed files with 100 additions and 7 deletions

View File

@ -16,13 +16,17 @@
*/
package org.apache.nifi.nar;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.util.NiFiProperties;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@ -36,10 +40,14 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Stream;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -47,6 +55,28 @@ public class NarUnpackerTest {
private static final String PROPERTIES_PATH = "/NarUnpacker/conf/nifi.properties";
private static final String GROUP_ID = "org.apache.nifi";
private static final String ARTIFACT_ID = "nifi-nar";
private static final String VERSION = "1.0.0";
private static final String DOCS_DIR = "docs";
private static final String COMPONENT_JAR = String.format("component-%s.jar", VERSION);
private static final String PROCESSOR = "org.apache.nifi.processors.UnpackNar";
private static final String PROCESSOR_PATH = "META-INF/services/org.apache.nifi.processor.Processor";
private static final String COMPONENT_DOCS_PATH = String.format("docs/%s/", PROCESSOR);
private static final String ADDITIONAL_DETAILS_HTML = "additionalDetails.html";
private static final String ADDITIONAL_DETAILS_PATH = String.format("docs/%s/%s", PROCESSOR, ADDITIONAL_DETAILS_HTML);
private static final String HTML = "<html></html>";
@BeforeAll
public static void copyResources() throws IOException {
final Path sourcePath = Paths.get("./src/test/resources");
@ -174,6 +204,66 @@ public class NarUnpackerTest {
assertNull(extensionMapping);
}
@Test
public void testMapExtensionNoDependencies(@TempDir final Path tempDir) throws IOException {
final File unpackedNarDir = tempDir.resolve(ARTIFACT_ID).toFile();
final File docsDir = tempDir.resolve(DOCS_DIR).toFile();
final BundleCoordinate bundleCoordinate = new BundleCoordinate(GROUP_ID, ARTIFACT_ID, VERSION);
final ExtensionMapping extensionMapping = new ExtensionMapping();
NarUnpacker.mapExtension(unpackedNarDir, bundleCoordinate, docsDir, extensionMapping);
assertNull(docsDir.listFiles());
}
@Test
public void testMapExtensionAdditionalDetails(@TempDir final Path tempDir) throws IOException {
final File unpackedNarDir = tempDir.resolve(ARTIFACT_ID).toFile();
final File docsDir = tempDir.resolve(DOCS_DIR).toFile();
final BundleCoordinate bundleCoordinate = new BundleCoordinate(GROUP_ID, ARTIFACT_ID, VERSION);
final ExtensionMapping extensionMapping = new ExtensionMapping();
extensionMapping.addProcessor(bundleCoordinate, PROCESSOR);
final Path unpackedNarDependenciesDir = unpackedNarDir.toPath().resolve(NarUnpacker.BUNDLED_DEPENDENCIES_DIRECTORY);
assertTrue(unpackedNarDependenciesDir.toFile().mkdirs());
final File componentJar = unpackedNarDependenciesDir.resolve(COMPONENT_JAR).toFile();
writeComponentJar(componentJar);
NarUnpacker.mapExtension(unpackedNarDir, bundleCoordinate, docsDir, extensionMapping);
final File[] documentationFiles = docsDir.listFiles();
assertNotNull(documentationFiles, "Documentation Files not found");
final Path expectedRelativePath = Paths.get(GROUP_ID, ARTIFACT_ID, VERSION, PROCESSOR, ADDITIONAL_DETAILS_HTML);
final Path documentationFilePath = docsDir.toPath().resolve(expectedRelativePath);
assertTrue(Files.exists(documentationFilePath), "Documentation File not found");
final byte[] documentationBytes = Files.readAllBytes(documentationFilePath);
assertArrayEquals(HTML.getBytes(StandardCharsets.UTF_8), documentationBytes);
}
private void writeComponentJar(final File componentJar) throws IOException {
try (JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(componentJar))) {
final JarEntry processorJarEntry = new JarEntry(PROCESSOR_PATH);
jarOutputStream.putNextEntry(processorJarEntry);
jarOutputStream.write(PROCESSOR.getBytes(StandardCharsets.UTF_8));
jarOutputStream.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8));
jarOutputStream.closeEntry();
final JarEntry docDirectoryEntry = new JarEntry(COMPONENT_DOCS_PATH);
jarOutputStream.putNextEntry(docDirectoryEntry);
jarOutputStream.closeEntry();
final JarEntry docJarEntry = new JarEntry(ADDITIONAL_DETAILS_PATH);
jarOutputStream.putNextEntry(docJarEntry);
jarOutputStream.write(HTML.getBytes(StandardCharsets.UTF_8));
jarOutputStream.closeEntry();
}
}
private NiFiProperties loadSpecifiedProperties(final Map<String, String> others) {
String filePath;
try {

View File

@ -61,6 +61,8 @@ public final class NarUnpacker {
private static final String BUNDLED_DEPENDENCIES_PREFIX = "META-INF/bundled-dependencies";
private static final String JAR_DOCUMENTATION_ROOT_PATH = "docs";
private static final Logger logger = LoggerFactory.getLogger(NarUnpacker.class);
private static final String HASH_FILENAME = "nar-digest";
private static final FileFilter NAR_FILTER = pathname -> {
@ -554,22 +556,23 @@ public final class NarUnpacker {
// look for all documentation related to each component
try (final JarFile jarFile = new JarFile(jar)) {
for (final String componentName : jarExtensionMapping.getAllExtensionNames().keySet()) {
final String entryName = "docs/" + componentName;
// Build documentation path based on component class using Paths.get() for platform compatibility
final String componentDocumentationDirectory = Paths.get(JAR_DOCUMENTATION_ROOT_PATH, componentName).toString();
// go through each entry in this jar
for (final Enumeration<JarEntry> jarEnumeration = jarFile.entries(); jarEnumeration.hasMoreElements();) {
final JarEntry jarEntry = jarEnumeration.nextElement();
final File jarEntryFile = getJarEntryFile(docsDirectory, jarEntry.getName());
final String jarEntryName = jarEntryFile.getName();
final String jarEntryFileAbsolutePath = jarEntryFile.getAbsolutePath();
// if this entry is documentation for this component
if (jarEntryName.startsWith(entryName)) {
final String name = StringUtils.substringAfter(jarEntryName, "docs/");
final String path = coordinate.getGroup() + "/" + coordinate.getId() + "/" + coordinate.getVersion() + "/" + name;
if (jarEntryFileAbsolutePath.contains(componentDocumentationDirectory)) {
final String relativePath = StringUtils.substringAfter(jarEntryFileAbsolutePath, componentDocumentationDirectory);
final String outputPath = Paths.get(coordinate.getGroup(), coordinate.getId(), coordinate.getVersion(), componentName, relativePath).toString();
// if this is a directory create it
if (jarEntry.isDirectory()) {
final File componentDocsDirectory = new File(docsDirectory, path);
final File componentDocsDirectory = new File(docsDirectory, outputPath);
// ensure the documentation directory can be created
if (!componentDocsDirectory.exists() && !componentDocsDirectory.mkdirs()) {
@ -578,7 +581,7 @@ public final class NarUnpacker {
}
} else {
// if this is a file, write to it
final File componentDoc = new File(docsDirectory, path);
final File componentDoc = new File(docsDirectory, outputPath);
makeFile(jarFile.getInputStream(jarEntry), componentDoc);
}
}