diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ExtensionComponent.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ExtensionComponent.java index 1f07f569fb..8a4447d585 100644 --- a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ExtensionComponent.java +++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ExtensionComponent.java @@ -47,6 +47,8 @@ public class ExtensionComponent extends DefinedType { private Stateful stateful; + private boolean additionalDetails; + @ApiModelProperty("The build metadata for this component") public BuildInfo getBuildInfo() { return buildInfo; @@ -120,6 +122,7 @@ public class ExtensionComponent extends DefinedType { this.explicitRestrictions = explicitRestrictions; } + @ApiModelProperty("Indicates if the component stores state") public Stateful getStateful() { return stateful; } @@ -128,6 +131,15 @@ public class ExtensionComponent extends DefinedType { this.stateful = stateful; } + @ApiModelProperty("Indicates if the component has additional details documentation") + public boolean isAdditionalDetails() { + return additionalDetails; + } + + public void setAdditionalDetails(boolean additionalDetails) { + this.additionalDetails = additionalDetails; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 73929956ff..416fb2799a 100644 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -32,16 +32,13 @@ language governing permissions and limitations under the License. --> generate-resources nar - **/extension-manifest.xml + **/docs/** false ${project.build.directory}/extension-manifests true true true true - - - diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestContainer.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestContainer.java new file mode 100644 index 0000000000..16b91bf193 --- /dev/null +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestContainer.java @@ -0,0 +1,48 @@ +/* + * 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.runtime.manifest; + +import org.apache.nifi.extension.manifest.ExtensionManifest; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public class ExtensionManifestContainer { + + private final ExtensionManifest manifest; + private final Map additionalDetails; + + public ExtensionManifestContainer(final ExtensionManifest manifest) { + this(manifest, null); + } + + public ExtensionManifestContainer(final ExtensionManifest manifest, final Map additionalDetails) { + this.manifest = Objects.requireNonNull(manifest); + this.additionalDetails = Collections.unmodifiableMap(additionalDetails == null + ? Collections.emptyMap() : new LinkedHashMap<>(additionalDetails)); + } + + public ExtensionManifest getManifest() { + return manifest; + } + + public Map getAdditionalDetails() { + return additionalDetails; + } +} diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java index 4039ed0c4e..1e4a5ff734 100644 --- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java @@ -16,8 +16,6 @@ */ package org.apache.nifi.runtime.manifest; -import org.apache.nifi.extension.manifest.ExtensionManifest; - import java.util.List; /** @@ -25,6 +23,6 @@ import java.util.List; */ public interface ExtensionManifestProvider { - List getExtensionManifests(); + List getExtensionManifests(); } diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java index f78298816a..6c5ce1e8ba 100644 --- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java @@ -20,7 +20,6 @@ import org.apache.nifi.c2.protocol.component.api.BuildInfo; import org.apache.nifi.c2.protocol.component.api.Bundle; import org.apache.nifi.c2.protocol.component.api.RuntimeManifest; import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults; -import org.apache.nifi.extension.manifest.ExtensionManifest; /** * Builder for creating a RuntimeManifest. @@ -57,7 +56,7 @@ public interface RuntimeManifestBuilder { * @param extensionManifest the extension manifest to add * @return the builder */ - RuntimeManifestBuilder addBundle(ExtensionManifest extensionManifest); + RuntimeManifestBuilder addBundle(ExtensionManifestContainer extensionManifest); /** * Adds a Bundle for each of the given ExtensionManifests. @@ -65,7 +64,7 @@ public interface RuntimeManifestBuilder { * @param extensionManifests the extension manifests to add * @return the builder */ - RuntimeManifestBuilder addBundles(Iterable extensionManifests); + RuntimeManifestBuilder addBundles(Iterable extensionManifests); /** * Adds the given Bundle. diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java index 387c84b267..b482096954 100644 --- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java @@ -16,8 +16,9 @@ */ package org.apache.nifi.runtime.manifest.impl; -import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; import org.apache.nifi.extension.manifest.ExtensionManifest; +import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; +import org.apache.nifi.runtime.manifest.ExtensionManifestContainer; import org.apache.nifi.runtime.manifest.ExtensionManifestProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +27,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * ExtensionManifestProvider that loads extension manifests from a directory where the nifi-assembly-manifests @@ -46,7 +51,7 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv } @Override - public List getExtensionManifests() { + public List getExtensionManifests() { if (!baseDir.exists()) { throw new IllegalArgumentException("The specified manifest directory does not exist"); } @@ -56,18 +61,22 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv LOGGER.info("Loading extension manifests from: {}", baseDir.getAbsolutePath()); - final List extensionManifests = new ArrayList<>(); + final List extensionManifests = new ArrayList<>(); for (final File manifestDir : baseDir.listFiles()) { if (!manifestDir.isDirectory()) { LOGGER.debug("Skipping [{}], not a directory...", manifestDir.getAbsolutePath()); continue; } - final File manifestFile = new File(manifestDir, "extension-manifest.xml"); + final File manifestFile = new File(manifestDir, "META-INF/docs/extension-manifest.xml"); LOGGER.debug("Loading extension manifest file [{}]", manifestFile.getAbsolutePath()); final ExtensionManifest extensionManifest = loadExtensionManifest(manifestFile); - extensionManifests.add(extensionManifest); + final Map additionalDetails = loadAdditionalDetails(manifestDir); + + final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, additionalDetails); + extensionManifests.add(container); + LOGGER.debug("Successfully loaded extension manifest for [{}-{}-{}]", extensionManifest.getGroupId(), extensionManifest.getArtifactId(), extensionManifest.getVersion()); } @@ -83,4 +92,40 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv throw new RuntimeException("Unable to load extension manifest: " + manifestFile.getAbsolutePath(), ioException); } } + + private Map loadAdditionalDetails(final File manifestDir) { + final Map additionalDetailsMap = new LinkedHashMap<>(); + + final File additionalDetailsDir = new File(manifestDir, "META-INF/docs/additional-details"); + if (!additionalDetailsDir.exists()) { + LOGGER.debug("No additional-details directory found under [{}]", manifestDir.getAbsolutePath()); + return additionalDetailsMap; + } + + for (final File additionalDetailsTypeDir : additionalDetailsDir.listFiles()) { + if (!additionalDetailsTypeDir.isDirectory()) { + LOGGER.debug("Skipping [{}], not a directory...", additionalDetailsTypeDir.getAbsolutePath()); + continue; + } + + final File additionalDetailsFile = new File(additionalDetailsTypeDir, "additionalDetails.html"); + if (!additionalDetailsFile.exists()) { + LOGGER.debug("No additionalDetails.html found under [{}]", additionalDetailsTypeDir.getAbsolutePath()); + continue; + } + + try { + final String typeName = additionalDetailsTypeDir.getName(); + final byte[] additionalDetailsBytes = Files.readAllBytes(additionalDetailsFile.toPath()); + LOGGER.debug("Added additionalDetails for {} from {}", typeName, additionalDetailsFile.getAbsolutePath()); + additionalDetailsMap.put(typeName, new String(additionalDetailsBytes, StandardCharsets.UTF_8)); + } catch (final IOException e) { + throw new RuntimeException("Unable to load additional details content for " + + additionalDetailsFile.getAbsolutePath() + " due to: " + e.getMessage(), e); + } + } + + return additionalDetailsMap; + } + } diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java index 334a4a11b3..bbbe1f9db1 100644 --- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java @@ -21,8 +21,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import org.apache.nifi.c2.protocol.component.api.BuildInfo; import org.apache.nifi.c2.protocol.component.api.RuntimeManifest; +import org.apache.nifi.extension.manifest.Extension; +import org.apache.nifi.extension.manifest.ExtensionManifest; import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; import org.apache.nifi.extension.manifest.parser.jaxb.JAXBExtensionManifestParser; +import org.apache.nifi.runtime.manifest.ExtensionManifestContainer; import org.apache.nifi.runtime.manifest.ExtensionManifestProvider; import org.apache.nifi.runtime.manifest.RuntimeManifestSerializer; import org.slf4j.Logger; @@ -34,8 +37,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -93,12 +100,14 @@ public class RuntimeManifestGenerator { buildInfo.setTimestamp(buildTimestampMillis); buildInfo.setCompiler(buildJdkVendor + " " + buildJdk); + final List extensionsManifests = extensionManifestProvider.getExtensionManifests(); + final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder() .identifier(runtimeManifestId) .version(runtimeVersion) .runtimeType("nifi") .buildInfo(buildInfo) - .addBundles(extensionManifestProvider.getExtensionManifests()) + .addBundles(extensionsManifests) .schedulingDefaults(SchedulingDefaultsFactory.getNifiSchedulingDefaults()) .build(); @@ -106,6 +115,30 @@ public class RuntimeManifestGenerator { try (final OutputStream outputStream = new FileOutputStream(runtimeManifestFile)) { runtimeManifestSerializer.write(runtimeManifest, outputStream); } + + final File docsDir = new File(runtimeManifestFile.getParent(), "docs"); + docsDir.mkdirs(); + + for (final ExtensionManifestContainer manifestContainer : extensionsManifests) { + final ExtensionManifest extensionManifest = manifestContainer.getManifest(); + final Map additionalDetailsMap = manifestContainer.getAdditionalDetails(); + + final File bundleDir = new File(docsDir, extensionManifest.getGroupId() + + "/" + extensionManifest.getArtifactId() + + "/" + extensionManifest.getVersion()); + + for (final Extension extension : extensionManifest.getExtensions()) { + final String extensionType = extension.getName(); + final File extensionDir = new File(bundleDir, extensionType); + + final String additionalDetails = additionalDetailsMap.get(extensionType); + if (additionalDetails != null) { + extensionDir.mkdirs(); + final File additionalDetailsFile = new File(extensionDir, "additionalDetails.html"); + Files.write(additionalDetailsFile.toPath(), additionalDetails.getBytes(StandardCharsets.UTF_8)); + } + } + } } private ExtensionManifestProvider createExtensionManifestProvider() { diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java index 6a56035dce..c5cf27d95c 100644 --- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java +++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java @@ -52,6 +52,7 @@ import org.apache.nifi.extension.manifest.Restricted; import org.apache.nifi.extension.manifest.Stateful; import org.apache.nifi.logging.LogLevel; import org.apache.nifi.runtime.manifest.ComponentManifestBuilder; +import org.apache.nifi.runtime.manifest.ExtensionManifestContainer; import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder; import org.apache.nifi.scheduling.SchedulingStrategy; @@ -105,7 +106,12 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { } @Override - public RuntimeManifestBuilder addBundle(final ExtensionManifest extensionManifest) { + public RuntimeManifestBuilder addBundle(final ExtensionManifestContainer extensionManifestContainer) { + if (extensionManifestContainer == null) { + throw new IllegalArgumentException("Extension manifest container is required"); + } + + final ExtensionManifest extensionManifest = extensionManifestContainer.getManifest(); if (extensionManifest == null) { throw new IllegalArgumentException("Extension manifest is required"); } @@ -125,8 +131,12 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { bundle.setVersion(extensionManifest.getVersion()); if (extensionManifest.getExtensions() != null) { + final Map additionalDetailsMap = extensionManifestContainer.getAdditionalDetails(); final ComponentManifestBuilder componentManifestBuilder = new StandardComponentManifestBuilder(); - extensionManifest.getExtensions().forEach(extension -> addExtension(extensionManifest, extension, componentManifestBuilder)); + extensionManifest.getExtensions().forEach(extension -> { + final String additionalDetails = additionalDetailsMap.get(extension.getName()); + addExtension(extensionManifest, extension, additionalDetails, componentManifestBuilder); + }); bundle.setComponentManifest(componentManifestBuilder.build()); } bundles.add(bundle); @@ -135,7 +145,7 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { } @Override - public RuntimeManifestBuilder addBundles(final Iterable extensionManifests) { + public RuntimeManifestBuilder addBundles(final Iterable extensionManifests) { extensionManifests.forEach(em -> addBundle(em)); return this; } @@ -167,30 +177,32 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { return runtimeManifest; } - private void addExtension(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { + private void addExtension(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails, + final ComponentManifestBuilder componentManifestBuilder) { if (extension == null) { throw new IllegalArgumentException("Extension cannot be null"); } switch(extension.getType()) { case PROCESSOR: - addProcessorDefinition(extensionManifest, extension, componentManifestBuilder); + addProcessorDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder); break; case CONTROLLER_SERVICE: - addControllerServiceDefinition(extensionManifest, extension, componentManifestBuilder); + addControllerServiceDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder); break; case REPORTING_TASK: - addReportingTaskDefinition(extensionManifest, extension, componentManifestBuilder); + addReportingTaskDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder); break; default: throw new IllegalArgumentException("Unknown extension type: " + extension.getType()); } } - private void addProcessorDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { + private void addProcessorDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails, + final ComponentManifestBuilder componentManifestBuilder) { final ProcessorDefinition processorDefinition = new ProcessorDefinition(); populateDefinedType(extensionManifest, extension, processorDefinition); - populateExtensionComponent(extensionManifest, extension, processorDefinition); + populateExtensionComponent(extensionManifest, extension, additionalDetails, processorDefinition); populateConfigurableComponent(extension, processorDefinition); // processor specific fields @@ -279,18 +291,20 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { return componentRelationships; } - private void addControllerServiceDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { + private void addControllerServiceDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails, + final ComponentManifestBuilder componentManifestBuilder) { final ControllerServiceDefinition controllerServiceDefinition = new ControllerServiceDefinition(); populateDefinedType(extensionManifest, extension, controllerServiceDefinition); - populateExtensionComponent(extensionManifest, extension, controllerServiceDefinition); + populateExtensionComponent(extensionManifest, extension, additionalDetails, controllerServiceDefinition); populateConfigurableComponent(extension, controllerServiceDefinition); componentManifestBuilder.addControllerService(controllerServiceDefinition); } - private void addReportingTaskDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { + private void addReportingTaskDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails, + final ComponentManifestBuilder componentManifestBuilder) { final ReportingTaskDefinition reportingTaskDefinition = new ReportingTaskDefinition(); populateDefinedType(extensionManifest, extension, reportingTaskDefinition); - populateDefinedType(extensionManifest, extension, reportingTaskDefinition); + populateExtensionComponent(extensionManifest, extension, additionalDetails, reportingTaskDefinition); populateConfigurableComponent(extension, reportingTaskDefinition); final List schedulingStrategies = new ArrayList<>(); @@ -326,7 +340,8 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { definedType.setVersion(extensionManifest.getVersion()); } - private void populateExtensionComponent(final ExtensionManifest extensionManifest, final Extension extension, final ExtensionComponent extensionComponent) { + private void populateExtensionComponent(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails, + final ExtensionComponent extensionComponent) { final org.apache.nifi.extension.manifest.BuildInfo buildInfo = extensionManifest.getBuildInfo(); if (buildInfo != null) { final BuildInfo componentBuildInfo = new BuildInfo(); @@ -377,7 +392,10 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder { ); extensionComponent.setStateful(componentStateful); } + } + if (additionalDetails != null) { + extensionComponent.setAdditionalDetails(true); } } diff --git a/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java b/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java index 9f053b987b..714128c3c5 100644 --- a/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java +++ b/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java @@ -107,6 +107,7 @@ class TestRuntimeManifest { assertFalse(listHdfsDefinition.getSupportsDynamicProperties()); assertFalse(listHdfsDefinition.getSupportsDynamicRelationships()); assertEquals(InputRequirement.Requirement.INPUT_FORBIDDEN, listHdfsDefinition.getInputRequirement()); + assertTrue(listHdfsDefinition.isAdditionalDetails()); assertEquals("30 sec", listHdfsDefinition.getDefaultPenaltyDuration()); assertEquals("1 sec", listHdfsDefinition.getDefaultYieldDuration()); @@ -159,6 +160,7 @@ class TestRuntimeManifest { "org.apache.nifi.processors.hadoop.FetchHDFS"); assertNotNull(fetchHdfsDefinition.isRestricted()); assertTrue(fetchHdfsDefinition.isRestricted()); + assertFalse(fetchHdfsDefinition.isAdditionalDetails()); final Set restrictions = fetchHdfsDefinition.getExplicitRestrictions(); assertNotNull(restrictions); @@ -171,6 +173,7 @@ class TestRuntimeManifest { // Verify ConsumeKafka_2_6 definition which has properties with dependencies final ProcessorDefinition consumeKafkaDefinition = getProcessorDefinition(bundles, "nifi-kafka-2-6-nar", "org.apache.nifi.processors.kafka.pubsub.ConsumeKafka_2_6"); + assertTrue(consumeKafkaDefinition.isAdditionalDetails()); final PropertyDescriptor maxUncommitProp = getPropertyDescriptor(consumeKafkaDefinition, "max-uncommit-offset-wait"); final List propertyDependencies = maxUncommitProp.getDependencies(); diff --git a/nifi-manifest/nifi-runtime-manifest/pom.xml b/nifi-manifest/nifi-runtime-manifest/pom.xml index e36f9d29c3..87d69216d0 100644 --- a/nifi-manifest/nifi-runtime-manifest/pom.xml +++ b/nifi-manifest/nifi-runtime-manifest/pom.xml @@ -92,7 +92,6 @@ nifi-assembly manifests - **/extension-manifest.xml true ${extension.manifest.unpack.dir} true diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java index 36fa80cb62..fe016be9c1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java @@ -24,6 +24,7 @@ import org.apache.nifi.extension.manifest.ExtensionManifest; import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; import org.apache.nifi.nar.ExtensionManager; import org.apache.nifi.nar.NarClassLoadersHolder; +import org.apache.nifi.runtime.manifest.ExtensionManifestContainer; import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder; import org.apache.nifi.runtime.manifest.impl.SchedulingDefaultsFactory; import org.apache.nifi.runtime.manifest.impl.StandardRuntimeManifestBuilder; @@ -32,11 +33,16 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; public class StandardRuntimeManifestService implements RuntimeManifestService { @@ -90,12 +96,25 @@ public class StandardRuntimeManifestService implements RuntimeManifestService { return manifestBuilder.build(); } - private Optional getExtensionManifest(final Bundle bundle) { + private Optional getExtensionManifest(final Bundle bundle) { final BundleDetails bundleDetails = bundle.getBundleDetails(); + try { + final ExtensionManifest extensionManifest = loadExtensionManifest(bundleDetails); + final Map additionalDetails = loadAdditionalDetails(bundleDetails); + + final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, additionalDetails); + return Optional.of(container); + } catch (final IOException e) { + LOGGER.error("Unable to load extension manifest for bundle [{}]", bundleDetails.getCoordinate(), e); + return Optional.empty(); + } + } + + private ExtensionManifest loadExtensionManifest(final BundleDetails bundleDetails) throws IOException { final File manifestFile = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/extension-manifest.xml"); if (!manifestFile.exists()) { - LOGGER.warn("Unable to find extension manifest for [{}] at [{}]...", bundleDetails.getCoordinate(), manifestFile.getAbsolutePath()); - return Optional.empty(); + throw new FileNotFoundException("Extension manifest files does not exist for " + + bundleDetails.getCoordinate() + " at " + manifestFile.getAbsolutePath()); } try (final InputStream inputStream = new FileInputStream(manifestFile)) { @@ -105,13 +124,45 @@ public class StandardRuntimeManifestService implements RuntimeManifestService { extensionManifest.setGroupId(bundleDetails.getCoordinate().getGroup()); extensionManifest.setArtifactId(bundleDetails.getCoordinate().getId()); extensionManifest.setVersion(bundleDetails.getCoordinate().getVersion()); - return Optional.of(extensionManifest); - } catch (final IOException e) { - LOGGER.error("Unable to load extension manifest for bundle [{}]", bundleDetails.getCoordinate(), e); - return Optional.empty(); + return extensionManifest; } } + private Map loadAdditionalDetails(final BundleDetails bundleDetails) { + final Map additionalDetailsMap = new LinkedHashMap<>(); + + final File additionalDetailsDir = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/additional-details"); + if (!additionalDetailsDir.exists()) { + LOGGER.debug("No additional-details directory found under [{}]", bundleDetails.getWorkingDirectory().getAbsolutePath()); + return additionalDetailsMap; + } + + for (final File additionalDetailsTypeDir : additionalDetailsDir.listFiles()) { + if (!additionalDetailsTypeDir.isDirectory()) { + LOGGER.debug("Skipping [{}], not a directory...", additionalDetailsTypeDir.getAbsolutePath()); + continue; + } + + final File additionalDetailsFile = new File(additionalDetailsTypeDir, "additionalDetails.html"); + if (!additionalDetailsFile.exists()) { + LOGGER.debug("No additionalDetails.html found under [{}]", additionalDetailsTypeDir.getAbsolutePath()); + continue; + } + + try { + final String typeName = additionalDetailsTypeDir.getName(); + final String additionalDetailsContent = Files.lines(additionalDetailsFile.toPath()).collect(Collectors.joining()); + LOGGER.debug("Added additionalDetails for {} from {}", typeName, additionalDetailsFile.getAbsolutePath()); + additionalDetailsMap.put(typeName, additionalDetailsContent); + } catch (final IOException e) { + throw new RuntimeException("Unable to load additional details content for " + + additionalDetailsFile.getAbsolutePath() + " due to: " + e.getMessage(), e); + } + } + + return additionalDetailsMap; + } + // Visible for overriding from tests Bundle getFrameworkBundle() { return NarClassLoadersHolder.getInstance().getFrameworkBundle();