diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java index a86a416480..d7bad78f75 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLOutputFactory; @@ -36,6 +37,7 @@ import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.ControllerService; import org.apache.nifi.documentation.DocumentationWriter; +import org.apache.nifi.nar.ExtensionManager; /** * Generates HTML documentation for a ConfigurableComponent. This class is used @@ -148,9 +150,7 @@ public class HtmlDocumentationWriter implements DocumentationWriter { xmlStreamWriter.writeCharacters(", "); } - final String link = "../" + linkedComponent.getCanonicalName() + "/index.html"; - - writeLink(xmlStreamWriter, linkedComponent.getSimpleName(), link); + writeLinkForComponent(xmlStreamWriter, linkedComponent); ++index; } @@ -434,12 +434,24 @@ public class HtmlDocumentationWriter implements DocumentationWriter { } xmlStreamWriter.writeEndElement(); } else if (property.getControllerServiceDefinition() != null) { - Class controllerServiceClass = property - .getControllerServiceDefinition(); + Class controllerServiceClass = property.getControllerServiceDefinition(); - writeSimpleElement(xmlStreamWriter, "strong", "Controller Service: "); + writeSimpleElement(xmlStreamWriter, "strong", "Controller Service API: "); xmlStreamWriter.writeEmptyElement("br"); xmlStreamWriter.writeCharacters(controllerServiceClass.getSimpleName()); + + final List> implementations = lookupControllerServiceImpls(controllerServiceClass); + xmlStreamWriter.writeEmptyElement("br"); + if (implementations.size() > 0) { + final String title = implementations.size() > 1 ? "Implementations: " : "Implementation:"; + writeSimpleElement(xmlStreamWriter, "strong", title); + for (int i = 0; i < implementations.size(); i++) { + xmlStreamWriter.writeEmptyElement("br"); + writeLinkForComponent(xmlStreamWriter, implementations.get(i)); + } + } else { + xmlStreamWriter.writeCharacters("No implementations found."); + } } } @@ -519,4 +531,41 @@ public class HtmlDocumentationWriter implements DocumentationWriter { xmlStreamWriter.writeCharacters(text); xmlStreamWriter.writeEndElement(); } + + /** + * Writes a link to another configurable component + * + * @param xmlStreamWriter the xml stream writer + * @param clazz the configurable component to link to + * @throws XMLStreamException thrown if there is a problem writing the XML + */ + protected void writeLinkForComponent(final XMLStreamWriter xmlStreamWriter, final Class clazz) throws XMLStreamException { + writeLink(xmlStreamWriter, clazz.getSimpleName(), "../" + clazz.getCanonicalName() + "/index.html"); + } + + /** + * Uses the {@link ExtensionManager} to discover any {@link ControllerService} implementations that implement a specific + * ControllerService API. + * + * @param parent the controller service API + * @return a list of controller services that implement the controller service API + */ + private List> lookupControllerServiceImpls( + final Class parent) { + + final List> implementations = new ArrayList<>(); + + // first get all ControllerService implementations + final Set controllerServices = ExtensionManager.getExtensions(ControllerService.class); + + // then iterate over all controller services looking for any that is a child of the parent + // ControllerService API that was passed in as a parameter + for (final Class controllerServiceClass : controllerServices) { + if (parent.isAssignableFrom(controllerServiceClass)) { + implementations.add(controllerServiceClass); + } + } + + return implementations; + } } diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedControllerService.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedControllerService.java index 4320c6eecf..cd68267d30 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedControllerService.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/FullyDocumentedControllerService.java @@ -26,9 +26,9 @@ import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.processor.util.StandardValidators; -@CapabilityDescription("A documented controller service that can help you do things") +@CapabilityDescription("A documented controller service that can help you do things") @Tags({"one", "two", "three"}) -public class FullyDocumentedControllerService extends AbstractControllerService { +public class FullyDocumentedControllerService extends AbstractControllerService implements SampleService{ public static final PropertyDescriptor KEYSTORE = new PropertyDescriptor.Builder().name("Keystore Filename") .description("The fully-qualified filename of the Keystore").defaultValue(null) @@ -53,6 +53,10 @@ public class FullyDocumentedControllerService extends AbstractControllerService @Override protected List getSupportedPropertyDescriptors() { return properties; - } + } + @Override + public void doSomething() { + // TODO Auto-generated method stub + } } diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java index c6ed9fb9ef..6016f95300 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java @@ -30,7 +30,7 @@ import org.apache.nifi.documentation.mock.MockProcessorInitializationContext; import org.junit.Test; public class ProcessorDocumentationWriterTest { - + @Test public void testFullyDocumentedProcessor() throws IOException { FullyDocumentedProcessor processor = new FullyDocumentedProcessor(); @@ -59,7 +59,7 @@ public class ProcessorDocumentationWriterTest { assertContains(results, FullyDocumentedProcessor.REL_SUCCESS.getDescription()); assertContains(results, FullyDocumentedProcessor.REL_FAILURE.getName()); assertContains(results, FullyDocumentedProcessor.REL_FAILURE.getDescription()); - assertContains(results, "Controller Service: "); + assertContains(results, "Controller Service API: "); assertContains(results, "SampleService"); assertNotContains(results, "iconSecure.png"); @@ -97,6 +97,6 @@ public class ProcessorDocumentationWriterTest { // relationships assertContains(results, "This processor has no relationships."); - } + } }