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

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


The following commit(s) were added to refs/heads/main by this push:
     new 7d29d839c9d NIFI-16316 Guard against null component names in (#11644)
7d29d839c9d is described below

commit 7d29d839c9d956e4530adb570e42c8c6833a5bfb
Author: Sönke Liebau <[email protected]>
AuthorDate: Tue Sep 8 15:56:34 2026 +0200

    NIFI-16316 Guard against null component names in (#11644)
    
    SiteToSiteStatusReportingTask
    
    A connection with a blank name and an empty relationship collection 
produces a ProcessGroupStatus tree containing a ConnectionStatus with a null 
name which causes an NPE on every trigger.
    
    This state is reachable through flow synchronization:
    
    updateConnection applies selectedRelationships verbatim, so a port- or 
funnel-sourced connection whose flow definition carries an empty 
selectedRelationships list loses the anonymous relationship that connection 
creation would substitute.
    
    - AbstractEventAccess: fall back to the connection identifier so the status 
name is never null, protecting all consumers of the controller status
    - SiteToSiteStatusReportingTask: treat a null component name as empty when 
matching filters, as defence in depth for any other status type
    - Add a regression test serializing a status tree that contains a 
connection with a null name
---
 .../reporting/SiteToSiteStatusReportingTask.java   |  2 +-
 .../TestSiteToSiteStatusReportingTask.java         | 21 +++++++++
 .../apache/nifi/reporting/AbstractEventAccess.java |  4 ++
 .../nifi/reporting/AbstractEventAccessTest.java    | 53 ++++++++++++++++++++++
 4 files changed, 79 insertions(+), 1 deletion(-)

diff --git 
a/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteStatusReportingTask.java
 
b/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteStatusReportingTask.java
index e15415ed111..47c1bf12a04 100644
--- 
a/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteStatusReportingTask.java
+++ 
b/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/SiteToSiteStatusReportingTask.java
@@ -221,7 +221,7 @@ public class SiteToSiteStatusReportingTask extends 
AbstractSiteToSiteReportingTa
      */
     private boolean componentMatchesFilters(final String componentType, final 
String componentName) {
         return componentTypeFilter.matcher(componentType).matches()
-                && componentNameFilter.matcher(componentName).matches();
+                && componentNameFilter.matcher(componentName == null ? "" : 
componentName).matches();
     }
 
     /**
diff --git 
a/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
 
b/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
index 9596c77eb6e..258b74f7ff9 100644
--- 
a/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
+++ 
b/nifi-extension-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
@@ -184,6 +184,27 @@ public class TestSiteToSiteStatusReportingTask {
         assertNull(object.get("destinationName"));
     }
 
+    @Test
+    public void testConnectionStatusWithNullName() throws IOException, 
InitializationException {
+        final ProcessGroupStatus pgStatus = generateProcessGroupStatus("root", 
"Awesome", 1, 0);
+        // A connection may carry no name in the status snapshot, e.g. an 
unnamed
+        // connection from a port or funnel whose relationship list is empty
+        pgStatus.getConnectionStatus().iterator().next().setName(null);
+
+        final Map<PropertyDescriptor, String> properties = new HashMap<>();
+        properties.put(SiteToSiteUtils.BATCH_SIZE, "100");
+        
properties.put(SiteToSiteStatusReportingTask.COMPONENT_NAME_FILTER_REGEX, ".*");
+        
properties.put(SiteToSiteStatusReportingTask.COMPONENT_TYPE_FILTER_REGEX, 
"(Connection)");
+
+        MockSiteToSiteStatusReportingTask task = initTask(properties, 
pgStatus);
+        assertDoesNotThrow(() -> task.onTrigger(context));
+
+        // All 12 connections are reported, including the one without a name
+        final String msg = new String(task.dataSent.getFirst(), 
StandardCharsets.UTF_8);
+        JsonReader jsonReader = Json.createReader(new 
ByteArrayInputStream(msg.getBytes()));
+        assertEquals(12, jsonReader.readArray().size());
+    }
+
     @Test
     public void testConnectionStatusWithNullValues() throws IOException, 
InitializationException {
         final ProcessGroupStatus pgStatus = generateProcessGroupStatus("root", 
"Awesome", 1, 0);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
index 69f3cc1ed6d..1720fa5d80e 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
@@ -337,6 +337,10 @@ public abstract class AbstractEventAccess implements 
EventAccess {
                         relationships.add(relationship.getName());
                     }
                     connStatus.setName(StringUtils.join(relationships, ", "));
+                } else {
+                    // Fall back to the identifier so that the status name is 
never null, e.g. for an
+                    // unnamed connection from a port or funnel whose 
relationship list is empty
+                    connStatus.setName(conn.getIdentifier());
                 }
             } else {
                 connStatus.setName(conn.getIdentifier());
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/reporting/AbstractEventAccessTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/reporting/AbstractEventAccessTest.java
index 7661d1996c7..492fd99a6ce 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/reporting/AbstractEventAccessTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/reporting/AbstractEventAccessTest.java
@@ -18,12 +18,17 @@ package org.apache.nifi.reporting;
 
 import org.apache.nifi.action.Action;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.QueueSize;
 import org.apache.nifi.controller.repository.FlowFileEventRepository;
 import org.apache.nifi.controller.repository.RepositoryStatusReport;
 import org.apache.nifi.controller.repository.StandardRepositoryStatusReport;
+import org.apache.nifi.controller.status.ConnectionStatus;
 import org.apache.nifi.controller.status.ProcessGroupStatus;
 import org.apache.nifi.controller.status.ProcessorStatus;
 import org.apache.nifi.controller.status.analytics.StatusAnalyticsEngine;
@@ -40,6 +45,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.function.Predicate;
 
@@ -61,6 +67,8 @@ class AbstractEventAccessTest {
 
     private static final String PROCESSOR_ID = UUID.randomUUID().toString();
 
+    private static final String CONNECTION_ID = UUID.randomUUID().toString();
+
     private static final int ZERO_DEPTH = 0;
 
     private static final int SINGLE_DEPTH = 1;
@@ -83,6 +91,18 @@ class AbstractEventAccessTest {
     @Mock
     private ProcessorNode processorNode;
 
+    @Mock
+    private Connection connection;
+
+    @Mock
+    private Connectable source;
+
+    @Mock
+    private Connectable destination;
+
+    @Mock
+    private FlowFileQueue flowFileQueue;
+
     private AbstractEventAccess eventAccess;
 
     @BeforeEach
@@ -192,6 +212,39 @@ class AbstractEventAccessTest {
         assertEquals(2, authorizables.size());
     }
 
+    @Test
+    void testGetGroupStatusConnectionWithoutNameOrRelationships() {
+        final RepositoryStatusReport repositoryStatusReport = new 
StandardRepositoryStatusReport();
+        final Predicate<Authorizable> checkAuthorization = authorizable -> 
true;
+
+        // The state under test: a connection without a name and without 
relationships, as
+        // produced by flow synchronization applying an empty 
selectedRelationships list to
+        // a port- or funnel-sourced connection
+        when(connection.getIdentifier()).thenReturn(CONNECTION_ID);
+        when(connection.getName()).thenReturn(null);
+        when(connection.getRelationships()).thenReturn(List.of());
+        // Needed to run the test, tested conditions are set above
+        when(connection.getProcessGroup()).thenReturn(processGroup);
+        when(connection.getSource()).thenReturn(source);
+        when(connection.getDestination()).thenReturn(destination);
+        when(connection.getFlowFileQueue()).thenReturn(flowFileQueue);
+        when(flowFileQueue.size()).thenReturn(new QueueSize(0, 0));
+        when(flowFileQueue.getBackPressureDataSizeThreshold()).thenReturn("1 
GB");
+
+        when(processGroup.getConnections()).thenReturn(Set.of(connection));
+        when(processGroup.getName()).thenReturn(PROCESS_GROUP_NAME);
+        when(processGroup.getIdentifier()).thenReturn(PROCESS_GROUP_ID);
+
+        final ProcessGroupStatus groupStatus = 
eventAccess.getGroupStatus(processGroup, repositoryStatusReport, 
checkAuthorization, SINGLE_DEPTH, SINGLE_DEPTH, INCLUDE_CONNECTION_DETAILS);
+
+        assertNotNull(groupStatus);
+        final Optional<ConnectionStatus> connectionStatusFound = 
groupStatus.getConnectionStatus().stream().findFirst();
+        assertTrue(connectionStatusFound.isPresent());
+        // The status name must never be null: an unnamed connection without 
relationships
+        // falls back to the connection identifier
+        assertEquals(CONNECTION_ID, connectionStatusFound.get().getName());
+    }
+
     private static class ConcreteEventAccess extends AbstractEventAccess {
 
         public ConcreteEventAccess(

Reply via email to