From 498f41f130a23bed43a5b1dcb039a1d684d846a8 Mon Sep 17 00:00:00 2001 From: Pierre Villard Date: Fri, 10 Apr 2026 12:20:43 +0200 Subject: [PATCH] NIFI-15812 - Support CapabilityTag annotation in Documentation Writer --- .../AbstractConnectorDocumentationWriter.java | 12 +++++ .../AbstractDocumentationWriter.java | 13 +++++ .../xml/XmlConnectorDocumentationWriter.java | 16 ++++++ .../xml/XmlDocumentationWriter.java | 16 ++++++ .../XmlConnectorDocumentationWriterTest.java | 47 +++++++++++++++++ .../xml/XmlDocumentationWriterTest.java | 52 +++++++++++++++++++ 6 files changed, 156 insertions(+) diff --git a/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java index f10fcd1..9fba49d 100644 --- a/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java +++ b/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java @@ -17,6 +17,7 @@ package org.apache.nifi.documentation; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; @@ -63,6 +64,7 @@ protected void writeBody(final Connector connector) throws IOException { writeDeprecationNotice(connector.getClass().getAnnotation(DeprecationNotice.class)); writeDescription(getDescription(connector)); writeTags(getTags(connector)); + writeCapabilityTags(getCapabilityTags(connector)); writeConfigurationSteps(connector.getConfigurationSteps()); writeSeeAlso(connector.getClass().getAnnotation(SeeAlso.class)); } @@ -84,6 +86,14 @@ protected List getTags(final Connector connector) { return tagValues == null ? Collections.emptyList() : Arrays.asList(tagValues); } + protected List getCapabilityTags(final Connector connector) { + final CapabilityTag[] capabilityTags = connector.getClass().getAnnotationsByType(CapabilityTag.class); + if (capabilityTags.length == 0) { + return Collections.emptyList(); + } + return Arrays.asList(capabilityTags); + } + protected abstract void writeHeader(Connector connector) throws IOException; protected abstract void writeExtensionName(String extensionName) throws IOException; @@ -96,6 +106,8 @@ protected List getTags(final Connector connector) { protected abstract void writeTags(List tags) throws IOException; + protected abstract void writeCapabilityTags(List capabilityTags) throws IOException; + protected abstract void writeConfigurationSteps(List configurationSteps) throws IOException; protected abstract void writeSeeAlso(SeeAlso seeAlso) throws IOException; diff --git a/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java index 9dedd1b..c126cf7 100644 --- a/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java +++ b/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java @@ -37,6 +37,7 @@ import org.apache.nifi.annotation.configuration.DefaultSchedule; import org.apache.nifi.annotation.configuration.DefaultSettings; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.MultiProcessorUseCase; import org.apache.nifi.annotation.documentation.SeeAlso; @@ -153,6 +154,7 @@ protected void writeBody(final ConfigurableComponent component, Map getMultiProcessorUseCases(final Configurable return Arrays.asList(useCases); } + private List getCapabilityTags(final ConfigurableComponent component) { + final CapabilityTag[] capabilityTags = component.getClass().getAnnotationsByType(CapabilityTag.class); + if (capabilityTags.length == 0) { + return Collections.emptyList(); + } + + return Arrays.asList(capabilityTags); + } + protected ExtensionType getExtensionType(final ConfigurableComponent component) { if (component instanceof Processor) { return ExtensionType.PROCESSOR; @@ -324,6 +335,8 @@ protected ExtensionType getExtensionType(final ConfigurableComponent component) protected abstract void writeTags(List tags) throws IOException; + protected abstract void writeCapabilityTags(List capabilityTags) throws IOException; + protected abstract void writeProperties(List properties, Map propertyServices) throws IOException; protected abstract void writeDynamicProperties(List dynamicProperties) throws IOException; diff --git a/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java index 9fdcc19..86712cd 100644 --- a/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java +++ b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java @@ -27,6 +27,7 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.components.DescribedValue; @@ -114,6 +115,21 @@ protected void writeTags(final List tags) throws IOException { writeTextArray("tags", "tag", tags); } + @Override + protected void writeCapabilityTags(final List capabilityTags) throws IOException { + if (capabilityTags == null || capabilityTags.isEmpty()) { + return; + } + writeStartElement("capabilityTags"); + for (final CapabilityTag capabilityTag : capabilityTags) { + writeStartElement("capabilityTag"); + writeTextElement("key", capabilityTag.key()); + writeTextElement("value", capabilityTag.value()); + writeEndElement(); + } + writeEndElement(); + } + @Override protected void writeConfigurationSteps(final List configurationSteps) throws IOException { if (configurationSteps == null || configurationSteps.isEmpty()) { diff --git a/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java b/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java index b9acd56..f0f67a0 100644 --- a/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java +++ b/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java @@ -34,6 +34,7 @@ import org.apache.nifi.annotation.behavior.WritesAttribute; import org.apache.nifi.annotation.configuration.DefaultSchedule; import org.apache.nifi.annotation.configuration.DefaultSettings; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.MultiProcessorUseCase; import org.apache.nifi.annotation.documentation.ProcessorConfiguration; @@ -149,6 +150,21 @@ protected void writeTags(final List tags) throws IOException { writeTextArray("tags", "tag", tags); } + @Override + protected void writeCapabilityTags(final List capabilityTags) throws IOException { + if (capabilityTags == null || capabilityTags.isEmpty()) { + return; + } + writeArray("capabilityTags", capabilityTags, this::writeCapabilityTag); + } + + private void writeCapabilityTag(final CapabilityTag capabilityTag) throws IOException { + writeStartElement("capabilityTag"); + writeTextElement("key", capabilityTag.key()); + writeTextElement("value", capabilityTag.value()); + writeEndElement(); + } + @Override protected void writeProperties(final List properties, Map propertyServices) throws IOException { if (properties == null || properties.isEmpty()) { diff --git a/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java index dc5912d..3b911fd 100644 --- a/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java +++ b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java @@ -17,6 +17,7 @@ package org.apache.nifi.documentation.xml; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; @@ -410,6 +411,47 @@ void testWriteConnectorWithBooleanPropertyType() throws Exception { assertEquals("BOOLEAN", propertyTypeNode.getTextContent()); } + @Test + void testWriteConnectorWithCapabilityTags() throws Exception { + final Connector connector = new CapabilityTagConnector(); + final Document document = writeDocumentation(connector); + + assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, document); + + final Node capabilityTagsNode = findNode("/extension/capabilityTags", document); + assertNotNull(capabilityTagsNode); + + final NodeList capabilityTagNodes = capabilityTagsNode.getChildNodes(); + assertEquals(2, capabilityTagNodes.getLength()); + + final Node firstTag = capabilityTagNodes.item(0); + assertEquals("capabilityTag", firstTag.getNodeName()); + final Node firstKey = firstTag.getFirstChild(); + assertEquals("key", firstKey.getNodeName()); + assertEquals("vendor", firstKey.getTextContent()); + final Node firstValue = firstKey.getNextSibling(); + assertEquals("value", firstValue.getNodeName()); + assertEquals("Acme Corp", firstValue.getTextContent()); + + final Node secondTag = capabilityTagNodes.item(1); + assertEquals("capabilityTag", secondTag.getNodeName()); + final Node secondKey = secondTag.getFirstChild(); + assertEquals("key", secondKey.getNodeName()); + assertEquals("category", secondKey.getTextContent()); + final Node secondValue = secondKey.getNextSibling(); + assertEquals("value", secondValue.getNodeName()); + assertEquals("networking", secondValue.getTextContent()); + } + + @Test + void testWriteConnectorWithNoCapabilityTags() throws Exception { + final Connector connector = new MinimalConnector(); + final Document document = writeDocumentation(connector); + + final Node capabilityTagsNode = findNode("/extension/capabilityTags", document); + assertNull(capabilityTagsNode); + } + private Node findNode(final String expression, final Node node) throws XPathExpressionException { final XPathFactory factory = XPathFactory.newInstance(); final XPath path = factory.newXPath(); @@ -862,5 +904,10 @@ public List getConfigurationSteps() { ); } } + + @CapabilityTag(key = "vendor", value = "Acme Corp") + @CapabilityTag(key = "category", value = "networking") + private static class CapabilityTagConnector extends MinimalConnector { + } } diff --git a/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java b/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java index e2ea473..9627c12 100644 --- a/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java +++ b/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.documentation.xml; +import org.apache.nifi.annotation.documentation.CapabilityTag; import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.components.ConfigurableComponent; import org.apache.nifi.components.PropertyDescriptor; @@ -203,6 +204,47 @@ void testWritePropertyDescriptors() throws Exception { assertListenPortPropertiesMatched(document); } + @Test + void testWriteCapabilityTags() throws Exception { + final Processor processor = new CapabilityTagProcessor(); + final Document document = writeDocumentation(processor); + + assertExtensionNameTypeFound(processor, ExtensionType.PROCESSOR, document); + + final Node capabilityTagsNode = findNode("/extension/capabilityTags", document); + assertNotNull(capabilityTagsNode); + + final NodeList capabilityTagNodes = capabilityTagsNode.getChildNodes(); + assertEquals(2, capabilityTagNodes.getLength()); + + final Node firstTag = capabilityTagNodes.item(0); + assertEquals("capabilityTag", firstTag.getNodeName()); + final Node firstKey = firstTag.getFirstChild(); + assertEquals("key", firstKey.getNodeName()); + assertEquals("vendor", firstKey.getTextContent()); + final Node firstValue = firstKey.getNextSibling(); + assertEquals("value", firstValue.getNodeName()); + assertEquals("Acme Corp", firstValue.getTextContent()); + + final Node secondTag = capabilityTagNodes.item(1); + assertEquals("capabilityTag", secondTag.getNodeName()); + final Node secondKey = secondTag.getFirstChild(); + assertEquals("key", secondKey.getNodeName()); + assertEquals("category", secondKey.getTextContent()); + final Node secondValue = secondKey.getNextSibling(); + assertEquals("value", secondValue.getNodeName()); + assertEquals("networking", secondValue.getTextContent()); + } + + @Test + void testWriteNoCapabilityTags() throws Exception { + final Processor processor = new MinimalProcessor(); + final Document document = writeDocumentation(processor); + + final Node capabilityTagsNode = findNode("/extension/capabilityTags", document); + assertNull(capabilityTagsNode); + } + private void assertRelationshipsMatched(final Document document) throws XPathExpressionException { final Node relationshipsNode = findNode("/extension/relationships", document); assertNotNull(relationshipsNode); @@ -400,4 +442,14 @@ protected List getSupportedPropertyDescriptors() { return PROPERTY_DESCRIPTORS; } } + + @CapabilityTag(key = "vendor", value = "Acme Corp") + @CapabilityTag(key = "category", value = "networking") + private static class CapabilityTagProcessor extends AbstractProcessor { + + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException { + + } + } }