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

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


The following commit(s) were added to refs/heads/NIFI-15258 by this push:
     new 8b56c99281 NIFI-15453: Adding an endpoint to return all controller 
services for … (#10770)
8b56c99281 is described below

commit 8b56c99281fc7ff4c245d4c8f1c03f1b4baae809
Author: Matt Gilman <[email protected]>
AuthorDate: Thu Jan 15 14:19:10 2026 -0500

    NIFI-15453: Adding an endpoint to return all controller services for … 
(#10770)
    
    * NIFI-15453: Adding an endpoint to return all controller services for a 
process group in a connector.
    
    * NIFI-15453: Addressing review feedback.
    
    This closes #10770
---
 .../org/apache/nifi/web/NiFiServiceFacade.java     |  3 +
 .../apache/nifi/web/StandardNiFiServiceFacade.java | 22 ++++++
 .../org/apache/nifi/web/api/ConnectorResource.java | 79 ++++++++++++++++++++++
 .../apache/nifi/web/api/TestConnectorResource.java | 34 ++++++++++
 4 files changed, 138 insertions(+)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 09761a5076..fba789287f 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -224,6 +224,9 @@ public interface NiFiServiceFacade {
 
     ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean 
recursive);
 
+    Set<ControllerServiceEntity> getConnectorControllerServices(String 
connectorId, String processGroupId, boolean includeAncestorGroups,
+                                                                boolean 
includeDescendantGroups, boolean includeReferencingComponents);
+
     void verifyCanVerifyConnectorConfigurationStep(String connectorId, String 
configurationStepName);
 
     List<ConfigVerificationResultDTO> 
performConnectorConfigurationStepVerification(String connectorId, String 
configurationStepName, ConfigurationStepConfigurationDTO 
configurationStepConfiguration);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 84087cbe70..323630ad03 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3720,6 +3720,28 @@ public class StandardNiFiServiceFacade implements 
NiFiServiceFacade {
         return entityFactory.createProcessGroupStatusEntity(dto, permissions);
     }
 
+    @Override
+    public Set<ControllerServiceEntity> getConnectorControllerServices(final 
String connectorId, final String processGroupId,
+            final boolean includeAncestorGroups, final boolean 
includeDescendantGroups, final boolean includeReferencingComponents) {
+        final ConnectorNode connectorNode = 
connectorDAO.getConnector(connectorId);
+        final ProcessGroup managedProcessGroup = 
connectorNode.getActiveFlowContext().getManagedProcessGroup();
+        final ProcessGroup targetProcessGroup = 
managedProcessGroup.findProcessGroup(processGroupId);
+        if (targetProcessGroup == null) {
+            throw new ResourceNotFoundException("Process Group with ID " + 
processGroupId + " was not found within Connector " + connectorId);
+        }
+
+        final Set<ControllerServiceNode> serviceNodes = new HashSet<>();
+        
serviceNodes.addAll(targetProcessGroup.getControllerServices(includeAncestorGroups));
+
+        if (includeDescendantGroups) {
+            
serviceNodes.addAll(targetProcessGroup.findAllControllerServices());
+        }
+
+        return serviceNodes.stream()
+                .map(serviceNode -> createControllerServiceEntity(serviceNode, 
includeReferencingComponents))
+                .collect(Collectors.toSet());
+    }
+
     @Override
     public void verifyCanVerifyConnectorConfigurationStep(final String 
connectorId, final String configurationStepName) {
         connectorDAO.verifyCanVerifyConfigurationStep(connectorId, 
configurationStepName);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index c2f7b4cddf..cb5146afdb 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -86,6 +86,8 @@ import 
org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
 import org.apache.nifi.web.api.entity.ConnectorEntity;
 import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
 import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
 import org.apache.nifi.web.api.entity.SearchResultsEntity;
@@ -104,6 +106,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -128,6 +131,7 @@ public class ConnectorResource extends ApplicationResource {
     private NiFiServiceFacade serviceFacade;
     private Authorizer authorizer;
     private FlowResource flowResource;
+    private ControllerServiceResource controllerServiceResource;
     private UploadRequestReplicator uploadRequestReplicator;
 
     private final RequestManager<VerifyConnectorConfigStepRequestEntity, 
List<ConfigVerificationResultDTO>> configVerificationRequestManager =
@@ -1411,6 +1415,76 @@ public class ConnectorResource extends 
ApplicationResource {
         return generateOkResponse(entity).build();
     }
 
+    /**
+     * Retrieves all the controller services in the specified process group 
within a connector.
+     *
+     * @param connectorId The id of the connector
+     * @param processGroupId The process group id within the connector's 
hierarchy
+     * @param includeAncestorGroups Whether to include ancestor process groups
+     * @param includeDescendantGroups Whether to include descendant process 
groups
+     * @param includeReferences Whether to include services' referencing 
components in the response
+     * @return A controllerServicesEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    
@Path("/{connectorId}/flow/process-groups/{processGroupId}/controller-services")
+    @Operation(
+            summary = "Gets all controller services for a process group within 
a connector",
+            responses = {
+                    @ApiResponse(responseCode = "200", content = 
@Content(schema = @Schema(implementation = ControllerServicesEntity.class))),
+                    @ApiResponse(responseCode = "400", description = "NiFi was 
unable to complete the request because it was invalid. The request should not 
be retried without modification."),
+                    @ApiResponse(responseCode = "401", description = "Client 
could not be authenticated."),
+                    @ApiResponse(responseCode = "403", description = "Client 
is not authorized to make this request."),
+                    @ApiResponse(responseCode = "404", description = "The 
specified resource could not be found."),
+                    @ApiResponse(responseCode = "409", description = "The 
request was valid but NiFi was not in the appropriate state to process it.")
+            },
+            security = {
+                    @SecurityRequirement(name = "Read - /connectors/{uuid}")
+            },
+            description = "Returns the controller services for the specified 
process group within the connector's hierarchy. The processGroupId can be " +
+                    "obtained from the managedProcessGroupId field of the 
ConnectorDTO for the root process group, or from child process " +
+                    "groups within the flow."
+    )
+    public Response getControllerServicesFromConnectorProcessGroup(
+            @Parameter(description = "The connector id.", required = true)
+            @PathParam("connectorId") final String connectorId,
+            @Parameter(description = "The process group id.", required = true)
+            @PathParam("processGroupId") final String processGroupId,
+            @Parameter(description = "Whether or not to include 
parent/ancestor process groups")
+            @QueryParam("includeAncestorGroups")
+            @DefaultValue("true") final boolean includeAncestorGroups,
+            @Parameter(description = "Whether or not to include descendant 
process groups")
+            @QueryParam("includeDescendantGroups")
+            @DefaultValue("false") final boolean includeDescendantGroups,
+            @Parameter(description = "Whether or not to include services' 
referencing components in the response")
+            @QueryParam("includeReferencingComponents")
+            @DefaultValue("true") final boolean includeReferences) {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        // authorize access to the connector
+        serviceFacade.authorizeAccess(lookup -> {
+            final Authorizable connector = lookup.getConnector(connectorId);
+            connector.authorize(authorizer, RequestAction.READ, 
NiFiUserUtils.getNiFiUser());
+        });
+
+        // get the controller services for the specified process group within 
the connector's hierarchy
+        final Set<ControllerServiceEntity> controllerServices = 
serviceFacade.getConnectorControllerServices(
+                connectorId, processGroupId, includeAncestorGroups, 
includeDescendantGroups, includeReferences);
+        
controllerServiceResource.populateRemainingControllerServiceEntitiesContent(controllerServices);
+
+        // create the response entity
+        final ControllerServicesEntity entity = new ControllerServicesEntity();
+        entity.setCurrentTime(new Date());
+        entity.setControllerServices(controllerServices);
+
+        // generate the response
+        return generateOkResponse(entity).build();
+    }
+
     /**
      * Retrieves the status for the process group managed by the specified 
connector.
      *
@@ -1710,6 +1784,11 @@ public class ConnectorResource extends 
ApplicationResource {
         this.flowResource = flowResource;
     }
 
+    @Autowired
+    public void setControllerServiceResource(final ControllerServiceResource 
controllerServiceResource) {
+        this.controllerServiceResource = controllerServiceResource;
+    }
+
     @Autowired(required = false)
     public void setUploadRequestReplicator(final UploadRequestReplicator 
uploadRequestReplicator) {
         this.uploadRequestReplicator = uploadRequestReplicator;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index 5c22f82fd1..c218d87d68 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -36,6 +36,8 @@ import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
 import org.apache.nifi.web.api.entity.ConnectorEntity;
 import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
 import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
 import org.apache.nifi.web.api.entity.SecretsEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
@@ -49,6 +51,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.net.URI;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -91,6 +94,9 @@ public class TestConnectorResource {
     @Mock
     private FlowResource flowResource;
 
+    @Mock
+    private ControllerServiceResource controllerServiceResource;
+
     private static final String CONNECTOR_ID = "test-connector-id";
     private static final String CONNECTOR_NAME = "Test Connector";
     private static final String CONNECTOR_TYPE = "TestConnectorType";
@@ -116,6 +122,7 @@ public class TestConnectorResource {
 
         connectorResource.setServiceFacade(serviceFacade);
         connectorResource.setFlowResource(flowResource);
+        
connectorResource.setControllerServiceResource(controllerServiceResource);
         connectorResource.httpServletRequest = httpServletRequest;
         connectorResource.properties = properties;
         connectorResource.uriInfo = uriInfo;
@@ -384,6 +391,33 @@ public class TestConnectorResource {
         verify(serviceFacade, never()).getConnectorFlow(anyString(), 
anyString(), eq(false));
     }
 
+    @Test
+    public void testGetControllerServicesFromConnectorProcessGroup() {
+        final Set<ControllerServiceEntity> controllerServices = Set.of();
+        when(serviceFacade.getConnectorControllerServices(CONNECTOR_ID, 
PROCESS_GROUP_ID, true, false, true)).thenReturn(controllerServices);
+
+        try (Response response = 
connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, 
PROCESS_GROUP_ID, true, false, true)) {
+            assertEquals(200, response.getStatus());
+            final ControllerServicesEntity entity = (ControllerServicesEntity) 
response.getEntity();
+            assertEquals(controllerServices, entity.getControllerServices());
+        }
+
+        verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+        verify(serviceFacade).getConnectorControllerServices(CONNECTOR_ID, 
PROCESS_GROUP_ID, true, false, true);
+        
verify(controllerServiceResource).populateRemainingControllerServiceEntitiesContent(controllerServices);
+    }
+
+    @Test
+    public void 
testGetControllerServicesFromConnectorProcessGroupNotAuthorized() {
+        
doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+        assertThrows(AccessDeniedException.class, () ->
+                
connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, 
PROCESS_GROUP_ID, true, false, true));
+
+        verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+        verify(serviceFacade, 
never()).getConnectorControllerServices(anyString(), anyString(), eq(true), 
eq(false), eq(true));
+    }
+
     private ConnectorEntity createConnectorEntity() {
         final ConnectorEntity entity = new ConnectorEntity();
 

Reply via email to