This is an automated email from the ASF dual-hosted git repository.

markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi-api.git

commit 6705411cc94b859e46b9931da92a92a92fd2e916
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Dec 23 11:50:26 2025 -0500

    NIFI-15353: Adding documentation writers for connectors. (#40)
    
    This closes #40
---
 .../AbstractConnectorDocumentationWriter.java      | 106 +++
 .../ConnectorDocumentationWriter.java              |  52 ++
 .../apache/nifi/documentation/ExtensionType.java   |   4 +-
 ...ocumentationConnectorInitializationContext.java |  54 ++
 .../xml/XmlConnectorDocumentationWriter.java       | 344 ++++++++
 ...entationConnectorInitializationContextTest.java |  77 ++
 .../xml/XmlConnectorDocumentationWriterTest.java   | 866 +++++++++++++++++++++
 7 files changed, 1502 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java
 
b/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java
new file mode 100644
index 0000000..f10fcd1
--- /dev/null
+++ 
b/src/main/java/org/apache/nifi/documentation/AbstractConnectorDocumentationWriter.java
@@ -0,0 +1,106 @@
+/*
+ * 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.documentation;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.DeprecationNotice;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.Connector;
+import 
org.apache.nifi.documentation.init.DocumentationConnectorInitializationContext;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Base class for ConnectorDocumentationWriter that simplifies iterating over 
all information for a Connector,
+ * creating a separate method for each piece of documentation, to ensure that 
implementations properly override
+ * all methods and therefore properly account for all information about a 
Connector.
+ *
+ * Please note that while this class lives within the nifi-api, it is provided 
primarily as a means for documentation components within
+ * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the 
API is needed in order to extract the relevant information and
+ * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so 
would cause a circular dependency). By having this homed within
+ * the nifi-api, the Maven plugin is able to discover the class dynamically 
and invoke the one or two methods necessary to create the documentation.
+ *
+ * This is a new capability for Connectors and therefore, you should
+ * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is 
still evolving and may change in a non-backward-compatible manner or even be
+ * removed from one incremental release to the next. Use at your own risk!
+ */
+public abstract class AbstractConnectorDocumentationWriter implements 
ConnectorDocumentationWriter {
+
+    @Override
+    public void initialize(final Connector connector) {
+        connector.initialize(new 
DocumentationConnectorInitializationContext());
+    }
+
+    @Override
+    public final void write(final Connector connector) throws IOException {
+        writeHeader(connector);
+        writeBody(connector);
+        writeFooter(connector);
+    }
+
+    protected void writeBody(final Connector connector) throws IOException {
+        writeExtensionName(connector.getClass().getName());
+        writeExtensionType(ExtensionType.CONNECTOR);
+        
writeDeprecationNotice(connector.getClass().getAnnotation(DeprecationNotice.class));
+        writeDescription(getDescription(connector));
+        writeTags(getTags(connector));
+        writeConfigurationSteps(connector.getConfigurationSteps());
+        writeSeeAlso(connector.getClass().getAnnotation(SeeAlso.class));
+    }
+
+    protected String getDescription(final Connector connector) {
+        final CapabilityDescription capabilityDescription = 
connector.getClass().getAnnotation(CapabilityDescription.class);
+        if (capabilityDescription == null) {
+            return null;
+        }
+        return capabilityDescription.value();
+    }
+
+    protected List<String> getTags(final Connector connector) {
+        final Tags tags = connector.getClass().getAnnotation(Tags.class);
+        if (tags == null) {
+            return Collections.emptyList();
+        }
+        final String[] tagValues = tags.value();
+        return tagValues == null ? Collections.emptyList() : 
Arrays.asList(tagValues);
+    }
+
+    protected abstract void writeHeader(Connector connector) throws 
IOException;
+
+    protected abstract void writeExtensionName(String extensionName) throws 
IOException;
+
+    protected abstract void writeExtensionType(ExtensionType extensionType) 
throws IOException;
+
+    protected abstract void writeDeprecationNotice(DeprecationNotice 
deprecationNotice) throws IOException;
+
+    protected abstract void writeDescription(String description) throws 
IOException;
+
+    protected abstract void writeTags(List<String> tags) throws IOException;
+
+    protected abstract void writeConfigurationSteps(List<ConfigurationStep> 
configurationSteps) throws IOException;
+
+    protected abstract void writeSeeAlso(SeeAlso seeAlso) throws IOException;
+
+    protected abstract void writeFooter(Connector connector) throws 
IOException;
+
+}
+
diff --git 
a/src/main/java/org/apache/nifi/documentation/ConnectorDocumentationWriter.java 
b/src/main/java/org/apache/nifi/documentation/ConnectorDocumentationWriter.java
new file mode 100644
index 0000000..a7edb41
--- /dev/null
+++ 
b/src/main/java/org/apache/nifi/documentation/ConnectorDocumentationWriter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.documentation;
+
+import java.io.IOException;
+import org.apache.nifi.components.connector.Connector;
+
+/**
+ * Generates documentation for an instance of a Connector.
+ *
+ * Please note that while this class lives within the nifi-api, it is provided 
primarily as a means for documentation components within
+ * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the 
API is needed in order to extract the relevant information and
+ * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so 
would cause a circular dependency). By having this homed within
+ * the nifi-api, the Maven plugin is able to discover the class dynamically 
and invoke the one or two methods necessary to create the documentation.
+ *
+ * This is a new capability for Connectors and therefore, you should
+ * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is 
still evolving and may change in a non-backward-compatible manner or even be
+ * removed from one incremental release to the next. Use at your own risk!
+ */
+public interface ConnectorDocumentationWriter {
+
+    /**
+     * Calls initialize on the connector. Must be called before calling any 
write methods.
+     *
+     * @param connector the connector to initialize
+     */
+    void initialize(Connector connector);
+
+    /**
+     * Write the documentation for the given connector.
+     *
+     * @param connector the connector to document
+     * @throws IOException if an error occurs writing the documentation
+     */
+    void write(Connector connector) throws IOException;
+
+}
+
diff --git a/src/main/java/org/apache/nifi/documentation/ExtensionType.java 
b/src/main/java/org/apache/nifi/documentation/ExtensionType.java
index 7f6fbab..160966e 100644
--- a/src/main/java/org/apache/nifi/documentation/ExtensionType.java
+++ b/src/main/java/org/apache/nifi/documentation/ExtensionType.java
@@ -27,5 +27,7 @@ public enum ExtensionType {
 
     PARAMETER_PROVIDER,
 
-    FLOW_REGISTRY_CLIENT;
+    FLOW_REGISTRY_CLIENT,
+
+    CONNECTOR;
 }
diff --git 
a/src/main/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContext.java
 
b/src/main/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContext.java
new file mode 100644
index 0000000..3529a9f
--- /dev/null
+++ 
b/src/main/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContext.java
@@ -0,0 +1,54 @@
+/*
+ * 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.documentation.init;
+
+import java.util.UUID;
+import org.apache.nifi.components.connector.ConnectorInitializationContext;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.logging.ComponentLog;
+
+/**
+ * A ConnectorInitializationContext implementation for use during 
documentation generation.
+ * This context provides minimal functionality needed to initialize a 
Connector for the purposes
+ * of extracting its configuration steps and other documentation metadata.
+ */
+public class DocumentationConnectorInitializationContext implements 
ConnectorInitializationContext {
+    private final String uuid = UUID.randomUUID().toString();
+
+    @Override
+    public String getIdentifier() {
+        return uuid;
+    }
+
+    @Override
+    public String getName() {
+        return "DocumentationConnector";
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return new NopComponentLog();
+    }
+
+    @Override
+    public void updateFlow(final FlowContext flowContext, final 
VersionedExternalFlow versionedExternalFlow) throws FlowUpdateException {
+        // No-op for documentation purposes - we don't actually update any 
flows
+    }
+}
+
diff --git 
a/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
 
b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
new file mode 100644
index 0000000..9fdcc19
--- /dev/null
+++ 
b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
@@ -0,0 +1,344 @@
+/*
+ * 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.documentation.xml;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.apache.nifi.annotation.documentation.DeprecationNotice;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.components.DescribedValue;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConfigurationStepDependency;
+import org.apache.nifi.components.connector.Connector;
+import org.apache.nifi.components.connector.ConnectorPropertyDependency;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.documentation.AbstractConnectorDocumentationWriter;
+import org.apache.nifi.documentation.ExtensionType;
+
+/**
+ * XML-based implementation of ConnectorDocumentationWriter.
+ * Please note that while this class lives within the nifi-api, it is provided 
primarily as a means for documentation components within
+ * the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the 
API is needed in order to extract the relevant information and
+ * the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so 
would cause a circular dependency). By having this homed within
+ * the nifi-api, the Maven plugin is able to discover the class dynamically 
and invoke the one or two methods necessary to create the documentation.
+ *
+ * This is a new capability for Connectors and therefore, you should
+ * <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is 
still evolving and may change in a non-backward-compatible manner or even be
+ * removed from one incremental release to the next. Use at your own risk!
+ */
+public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentationWriter {
+    private final XMLStreamWriter writer;
+
+    public XmlConnectorDocumentationWriter(final OutputStream out) throws 
XMLStreamException {
+        this.writer = 
XMLOutputFactory.newInstance().createXMLStreamWriter(out, "UTF-8");
+    }
+
+    public XmlConnectorDocumentationWriter(final XMLStreamWriter writer) {
+        this.writer = writer;
+    }
+
+    @Override
+    protected void writeHeader(final Connector connector) throws IOException {
+        writeStartElement("extension");
+    }
+
+    @Override
+    protected void writeExtensionName(final String extensionName) throws 
IOException {
+        writeTextElement("name", extensionName);
+    }
+
+    @Override
+    protected void writeExtensionType(final ExtensionType extensionType) 
throws IOException {
+        writeTextElement("type", extensionType.name());
+    }
+
+    @Override
+    protected void writeDeprecationNotice(final DeprecationNotice 
deprecationNotice) throws IOException {
+        if (deprecationNotice == null) {
+            return;
+        }
+
+        final Class<?>[] classes = deprecationNotice.alternatives();
+        final String[] classNames = deprecationNotice.classNames();
+
+        final Set<String> alternatives = new LinkedHashSet<>();
+        for (final Class<?> alternativeClass : classes) {
+            alternatives.add(alternativeClass.getName());
+        }
+
+        Collections.addAll(alternatives, classNames);
+
+        writeStartElement("deprecationNotice");
+        writeTextElement("reason", deprecationNotice.reason());
+        writeTextArray("alternatives", "alternative", alternatives);
+        writeEndElement();
+    }
+
+    @Override
+    protected void writeDescription(final String description) throws 
IOException {
+        if (description == null) {
+            return;
+        }
+        writeTextElement("description", description);
+    }
+
+    @Override
+    protected void writeTags(final List<String> tags) throws IOException {
+        if (tags == null || tags.isEmpty()) {
+            return;
+        }
+        writeTextArray("tags", "tag", tags);
+    }
+
+    @Override
+    protected void writeConfigurationSteps(final List<ConfigurationStep> 
configurationSteps) throws IOException {
+        if (configurationSteps == null || configurationSteps.isEmpty()) {
+            return;
+        }
+
+        writeStartElement("configurationSteps");
+        for (final ConfigurationStep step : configurationSteps) {
+            writeConfigurationStep(step);
+        }
+        writeEndElement();
+    }
+
+    private void writeConfigurationStep(final ConfigurationStep step) throws 
IOException {
+        writeStartElement("configurationStep");
+
+        writeTextElement("name", step.getName());
+        if (step.getDescription() != null) {
+            writeTextElement("description", step.getDescription());
+        }
+
+        // Write step dependencies
+        final Set<ConfigurationStepDependency> stepDependencies = 
step.getDependencies();
+        if (stepDependencies != null && !stepDependencies.isEmpty()) {
+            writeStartElement("stepDependencies");
+            for (final ConfigurationStepDependency dependency : 
stepDependencies) {
+                writeConfigurationStepDependency(dependency);
+            }
+            writeEndElement();
+        }
+
+        // Write property groups
+        final List<ConnectorPropertyGroup> propertyGroups = 
step.getPropertyGroups();
+        if (propertyGroups != null && !propertyGroups.isEmpty()) {
+            writeStartElement("propertyGroups");
+            for (final ConnectorPropertyGroup group : propertyGroups) {
+                writePropertyGroup(group);
+            }
+            writeEndElement();
+        }
+
+        writeEndElement();
+    }
+
+    private void writeConfigurationStepDependency(final 
ConfigurationStepDependency dependency) throws IOException {
+        writeStartElement("stepDependency");
+
+        writeTextElement("stepName", dependency.getStepName());
+        writeTextElement("propertyName", dependency.getPropertyName());
+
+        final Set<String> dependentValues = dependency.getDependentValues();
+        if (dependentValues != null && !dependentValues.isEmpty()) {
+            writeTextArray("dependentValues", "dependentValue", 
dependentValues);
+        }
+
+        writeEndElement();
+    }
+
+    private void writePropertyGroup(final ConnectorPropertyGroup group) throws 
IOException {
+        writeStartElement("propertyGroup");
+
+        if (group.getName() != null) {
+            writeTextElement("name", group.getName());
+        }
+        if (group.getDescription() != null) {
+            writeTextElement("description", group.getDescription());
+        }
+
+        final List<ConnectorPropertyDescriptor> properties = 
group.getProperties();
+        if (properties != null && !properties.isEmpty()) {
+            writeStartElement("properties");
+            for (final ConnectorPropertyDescriptor property : properties) {
+                writeConnectorProperty(property);
+            }
+            writeEndElement();
+        }
+
+        writeEndElement();
+    }
+
+    private void writeConnectorProperty(final ConnectorPropertyDescriptor 
property) throws IOException {
+        writeStartElement("property");
+
+        writeTextElement("name", property.getName());
+        if (property.getDescription() != null) {
+            writeTextElement("description", property.getDescription());
+        }
+        if (property.getDefaultValue() != null) {
+            writeTextElement("defaultValue", property.getDefaultValue());
+        }
+        writeBooleanElement("required", property.isRequired());
+        writeTextElement("propertyType", property.getType().name());
+        writeBooleanElement("allowableValuesFetchable", 
property.isAllowableValuesFetchable());
+
+        // Write allowable values
+        final List<DescribedValue> allowableValues = 
property.getAllowableValues();
+        if (allowableValues != null && !allowableValues.isEmpty()) {
+            writeStartElement("allowableValues");
+            for (final DescribedValue value : allowableValues) {
+                writeAllowableValue(value);
+            }
+            writeEndElement();
+        }
+
+        // Write property dependencies
+        final Set<ConnectorPropertyDependency> dependencies = 
property.getDependencies();
+        if (dependencies != null && !dependencies.isEmpty()) {
+            writeStartElement("dependencies");
+            for (final ConnectorPropertyDependency dependency : dependencies) {
+                writePropertyDependency(dependency);
+            }
+            writeEndElement();
+        }
+
+        writeEndElement();
+    }
+
+    private void writeAllowableValue(final DescribedValue value) throws 
IOException {
+        writeStartElement("allowableValue");
+        writeTextElement("displayName", value.getDisplayName());
+        writeTextElement("value", value.getValue());
+        if (value.getDescription() != null) {
+            writeTextElement("description", value.getDescription());
+        }
+        writeEndElement();
+    }
+
+    private void writePropertyDependency(final ConnectorPropertyDependency 
dependency) throws IOException {
+        writeStartElement("dependency");
+        writeTextElement("propertyName", dependency.getPropertyName());
+
+        final Set<String> dependentValues = dependency.getDependentValues();
+        if (dependentValues != null && !dependentValues.isEmpty()) {
+            writeTextArray("dependentValues", "dependentValue", 
dependentValues);
+        }
+
+        writeEndElement();
+    }
+
+    @Override
+    protected void writeSeeAlso(final SeeAlso seeAlso) throws IOException {
+        if (seeAlso == null) {
+            return;
+        }
+
+        final Class<?>[] classes = seeAlso.value();
+        final String[] classNames = seeAlso.classNames();
+
+        final Set<String> toSee = new LinkedHashSet<>();
+        if (classes != null) {
+            for (final Class<?> classToSee : classes) {
+                toSee.add(classToSee.getName());
+            }
+        }
+
+        if (classNames != null) {
+            Collections.addAll(toSee, classNames);
+        }
+
+        writeTextArray("seeAlso", "see", toSee);
+    }
+
+    @Override
+    protected void writeFooter(final Connector connector) throws IOException {
+        writeEndElement();
+    }
+
+    // Utility methods for XML writing
+
+    private void writeStartElement(final String elementName) throws 
IOException {
+        try {
+            writer.writeStartElement(elementName);
+        } catch (final XMLStreamException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void writeEndElement() throws IOException {
+        try {
+            writer.writeEndElement();
+        } catch (final XMLStreamException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void writeTextElement(final String name, final String text) throws 
IOException {
+        writeStartElement(name);
+        writeText(text);
+        writeEndElement();
+    }
+
+    private void writeBooleanElement(final String name, final boolean value) 
throws IOException {
+        writeTextElement(name, String.valueOf(value));
+    }
+
+    private void writeText(final String text) throws IOException {
+        if (text == null) {
+            return;
+        }
+        try {
+            writer.writeCharacters(text);
+        } catch (final XMLStreamException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void writeTextArray(final String outerTagName, final String 
elementTagName, final Collection<String> values) throws IOException {
+        writeTextArray(outerTagName, elementTagName, values, String::toString);
+    }
+
+    private <T> void writeTextArray(final String outerTagName, final String 
elementTagName, final Collection<T> values, final Function<T, String> 
transform) throws IOException {
+        writeStartElement(outerTagName);
+
+        if (values != null) {
+            for (final T value : values) {
+                writeStartElement(elementTagName);
+                if (value != null) {
+                    writeText(transform.apply(value));
+                }
+                writeEndElement();
+            }
+        }
+
+        writeEndElement();
+    }
+
+}
+
diff --git 
a/src/test/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContextTest.java
 
b/src/test/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContextTest.java
new file mode 100644
index 0000000..96c4dcb
--- /dev/null
+++ 
b/src/test/java/org/apache/nifi/documentation/init/DocumentationConnectorInitializationContextTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.documentation.init;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class DocumentationConnectorInitializationContextTest {
+
+    private DocumentationConnectorInitializationContext context;
+
+    @BeforeEach
+    void setUp() {
+        context = new DocumentationConnectorInitializationContext();
+    }
+
+    @Test
+    void testGetIdentifierReturnsNonNullUUID() {
+        final String identifier = context.getIdentifier();
+
+        assertNotNull(identifier);
+    }
+
+    @Test
+    void testGetIdentifierReturnsConsistentValue() {
+        final String identifier1 = context.getIdentifier();
+        final String identifier2 = context.getIdentifier();
+
+        assertEquals(identifier1, identifier2);
+    }
+
+    @Test
+    void testDifferentInstancesHaveDifferentIdentifiers() {
+        final DocumentationConnectorInitializationContext otherContext = new 
DocumentationConnectorInitializationContext();
+
+        assertNotEquals(context.getIdentifier(), otherContext.getIdentifier());
+    }
+
+    @Test
+    void testGetNameReturnsExpectedValue() {
+        assertEquals("DocumentationConnector", context.getName());
+    }
+
+    @Test
+    void testGetLoggerReturnsNopComponentLog() {
+        final ComponentLog logger = context.getLogger();
+
+        assertNotNull(logger);
+        assertEquals(NopComponentLog.class, logger.getClass());
+    }
+
+    @Test
+    void testUpdateFlowDoesNotThrow() throws Exception {
+        // updateFlow is a no-op for documentation purposes, should not throw
+        context.updateFlow(null, null);
+    }
+}
+
diff --git 
a/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
 
b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
new file mode 100644
index 0000000..dc5912d
--- /dev/null
+++ 
b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
@@ -0,0 +1,866 @@
+/*
+ * 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.documentation.xml;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.DeprecationNotice;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.Connector;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.documentation.ExtensionType;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+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;
+
+@ExtendWith(MockitoExtension.class)
+class XmlConnectorDocumentationWriterTest {
+
+    @Test
+    void testWriteMinimalConnector() throws Exception {
+        final Connector connector = new MinimalConnector();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+    }
+
+    @Test
+    void testWriteConnectorWithDescription() throws Exception {
+        final Connector connector = new DescribedConnector();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node descriptionNode = findNode("/extension/description", 
document);
+        assertNotNull(descriptionNode);
+        assertEquals("A connector with a description", 
descriptionNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithTags() throws Exception {
+        final Connector connector = new TaggedConnector();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node tagsNode = findNode("/extension/tags", document);
+        assertNotNull(tagsNode);
+
+        final List<String> tagValues = new ArrayList<>();
+        final NodeList tagNodes = tagsNode.getChildNodes();
+        for (int i = 0; i < tagNodes.getLength(); i++) {
+            final Node tagNode = tagNodes.item(i);
+            assertEquals("tag", tagNode.getNodeName());
+            tagValues.add(tagNode.getTextContent());
+        }
+
+        assertEquals(List.of("test", "documentation", "connector"), tagValues);
+    }
+
+    @Test
+    void testWriteDeprecatedConnector() throws Exception {
+        final Connector connector = new DeprecatedConnector();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node deprecationNoticeNode = 
findNode("/extension/deprecationNotice", document);
+        assertNotNull(deprecationNoticeNode);
+
+        final Node reasonNode = 
findNode("/extension/deprecationNotice/reason", document);
+        assertNotNull(reasonNode);
+        assertEquals("Use a different connector", reasonNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithSeeAlso() throws Exception {
+        final Connector connector = new SeeAlsoConnector();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node seeAlsoNode = findNode("/extension/seeAlso", document);
+        assertNotNull(seeAlsoNode);
+
+        final List<String> seeAlsoValues = new ArrayList<>();
+        final NodeList seeNodes = seeAlsoNode.getChildNodes();
+        for (int i = 0; i < seeNodes.getLength(); i++) {
+            final Node seeNode = seeNodes.item(i);
+            assertEquals("see", seeNode.getNodeName());
+            seeAlsoValues.add(seeNode.getTextContent());
+        }
+
+        assertEquals(1, seeAlsoValues.size());
+        
assertEquals("org.apache.nifi.documentation.xml.XmlConnectorDocumentationWriterTest$MinimalConnector",
 seeAlsoValues.getFirst());
+    }
+
+    @Test
+    void testWriteConnectorWithConfigurationSteps() throws Exception {
+        final Connector connector = new ConnectorWithConfigurationSteps();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node configStepsNode = findNode("/extension/configurationSteps", 
document);
+        assertNotNull(configStepsNode);
+
+        final Node firstStepNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step One']", 
document);
+        assertNotNull(firstStepNode);
+
+        final Node stepNameNode = 
findNode("/extension/configurationSteps/configurationStep/name", document);
+        assertNotNull(stepNameNode);
+        assertEquals("Step One", stepNameNode.getTextContent());
+
+        final Node stepDescriptionNode = 
findNode("/extension/configurationSteps/configurationStep/description", 
document);
+        assertNotNull(stepDescriptionNode);
+        assertEquals("First configuration step", 
stepDescriptionNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithConfigurationStepWithoutDescription() throws 
Exception {
+        final Connector connector = new ConnectorWithStepWithoutDescription();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node stepNameNode = 
findNode("/extension/configurationSteps/configurationStep/name", document);
+        assertNotNull(stepNameNode);
+        assertEquals("Minimal Step", stepNameNode.getTextContent());
+
+        // Step description is optional - should not be present when not 
provided
+        final Node stepDescriptionNode = 
findNode("/extension/configurationSteps/configurationStep/description", 
document);
+        assertNull(stepDescriptionNode);
+    }
+
+    @Test
+    void testWriteConnectorWithPropertyGroups() throws Exception {
+        final Connector connector = new ConnectorWithPropertyGroups();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node propertyGroupsNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups", 
document);
+        assertNotNull(propertyGroupsNode);
+
+        final Node propertyGroupNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup",
 document);
+        assertNotNull(propertyGroupNode);
+
+        final Node groupNameNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/name",
 document);
+        assertNotNull(groupNameNode);
+        assertEquals("Connection Settings", groupNameNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithProperties() throws Exception {
+        final Connector connector = new ConnectorWithProperties();
+        final Document document = writeDocumentation(connector);
+
+        assertExtensionNameTypeFound(connector, ExtensionType.CONNECTOR, 
document);
+
+        final Node propertiesNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties",
 document);
+        assertNotNull(propertiesNode);
+
+        final Node propertyNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property",
 document);
+        assertNotNull(propertyNode);
+
+        final Node propertyNameNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/name",
 document);
+        assertNotNull(propertyNameNode);
+        assertEquals("Hostname", propertyNameNode.getTextContent());
+
+        final Node propertyDescriptionNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/description",
 document);
+        assertNotNull(propertyDescriptionNode);
+        assertEquals("The hostname to connect to", 
propertyDescriptionNode.getTextContent());
+
+        final Node propertyRequiredNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/required",
 document);
+        assertNotNull(propertyRequiredNode);
+        assertEquals("true", propertyRequiredNode.getTextContent());
+
+        final Node propertyTypeNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/propertyType",
 document);
+        assertNotNull(propertyTypeNode);
+        assertEquals("STRING", propertyTypeNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithPropertyDefaultValue() throws Exception {
+        final Connector connector = new ConnectorWithPropertyDefaultValue();
+        final Document document = writeDocumentation(connector);
+
+        final Node defaultValueNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/defaultValue",
 document);
+        assertNotNull(defaultValueNode);
+        assertEquals("localhost", defaultValueNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithAllowableValues() throws Exception {
+        final Connector connector = new ConnectorWithAllowableValues();
+        final Document document = writeDocumentation(connector);
+
+        final Node allowableValuesNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/allowableValues",
 document);
+        assertNotNull(allowableValuesNode);
+
+        final List<String> allowableValues = new ArrayList<>();
+        final NodeList valueNodes = allowableValuesNode.getChildNodes();
+        for (int i = 0; i < valueNodes.getLength(); i++) {
+            final Node valueNode = valueNodes.item(i);
+            if ("allowableValue".equals(valueNode.getNodeName())) {
+                final Node valueTextNode = findNode("value", valueNode);
+                if (valueTextNode != null) {
+                    allowableValues.add(valueTextNode.getTextContent());
+                }
+            }
+        }
+
+        assertEquals(List.of("TCP", "UDP"), allowableValues);
+    }
+
+    @Test
+    void testWriteConnectorWithPropertyDependencies() throws Exception {
+        final Connector connector = new ConnectorWithPropertyDependencies();
+        final Document document = writeDocumentation(connector);
+
+        final Node dependenciesNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property[name='Port']/dependencies",
 document);
+        assertNotNull(dependenciesNode);
+
+        final Node dependencyNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property[name='Port']/dependencies/dependency",
 document);
+        assertNotNull(dependencyNode);
+
+        final Node dependencyPropertyNameNode = findNode(
+                
"/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property[name='Port']/dependencies/dependency/propertyName",
+                document);
+        assertNotNull(dependencyPropertyNameNode);
+        assertEquals("Hostname", dependencyPropertyNameNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithStepDependencies() throws Exception {
+        final Connector connector = new ConnectorWithStepDependencies();
+        final Document document = writeDocumentation(connector);
+
+        final Node stepDependenciesNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step 
Two']/stepDependencies", document);
+        assertNotNull(stepDependenciesNode);
+
+        final Node stepDependencyNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step 
Two']/stepDependencies/stepDependency", document);
+        assertNotNull(stepDependencyNode);
+
+        final Node stepNameNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step 
Two']/stepDependencies/stepDependency/stepName", document);
+        assertNotNull(stepNameNode);
+        assertEquals("Step One", stepNameNode.getTextContent());
+
+        final Node propertyNameNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step 
Two']/stepDependencies/stepDependency/propertyName", document);
+        assertNotNull(propertyNameNode);
+        assertEquals("Enable Advanced", propertyNameNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithFetchableAllowableValues() throws Exception {
+        final Connector connector = new 
ConnectorWithFetchableAllowableValues();
+        final Document document = writeDocumentation(connector);
+
+        final Node allowableValuesFetchableNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/allowableValuesFetchable",
 document);
+        assertNotNull(allowableValuesFetchableNode);
+        assertEquals("true", allowableValuesFetchableNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithNoDescription() throws Exception {
+        final Connector connector = new MinimalConnector();
+        final Document document = writeDocumentation(connector);
+
+        final Node descriptionNode = findNode("/extension/description", 
document);
+        assertNull(descriptionNode);
+    }
+
+    @Test
+    void testWriteConnectorWithNoTags() throws Exception {
+        final Connector connector = new MinimalConnector();
+        final Document document = writeDocumentation(connector);
+
+        final Node tagsNode = findNode("/extension/tags", document);
+        assertNull(tagsNode);
+    }
+
+    @Test
+    void testWriteConnectorWithPropertyDependencyValues() throws Exception {
+        final Connector connector = new 
ConnectorWithPropertyDependencyValues();
+        final Document document = writeDocumentation(connector);
+
+        final String xpath = 
"/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup"
+            + "/properties/property[name='Advanced 
Setting']/dependencies/dependency/dependentValues";
+        final Node dependentValuesNode = findNode(xpath, document);
+        assertNotNull(dependentValuesNode);
+
+        final List<String> dependentValues = new ArrayList<>();
+        final NodeList valueNodes = dependentValuesNode.getChildNodes();
+        for (int i = 0; i < valueNodes.getLength(); i++) {
+            final Node valueNode = valueNodes.item(i);
+            if ("dependentValue".equals(valueNode.getNodeName())) {
+                dependentValues.add(valueNode.getTextContent());
+            }
+        }
+
+        assertEquals(2, dependentValues.size());
+        assertTrue(dependentValues.contains("advanced"));
+        assertTrue(dependentValues.contains("expert"));
+    }
+
+    @Test
+    void testWriteConnectorWithStepDependencyValues() throws Exception {
+        final Connector connector = new ConnectorWithStepDependencyValues();
+        final Document document = writeDocumentation(connector);
+
+        final Node dependentValuesNode = 
findNode("/extension/configurationSteps/configurationStep[name='Step 
Two']/stepDependencies/stepDependency/dependentValues", document);
+        assertNotNull(dependentValuesNode);
+
+        final List<String> dependentValues = new ArrayList<>();
+        final NodeList valueNodes = dependentValuesNode.getChildNodes();
+        for (int i = 0; i < valueNodes.getLength(); i++) {
+            final Node valueNode = valueNodes.item(i);
+            if ("dependentValue".equals(valueNode.getNodeName())) {
+                dependentValues.add(valueNode.getTextContent());
+            }
+        }
+
+        assertEquals(1, dependentValues.size());
+        assertEquals("true", dependentValues.getFirst());
+    }
+
+    @Test
+    void testWriteConnectorWithMultipleConfigurationSteps() throws Exception {
+        final Connector connector = new ConnectorWithMultipleSteps();
+        final Document document = writeDocumentation(connector);
+
+        final Node configStepsNode = findNode("/extension/configurationSteps", 
document);
+        assertNotNull(configStepsNode);
+
+        final List<String> stepNames = new ArrayList<>();
+        final NodeList stepNodes = configStepsNode.getChildNodes();
+        for (int i = 0; i < stepNodes.getLength(); i++) {
+            final Node stepNode = stepNodes.item(i);
+            if ("configurationStep".equals(stepNode.getNodeName())) {
+                final Node nameNode = findNode("name", stepNode);
+                if (nameNode != null) {
+                    stepNames.add(nameNode.getTextContent());
+                }
+            }
+        }
+
+        assertEquals(List.of("Connection", "Authentication", "Advanced"), 
stepNames);
+    }
+
+    @Test
+    void testWriteConnectorWithIntegerPropertyType() throws Exception {
+        final Connector connector = new ConnectorWithIntegerProperty();
+        final Document document = writeDocumentation(connector);
+
+        final Node propertyTypeNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/propertyType",
 document);
+        assertNotNull(propertyTypeNode);
+        assertEquals("INTEGER", propertyTypeNode.getTextContent());
+    }
+
+    @Test
+    void testWriteConnectorWithBooleanPropertyType() throws Exception {
+        final Connector connector = new ConnectorWithBooleanProperty();
+        final Document document = writeDocumentation(connector);
+
+        final Node propertyTypeNode = 
findNode("/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup/properties/property/propertyType",
 document);
+        assertNotNull(propertyTypeNode);
+        assertEquals("BOOLEAN", propertyTypeNode.getTextContent());
+    }
+
+    private Node findNode(final String expression, final Node node) throws 
XPathExpressionException {
+        final XPathFactory factory = XPathFactory.newInstance();
+        final XPath path = factory.newXPath();
+
+        return path.evaluateExpression(expression, node, Node.class);
+    }
+
+    private void assertExtensionNameTypeFound(final Connector connector, final 
ExtensionType expectedExtensionType, final Document document) {
+        assertNotNull(document);
+
+        final Node extensionNode = document.getFirstChild();
+        assertEquals("extension", extensionNode.getNodeName());
+
+        final Node nameNode = extensionNode.getFirstChild();
+        assertEquals("name", nameNode.getNodeName());
+        assertEquals(connector.getClass().getName(), 
nameNode.getTextContent());
+
+        final Node typeNode = nameNode.getNextSibling();
+        assertEquals("type", typeNode.getNodeName());
+        assertEquals(expectedExtensionType.name(), typeNode.getTextContent());
+    }
+
+    private Document writeDocumentation(final Connector connector) throws 
Exception {
+        final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+        final DocumentBuilderFactory documentBuilderFactory = 
DocumentBuilderFactory.newInstance();
+        final DocumentBuilder documentBuilder = 
documentBuilderFactory.newDocumentBuilder();
+        final Document document = documentBuilder.newDocument();
+
+        final DOMResult result = new DOMResult(document);
+        final XMLStreamWriter streamWriter = 
outputFactory.createXMLStreamWriter(result);
+
+        final XmlConnectorDocumentationWriter documentationWriter = new 
XmlConnectorDocumentationWriter(streamWriter);
+
+        try {
+            documentationWriter.initialize(connector);
+            documentationWriter.write(connector);
+        } finally {
+            streamWriter.close();
+        }
+
+        return document;
+    }
+
+    // Test Connector implementations
+
+    private static class MinimalConnector extends AbstractConnector {
+        @Override
+        public VersionedExternalFlow getInitialFlow() {
+            return null;
+        }
+
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        protected void onStepConfigured(final String stepName, final 
FlowContext workingContext) {
+        }
+
+        @Override
+        public void applyUpdate(final FlowContext workingFlowContext, final 
FlowContext activeFlowContext) {
+        }
+
+        @Override
+        public List<ConfigVerificationResult> verifyConfigurationStep(final 
String stepName, final Map<String, String> overrides, final FlowContext 
flowContext) {
+            return Collections.emptyList();
+        }
+    }
+
+    @CapabilityDescription("A connector with a description")
+    private static class DescribedConnector extends MinimalConnector {
+    }
+
+    @Tags({"test", "documentation", "connector"})
+    private static class TaggedConnector extends MinimalConnector {
+    }
+
+    @DeprecationNotice(reason = "Use a different connector")
+    private static class DeprecatedConnector extends MinimalConnector {
+    }
+
+    @SeeAlso(classNames = 
"org.apache.nifi.documentation.xml.XmlConnectorDocumentationWriterTest$MinimalConnector")
+    private static class SeeAlsoConnector extends MinimalConnector {
+    }
+
+    private static class ConnectorWithConfigurationSteps extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Step One")
+                    .description("First configuration step")
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithStepWithoutDescription extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Minimal Step")
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithPropertyGroups extends MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Connection Settings")
+                .description("Settings for connection")
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithProperties extends MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Hostname")
+                .description("The hostname to connect to")
+                .required(true)
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Connection Settings")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithPropertyDefaultValue extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Hostname")
+                .description("The hostname to connect to")
+                .defaultValue("localhost")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Connection Settings")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithAllowableValues extends MinimalConnector 
{
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Protocol")
+                .description("The protocol to use")
+                .allowableValues(
+                    new AllowableValue("TCP", "TCP", "Transmission Control 
Protocol"),
+                    new AllowableValue("UDP", "UDP", "User Datagram Protocol")
+                )
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Protocol Settings")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Protocol Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithPropertyDependencies extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor hostnameProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Hostname")
+                .description("The hostname to connect to")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyDescriptor portProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Port")
+                .description("The port to connect to")
+                .type(PropertyType.INTEGER)
+                .dependsOn(hostnameProperty)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Connection Settings")
+                .addProperty(hostnameProperty)
+                .addProperty(portProperty)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithStepDependencies extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor enableAdvanced = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Enable Advanced")
+                .description("Enable advanced settings")
+                .type(PropertyType.BOOLEAN)
+                .build();
+
+            final ConnectorPropertyGroup group1 = 
ConnectorPropertyGroup.builder()
+                .name("Basic Settings")
+                .addProperty(enableAdvanced)
+                .build();
+
+            final ConfigurationStep step1 = new ConfigurationStep.Builder()
+                .name("Step One")
+                .propertyGroups(List.of(group1))
+                .build();
+
+            final ConnectorPropertyDescriptor advancedProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Advanced Setting")
+                .description("An advanced setting")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group2 = 
ConnectorPropertyGroup.builder()
+                .name("Advanced Settings")
+                .addProperty(advancedProperty)
+                .build();
+
+            final ConfigurationStep step2 = new ConfigurationStep.Builder()
+                .name("Step Two")
+                .propertyGroups(List.of(group2))
+                .dependsOn(step1, enableAdvanced, "true")
+                .build();
+
+            return List.of(step1, step2);
+        }
+    }
+
+    private static class ConnectorWithFetchableAllowableValues extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Dynamic Options")
+                .description("Options that are fetched dynamically")
+                .allowableValuesFetchable(true)
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Dynamic Settings")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Dynamic Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithPropertyDependencyValues extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor modeProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Mode")
+                .description("The mode of operation")
+                .type(PropertyType.STRING)
+                .allowableValues("basic", "advanced", "expert")
+                .build();
+
+            final ConnectorPropertyDescriptor advancedProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Advanced Setting")
+                .description("An advanced setting")
+                .type(PropertyType.STRING)
+                .dependsOn(modeProperty, "advanced", "expert")
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Settings")
+                .addProperty(modeProperty)
+                .addProperty(advancedProperty)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Settings Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithStepDependencyValues extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor enableAdvanced = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Enable Advanced")
+                .description("Enable advanced settings")
+                .type(PropertyType.BOOLEAN)
+                .allowableValues("true", "false")
+                .defaultValue("false")
+                .build();
+
+            final ConnectorPropertyGroup group1 = 
ConnectorPropertyGroup.builder()
+                .name("Basic Settings")
+                .addProperty(enableAdvanced)
+                .build();
+
+            final ConfigurationStep step1 = new ConfigurationStep.Builder()
+                .name("Step One")
+                .propertyGroups(List.of(group1))
+                .build();
+
+            final ConnectorPropertyDescriptor advancedProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Advanced Setting")
+                .description("An advanced setting")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyGroup group2 = 
ConnectorPropertyGroup.builder()
+                .name("Advanced Settings")
+                .addProperty(advancedProperty)
+                .build();
+
+            final ConfigurationStep step2 = new ConfigurationStep.Builder()
+                .name("Step Two")
+                .propertyGroups(List.of(group2))
+                .dependsOn(step1, enableAdvanced, "true")
+                .build();
+
+            return List.of(step1, step2);
+        }
+    }
+
+    private static class ConnectorWithMultipleSteps extends MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyGroup connectionGroup = 
ConnectorPropertyGroup.builder()
+                .name("Connection")
+                .build();
+
+            final ConnectorPropertyGroup authGroup = 
ConnectorPropertyGroup.builder()
+                .name("Auth")
+                .build();
+
+            final ConnectorPropertyGroup advancedGroup = 
ConnectorPropertyGroup.builder()
+                .name("Options")
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection")
+                    .description("Connection settings")
+                    .propertyGroups(List.of(connectionGroup))
+                    .build(),
+                new ConfigurationStep.Builder()
+                    .name("Authentication")
+                    .description("Authentication settings")
+                    .propertyGroups(List.of(authGroup))
+                    .build(),
+                new ConfigurationStep.Builder()
+                    .name("Advanced")
+                    .description("Advanced settings")
+                    .propertyGroups(List.of(advancedGroup))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithIntegerProperty extends MinimalConnector 
{
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Port")
+                .description("The port number")
+                .type(PropertyType.INTEGER)
+                .defaultValue("8080")
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Connection")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Connection Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithBooleanProperty extends MinimalConnector 
{
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor property = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Enabled")
+                .description("Whether the feature is enabled")
+                .type(PropertyType.BOOLEAN)
+                .defaultValue("true")
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Settings")
+                .addProperty(property)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Settings Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+}
+

Reply via email to