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

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


The following commit(s) were added to refs/heads/main by this push:
     new 66f45b0  NIFI-15155 Added ListenPort API (#26)
66f45b0 is described below

commit 66f45b0739748a48c65485a4dd0cc6c0e8756191
Author: Kevin Doran <[email protected]>
AuthorDate: Tue Nov 4 12:41:15 2025 -0500

    NIFI-15155 Added ListenPort API (#26)
    
    - Add ListenPortDefinition to PropertyDescriptor that will be serialized as 
part of flow components
    - Add ListenComponent interface that components can implement to provide 
bridge to framework
    - Update XML Manifest Writer to support PropertyDescriptors with 
ListenPortDefinitions
    - Update unit tests
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../apache/nifi/components/PropertyDescriptor.java |  37 +++++++
 .../nifi/components/listen/ListenComponent.java    |  42 ++++++++
 .../apache/nifi/components/listen/ListenPort.java  |  61 +++++++++++
 .../components/listen/ListenPortDefinition.java    |  74 +++++++++++++
 .../nifi/components/listen/StandardListenPort.java | 117 +++++++++++++++++++++
 .../listen/StandardListenPortDefinition.java       |  82 +++++++++++++++
 .../nifi/components/listen/TransportProtocol.java  |  25 +++++
 .../documentation/xml/XmlDocumentationWriter.java  |  15 +++
 .../nifi/flow/VersionedListenPortDefinition.java   |  52 +++++++++
 .../nifi/flow/VersionedPropertyDescriptor.java     |  10 ++
 .../nifi/processor/util/StandardValidators.java    |   3 +
 .../nifi/components/TestPropertyDescriptor.java    |  59 +++++++++++
 .../xml/XmlDocumentationWriterTest.java            |  32 ++++++
 13 files changed, 609 insertions(+)

diff --git a/src/main/java/org/apache/nifi/components/PropertyDescriptor.java 
b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
index 213584d..91dd576 100644
--- a/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
+++ b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
@@ -16,6 +16,9 @@
  */
 package org.apache.nifi.components;
 
+import org.apache.nifi.components.listen.ListenPortDefinition;
+import org.apache.nifi.components.listen.StandardListenPortDefinition;
+import org.apache.nifi.components.listen.TransportProtocol;
 import org.apache.nifi.components.resource.ResourceCardinality;
 import org.apache.nifi.components.resource.ResourceDefinition;
 import org.apache.nifi.components.resource.ResourceReference;
@@ -117,6 +120,11 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
      */
     private final ResourceDefinition resourceDefinition;
 
+    /**
+     * Metadata about the listen port that this property specifies, if 
applicable
+     */
+    private final ListenPortDefinition listenPortDefinition;
+
     protected PropertyDescriptor(final Builder builder) {
         this.displayName = builder.displayName == null ? builder.name : 
builder.displayName;
         this.name = builder.name;
@@ -132,6 +140,7 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
         this.validators = List.copyOf(builder.validators);
         this.dependencies = builder.dependencies == null ? 
Collections.emptySet() : Set.copyOf(builder.dependencies);
         this.resourceDefinition = builder.resourceDefinition;
+        this.listenPortDefinition = builder.listenPortDefinition;
     }
 
     @Override
@@ -217,6 +226,7 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
         private boolean dynamicallyModifiesClasspath = false;
         private Class<? extends ControllerService> controllerServiceDefinition;
         private ResourceDefinition resourceDefinition;
+        private ListenPortDefinition listenPortDefinition;
         private List<Validator> validators = new ArrayList<>();
 
         public Builder fromPropertyDescriptor(final PropertyDescriptor 
specDescriptor) {
@@ -234,6 +244,7 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
             this.validators = new ArrayList<>(specDescriptor.validators);
             this.dependencies = new HashSet<>(specDescriptor.dependencies);
             this.resourceDefinition = specDescriptor.resourceDefinition;
+            this.listenPortDefinition = specDescriptor.listenPortDefinition;
             return this;
         }
 
@@ -577,6 +588,28 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
             return this;
         }
 
+        /**
+         * Specifies that this property defines a numbered host port that a 
server will bind to and listen for client-initiated connections.
+         * <p>
+         * This enables discoverability of Listen Ports when deploying NiFi as 
part of a system, which can simplify the dynamic creation of external network 
components that need to facilitate
+         * inbound connections to NiFi, such as gateways, ingress controllers, 
load balancers, and reverse proxies.
+         * <p>
+         * See {@link ListenPortDefinition} for guidance on how to specify 
protocols.
+         * <p>
+         * Properties that identify Listen Ports should use the PORT_VALIDATOR 
from {@link org.apache.nifi.processor.util.StandardValidators} to guarantee the 
value is a valid port number.
+         *
+         * @param transportProtocol     specifies the layer 4 protocol used at 
the host operating system level for the port specified by this Property.
+         * @param applicationProtocols  optionally specifies one or more layer 
7 protocols supported by the NiFi component listening on the port specified by 
this Property.
+         * @return the builder
+         */
+        public Builder identifiesListenPort(final TransportProtocol 
transportProtocol, final String... applicationProtocols) {
+            Objects.requireNonNull(transportProtocol);
+            final List<String> appProtocols = applicationProtocols != null ? 
Arrays.asList(applicationProtocols) : new ArrayList<>();
+
+            this.listenPortDefinition = new 
StandardListenPortDefinition(transportProtocol, appProtocols);
+            return this;
+        }
+
         /**
          * Establishes a relationship between this Property and the given 
property by declaring that this Property is only relevant if the given Property 
has a non-null value.
          * Furthermore, if one or more explicit Allowable Values are provided, 
this Property will not be relevant unless the given Property's value is equal 
to one of the given Allowable Values.
@@ -756,6 +789,10 @@ public final class PropertyDescriptor implements 
Comparable<PropertyDescriptor>
         return resourceDefinition;
     }
 
+    public ListenPortDefinition getListenPortDefinition() {
+        return listenPortDefinition;
+    }
+
     @Override
     public boolean equals(final Object other) {
         if (this == other) {
diff --git 
a/src/main/java/org/apache/nifi/components/listen/ListenComponent.java 
b/src/main/java/org/apache/nifi/components/listen/ListenComponent.java
new file mode 100644
index 0000000..96dee82
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/listen/ListenComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.components.listen;
+
+import org.apache.nifi.controller.ConfigurationContext;
+
+import java.util.List;
+
+/**
+ * An extension component (e.g., Processor or ControllerService) that creates 
one or more ingress network ports.
+ * <p>
+ *   Implementing this interface allows {@link ListenPort}s provided by this 
component to be dynamically discoverable by the framework.
+ * </p>
+ * <p>
+ *   Typically, components implementing this interface should have at least 
one property described using a {@link 
org.apache.nifi.components.PropertyDescriptor}
+ *   that identifies a {@link ListenPortDefinition}. The Property Descriptor 
identifies a possible Listen Port that could be created.
+ *   This interface provides actual the ports configured based on component 
property values, along with additional ingress metadata.
+ * </p>
+ */
+public interface ListenComponent {
+
+    /**
+     * A list of listen ports provided by this component based on its current 
configuration.
+     *
+     * @param context provides access to convenience methods for obtaining 
property values
+     * @return a list of zero or more listen ports that are actively 
configured to be provided by this component.
+     */
+    List<ListenPort> getListenPorts(final ConfigurationContext context);
+}
diff --git a/src/main/java/org/apache/nifi/components/listen/ListenPort.java 
b/src/main/java/org/apache/nifi/components/listen/ListenPort.java
new file mode 100644
index 0000000..df52889
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/listen/ListenPort.java
@@ -0,0 +1,61 @@
+/*
+ * 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.components.listen;
+
+import java.util.List;
+
+/**
+ * Represents a dynamically discoverable ingress port provided by a {@link 
ListenComponent}.
+ */
+public interface ListenPort {
+
+    /**
+     * Get the operating system numbered port that is listening for network 
traffic.
+     *
+     * @return the port number
+     */
+    int getPortNumber();
+
+    /**
+     * Get the name of the listen port.
+     *
+     * @return A descriptive name of the listen port. Useful for {@link 
ListenComponent}s that provide more than one port.
+     */
+    String getPortName();
+
+    /**
+     * Get the layer 4 transport protocol that is used at the OS networking 
level for this port.
+     *
+     * @return the transport protocol
+     */
+    TransportProtocol getTransportProtocol();
+
+    /**
+     * Get the currently configured application protocols that this port 
supports.
+     * <p>
+     *   Note that this is not always the same as the application protocols 
that could be supported. For example, if this port could support http/1.1 or h2 
(HTTP 2),
+     *   but is currently configured to require h2, then this method should 
return [h2], not [http1.1, h2].
+     * </p>
+     * <p>
+     *   See {@link ListenPortDefinition#getApplicationProtocols()} for 
guidance on application protocol string values.
+     *   This method should return a subset of application protocol values 
specified by the corresponding PropertyDescriptor {@link ListenPortDefinition}.
+     * </p>
+     *
+     * @return the application protocols supported by this listen port, if 
applicable; otherwise an empty list.
+     */
+    List<String> getApplicationProtocols();
+
+}
diff --git 
a/src/main/java/org/apache/nifi/components/listen/ListenPortDefinition.java 
b/src/main/java/org/apache/nifi/components/listen/ListenPortDefinition.java
new file mode 100644
index 0000000..3d32caa
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/listen/ListenPortDefinition.java
@@ -0,0 +1,74 @@
+/*
+ * 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.components.listen;
+
+import java.util.List;
+
+/**
+ * Defines the number and types of resources that allowed to be referenced by 
a component property
+ */
+public interface ListenPortDefinition {
+
+    /**
+     * Specifies the transport protocol that is used for communication with 
this listen port.
+     *
+     * @return the {@link TransportProtocol} enum value
+     */
+    TransportProtocol getTransportProtocol();
+
+    /**
+     * Specifies zero, one, or many application protocols that could be 
supported on this listen port.
+     * <p>
+     * This is used as a hint for NiFi runtimes and environments to offer 
richer behavior (such as configuration or validation) for application protocols 
they understand.
+     * If more than one application protocol could be supported, but is 
decided at runtime based on configuration, this method should return all 
possible application protocols.
+     * Inspecting the component with a Listen Port at runtime can determine 
more details about what has been configured.
+     * <p>
+     * General guidance for application protocol string values:
+     * <ol>
+     *     <li>
+     *       Use IANA names when possible. For example:
+     *        <p>
+     *        <a 
href="https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml";>
+     *          IANA Service Name and Transport Protocol Port Number Registry
+     *        </a>
+     *        <p>
+     *        <a 
href="https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids";>
+     *          IANA TLS Application-Layer Protocol Negotiation (ALPN) 
Protocol IDs
+     *        </a>
+     *        <p>
+     *        <a 
href="https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml";>
+     *          IANA Uniform Resource Identifier (URI) Schemes
+     *        </a>
+     *     </li>
+     *     <li>
+     *       Do not include TLS variants of protocols. NiFi Listen Processors 
generally support TLS when possible, and the SSLContextProvider configuration 
is enough information to infer if the app
+     *      protocol is using TLS. For example, there is no need to include 
wss for Websocket over TLS or h2c for HTTP/2 over TCP without TLS.
+     *     </li>
+     *     <li>
+     *       For application protocols built on HTTP, such as gPRC, use or 
include the foundational HTTP protocol(s) in the application protocol list for 
the ListenPortDefinition.
+     *       Protocols built on HTTP usually are just specifications for 
structuring data payloads within HTTP requests, but the HTTP request semantics 
are likely the most important aspect for system
+     *       components that will be discovering NiFi Listen Ports, such as 
ingress controllers, load balancers, gateways, proxies, etc. Data payload 
structure is usually only important to a NiFi
+     *      component, not networking components external to NiFi. You may 
also include application protocol(s) layered atop HTTP that are relevant to the 
Listen Port, if applicable.
+     *      For example: ["http/1.1", "h2", "grpc"]
+     *     </li>
+     * </ol>
+     *
+     * @return one or more application protocols that could be supported by 
the processor,
+     * or an empty list if no application protocols are known to be supported.
+     */
+    List<String> getApplicationProtocols();
+
+}
diff --git 
a/src/main/java/org/apache/nifi/components/listen/StandardListenPort.java 
b/src/main/java/org/apache/nifi/components/listen/StandardListenPort.java
new file mode 100644
index 0000000..4fcc8e2
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/listen/StandardListenPort.java
@@ -0,0 +1,117 @@
+/*
+ * 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.components.listen;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class StandardListenPort implements ListenPort {
+
+    private final String portName;
+    private final int portNumber;
+    private final TransportProtocol transportProtocol;
+    private final List<String> applicationProtocols;
+
+    private StandardListenPort(final Builder builder) {
+        Objects.requireNonNull(builder.portName, "Port name is required");
+        Objects.requireNonNull(builder.transportProtocol, "Transport protocol 
is required");
+        Objects.requireNonNull(builder.applicationProtocols, "Application 
protocols is required. Use empty list if there are no application protocols.");
+
+        this.portName = builder.portName;
+        this.portNumber = builder.portNumber;
+        this.transportProtocol = builder.transportProtocol;
+        this.applicationProtocols = builder.applicationProtocols;
+    }
+
+    @Override
+    public int getPortNumber() {
+        return portNumber;
+    }
+
+    @Override
+    public String getPortName() {
+        return portName;
+    }
+
+    @Override
+    public TransportProtocol getTransportProtocol() {
+        return transportProtocol;
+    }
+
+    @Override
+    public List<String> getApplicationProtocols() {
+        return applicationProtocols;
+    }
+
+    @Override
+    public String toString() {
+        return "StandardListenPort[portName=%s, portNumber=%s, 
transportProtocol=%s, applicationProtocols=%s]".formatted(portName, portNumber, 
transportProtocol, applicationProtocols);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final StandardListenPort that = (StandardListenPort) o;
+        return portNumber == that.portNumber
+            && Objects.equals(portName, that.portName)
+            && transportProtocol == that.transportProtocol
+            && Objects.equals(applicationProtocols, that.applicationProtocols);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(portName, portNumber, transportProtocol, 
applicationProtocols);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+        private String portName;
+        private int portNumber;
+        private TransportProtocol transportProtocol;
+        private List<String> applicationProtocols = Collections.emptyList();
+
+        public Builder portNumber(final int portNumber) {
+            this.portNumber = portNumber;
+            return this;
+        }
+
+        public Builder portName(final String portName) {
+            this.portName = portName;
+            return this;
+        }
+
+        public Builder transportProtocol(final TransportProtocol 
transportProtocol) {
+            this.transportProtocol = transportProtocol;
+            return this;
+        }
+
+        public Builder applicationProtocols(final List<String> 
applicationProtocols) {
+            this.applicationProtocols = applicationProtocols != null ? 
applicationProtocols : Collections.emptyList();
+            return this;
+        }
+
+        public StandardListenPort build() {
+            return new StandardListenPort(this);
+        }
+    }
+
+}
diff --git 
a/src/main/java/org/apache/nifi/components/listen/StandardListenPortDefinition.java
 
b/src/main/java/org/apache/nifi/components/listen/StandardListenPortDefinition.java
new file mode 100644
index 0000000..cf9f126
--- /dev/null
+++ 
b/src/main/java/org/apache/nifi/components/listen/StandardListenPortDefinition.java
@@ -0,0 +1,82 @@
+/*
+ * 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.components.listen;
+
+import org.apache.nifi.components.PropertyDescriptor;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a Listen Port definition for a {@link PropertyDescriptor} that 
specifies a listen port
+ */
+public class StandardListenPortDefinition implements ListenPortDefinition {
+
+    private final TransportProtocol transportProtocol;
+    private final List<String> applicationProtocols;
+
+    /**
+     * Create a {@link ListenPortDefinition}.
+     *
+     * @param transportProtocol - the layer 4 transport protocol used by the 
listen port
+     * @param applicationProtocols - application protocols supported by the 
listen port or empty list
+     */
+    public StandardListenPortDefinition(TransportProtocol transportProtocol, 
List<String> applicationProtocols) {
+        Objects.requireNonNull(transportProtocol, "Transport protocol is 
required.");
+        Objects.requireNonNull(applicationProtocols, "Application protocols or 
empty list is required.");
+
+        this.transportProtocol = transportProtocol;
+        this.applicationProtocols = applicationProtocols;
+    }
+
+    /**
+     * Create a {@link ListenPortDefinition} without any application protocols.
+     *
+     * @param transportProtocol - the layer 4 transport protocol used by the 
listen port
+     */
+    public StandardListenPortDefinition(final TransportProtocol 
transportProtocol) {
+        this(transportProtocol, Collections.emptyList());
+    }
+
+    @Override
+    public TransportProtocol getTransportProtocol() {
+        return transportProtocol;
+    }
+
+    @Override
+    public List<String> getApplicationProtocols() {
+        return applicationProtocols;
+    }
+
+    @Override
+    public String toString() {
+        return "StandardListenPortDefinition[transportProtocol=%s, 
applicationProtocols=%s]".formatted(transportProtocol, applicationProtocols);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final StandardListenPortDefinition that = 
(StandardListenPortDefinition) o;
+        return transportProtocol == that.transportProtocol && 
Objects.equals(applicationProtocols, that.applicationProtocols);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(transportProtocol, applicationProtocols);
+    }
+}
diff --git 
a/src/main/java/org/apache/nifi/components/listen/TransportProtocol.java 
b/src/main/java/org/apache/nifi/components/listen/TransportProtocol.java
new file mode 100644
index 0000000..6d8a89d
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/listen/TransportProtocol.java
@@ -0,0 +1,25 @@
+/*
+ * 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.components.listen;
+
+/**
+ * Allowed values for ListenPortDefinition transportProtocol field.
+ * Identifies the layer 4 protocol used for the port number at the host 
operating system level.
+ */
+public enum TransportProtocol {
+    TCP,
+    UDP
+}
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 dc3770b..4b6c244 100644
--- 
a/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
+++ 
b/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
@@ -56,6 +56,7 @@ import org.apache.nifi.annotation.documentation.SeeAlso;
 import org.apache.nifi.annotation.documentation.UseCase;
 import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.listen.ListenPortDefinition;
 import org.apache.nifi.components.PropertyDependency;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.RequiredPermission;
@@ -202,6 +203,7 @@ public class XmlDocumentationWriter extends 
AbstractDocumentationWriter {
         writeBooleanElement("dynamicallyModifiesClasspath", 
property.isDynamicClasspathModifier());
         writeBooleanElement("dynamic", property.isDynamic());
         writeResourceDefinition(property.getResourceDefinition());
+        writeListenPortDefinition(property.getListenPortDefinition());
         writeDependencies(property);
 
         writeEndElement();
@@ -227,6 +229,19 @@ public class XmlDocumentationWriter extends 
AbstractDocumentationWriter {
         writeEndElement();
     }
 
+    private void writeListenPortDefinition(final ListenPortDefinition 
listenPortDefinition) throws IOException {
+        if (listenPortDefinition == null) {
+            return;
+        }
+
+        writeStartElement("listenPortDefinition");
+
+        writeTextElement("transportProtocol", 
listenPortDefinition.getTransportProtocol().name());
+        writeTextArray("applicationProtocols", "applicationProtocol", 
listenPortDefinition.getApplicationProtocols());
+
+        writeEndElement();
+    }
+
     private void writeResourceType(final ResourceType resourceType) throws 
IOException {
         writeTextElement("resourceType", resourceType.name());
     }
diff --git 
a/src/main/java/org/apache/nifi/flow/VersionedListenPortDefinition.java 
b/src/main/java/org/apache/nifi/flow/VersionedListenPortDefinition.java
new file mode 100644
index 0000000..ba0f395
--- /dev/null
+++ b/src/main/java/org/apache/nifi/flow/VersionedListenPortDefinition.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.flow;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.List;
+
+public class VersionedListenPortDefinition {
+
+    private TransportProtocol transportProtocol;
+    private List<String> applicationProtocols;
+
+    @Schema(description = "The transport protocol used by the listen port")
+    public TransportProtocol getTransportProtocol() {
+        return transportProtocol;
+    }
+
+    public void setTransportProtocol(final TransportProtocol 
transportProtocol) {
+        this.transportProtocol = transportProtocol;
+    }
+
+    @Schema(description = "The application protocol(s) that the listen port 
could support (if any)")
+    public List<String> getApplicationProtocols() {
+        return applicationProtocols;
+    }
+
+    public void setApplicationProtocols(final List<String> 
applicationProtocols) {
+        this.applicationProtocols = applicationProtocols;
+    }
+
+
+    public enum TransportProtocol {
+        TCP,
+        UDP
+    }
+}
diff --git 
a/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java 
b/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java
index 1540935..3df1d7e 100644
--- a/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java
+++ b/src/main/java/org/apache/nifi/flow/VersionedPropertyDescriptor.java
@@ -26,6 +26,7 @@ public class VersionedPropertyDescriptor {
     private boolean sensitive;
     private boolean dynamic;
     private VersionedResourceDefinition resourceDefinition;
+    private VersionedListenPortDefinition listenPortDefinition;
 
     @Schema(description = "The name of the property")
     public String getName() {
@@ -80,4 +81,13 @@ public class VersionedPropertyDescriptor {
     public void setResourceDefinition(final VersionedResourceDefinition 
resourceDefinition) {
         this.resourceDefinition = resourceDefinition;
     }
+
+    @Schema(description = "Returns the Listen Port Definition for the port 
this property specifies, if applicable")
+    public VersionedListenPortDefinition getListenPortDefinition() {
+        return listenPortDefinition;
+    }
+
+    public void setListenPortDefinition(final VersionedListenPortDefinition 
listenPortDefinition) {
+        this.listenPortDefinition = listenPortDefinition;
+    }
 }
diff --git 
a/src/main/java/org/apache/nifi/processor/util/StandardValidators.java 
b/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
index f24001a..26d88c7 100644
--- a/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
+++ b/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
@@ -150,6 +150,9 @@ public class StandardValidators {
         }
     };
 
+    /**
+     * {@link Validator} that ensures the value is an integer between 0 and 
65535
+     */
     public static final Validator PORT_VALIDATOR = createLongValidator(0, 
65535, true);
 
     /**
diff --git 
a/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java 
b/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
index c3288e8..7e13b26 100644
--- a/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
+++ b/src/test/java/org/apache/nifi/components/TestPropertyDescriptor.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.components;
 
+import org.apache.nifi.components.listen.ListenPortDefinition;
+import org.apache.nifi.components.listen.TransportProtocol;
 import org.apache.nifi.components.PropertyDescriptor.Builder;
 import org.apache.nifi.components.resource.ResourceCardinality;
 import org.apache.nifi.components.resource.ResourceType;
@@ -25,6 +27,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.List;
@@ -215,6 +218,62 @@ public class TestPropertyDescriptor {
         }
     }
 
+    @Nested
+    class RegardingListenPortDefinitions {
+
+        @Test
+        void testListenPortDefinitionNullByDefault() {
+            final PropertyDescriptor descriptor = new 
PropertyDescriptor.Builder()
+                .name("My Property")
+                .required(false)
+                .build();
+
+            assertNull(descriptor.getListenPortDefinition());
+        }
+
+        @Test
+        void testTcpWithoutApplicationProtocols() {
+            final PropertyDescriptor descriptor = new 
PropertyDescriptor.Builder()
+                .name("TCP Port")
+                .identifiesListenPort(TransportProtocol.TCP)
+                .required(true)
+                .build();
+
+            final ListenPortDefinition actualListenPortDefinition = 
descriptor.getListenPortDefinition();
+
+            assertEquals(TransportProtocol.TCP, 
actualListenPortDefinition.getTransportProtocol());
+            assertEquals(Collections.emptyList(), 
actualListenPortDefinition.getApplicationProtocols());
+        }
+
+        @Test
+        void testTcpWithApplicationProtocols() {
+            final PropertyDescriptor descriptor = new 
PropertyDescriptor.Builder()
+                .name("HTTP Port")
+                .identifiesListenPort(TransportProtocol.TCP, "http/1.1", "h2")
+                .required(true)
+                .build();
+
+            final ListenPortDefinition actualListenPortDefinition = 
descriptor.getListenPortDefinition();
+
+            assertEquals(TransportProtocol.TCP, 
actualListenPortDefinition.getTransportProtocol());
+            assertEquals(List.of("http/1.1", "h2"), 
actualListenPortDefinition.getApplicationProtocols());
+        }
+
+        @Test
+        void testUdpWithApplicationProtocols() {
+            final PropertyDescriptor descriptor = new 
PropertyDescriptor.Builder()
+                .name("Port")
+                .identifiesListenPort(TransportProtocol.UDP, "syslog")
+                .required(true)
+                .build();
+
+            final ListenPortDefinition actualListenPortDefinition = 
descriptor.getListenPortDefinition();
+
+            assertEquals(TransportProtocol.UDP, 
actualListenPortDefinition.getTransportProtocol());
+            assertEquals(List.of("syslog"), 
actualListenPortDefinition.getApplicationProtocols());
+        }
+    }
+
     @Test
     void testDependsOnWithEnumValue() {
         final PropertyDescriptor dependentPropertyDescriptor = new 
PropertyDescriptor.Builder()
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 71e0833..e2ea473 100644
--- 
a/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java
+++ 
b/src/test/java/org/apache/nifi/documentation/xml/XmlDocumentationWriterTest.java
@@ -19,6 +19,7 @@ package org.apache.nifi.documentation.xml;
 import org.apache.nifi.annotation.documentation.DeprecationNotice;
 import org.apache.nifi.components.ConfigurableComponent;
 import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.listen.TransportProtocol;
 import org.apache.nifi.components.resource.ResourceCardinality;
 import org.apache.nifi.components.resource.ResourceType;
 import org.apache.nifi.controller.AbstractControllerService;
@@ -103,6 +104,7 @@ class XmlDocumentationWriterTest {
 
     private static final PropertyDescriptor THIRD_PROPERTY = new 
PropertyDescriptor.Builder()
             .name("Third Property")
+            .identifiesListenPort(TransportProtocol.TCP, "http/1.1", "h2")
             .dependsOn(SECOND_PROPERTY)
             .dependsOn(FIRST_PROPERTY)
             .build();
@@ -137,6 +139,14 @@ class XmlDocumentationWriterTest {
             ResourceType.URL.name()
     );
 
+    private static final String EXPECTED_LISTEN_PORT_TRANSPORT_PROTOCOL = 
TransportProtocol.TCP.name();
+
+    private static final List<String> 
EXPECTED_LISTEN_PORT_APPLICATION_PROTOCOLS = List.of(
+        "http/1.1",
+        "h2"
+    );
+
+
     @Test
     void testWriteMinimalProcessor() throws Exception {
         final Processor processor = new MinimalProcessor();
@@ -190,6 +200,7 @@ class XmlDocumentationWriterTest {
         assertDependentPropertyNamesMatched(document);
         assertDependentPropertyValuesMatched(document);
         assertResourceTypesMatched(document);
+        assertListenPortPropertiesMatched(document);
     }
 
     private void assertRelationshipsMatched(final Document document) throws 
XPathExpressionException {
@@ -283,6 +294,27 @@ class XmlDocumentationWriterTest {
         assertEquals(EXPECTED_RESOURCE_TYPE_NAMES, resourceTypeNames);
     }
 
+    private void assertListenPortPropertiesMatched(final Document document) 
throws XPathExpressionException {
+        final Node listenPortTransportProtocol = 
findNode("/extension/properties/property[name='Third 
Property']/listenPortDefinition/transportProtocol", document);
+        assertNotNull(listenPortTransportProtocol);
+        final String transportProtocol = 
listenPortTransportProtocol.getTextContent();
+        assertEquals(EXPECTED_LISTEN_PORT_TRANSPORT_PROTOCOL, 
transportProtocol);
+
+        final Node listenPortApplicationProtocolsNode = 
findNode("/extension/properties/property[name='Third 
Property']/listenPortDefinition/applicationProtocols", document);
+        assertNotNull(listenPortApplicationProtocolsNode);
+
+        final NodeList listenPortApplicationProtocols = 
listenPortApplicationProtocolsNode.getChildNodes();
+        final List<String> applicationProtocolNames = new ArrayList<>();
+        for (int i = 0; i < listenPortApplicationProtocols.getLength(); i++) {
+            final Node applicationProtocolNode = 
listenPortApplicationProtocols.item(i);
+            assertEquals("applicationProtocol", 
applicationProtocolNode.getNodeName());
+            final String applicationProtocol = 
applicationProtocolNode.getTextContent();
+            applicationProtocolNames.add(applicationProtocol);
+        }
+
+        assertEquals(EXPECTED_LISTEN_PORT_APPLICATION_PROTOCOLS, 
applicationProtocolNames);
+    }
+
 
     private Node findNode(final String expression, final Node node) throws 
XPathExpressionException {
         final XPathFactory factory = XPathFactory.newInstance();


Reply via email to