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(