You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2020/11/24 16:38:16 UTC

[nifi] branch main updated: NIFI-7897: Refactoring to create a new nifi-framework-components module. - Refactored nifi-stateless to make use of nifi-framework-components - Removed requirement for nifi-framework-nar to be provided. - Refactored stateless nifi into api, engine, nar, and bootstrap modules, with a parent 'bundle' module - Creation of nifi-stateless-system-tests - Added unit tests and logging - Changed flow configuration to use properties file instead of json - Allow for -p parameter to specify parameters [...]

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

bbende 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 3c9d8a7  NIFI-7897: Refactoring to create a new nifi-framework-components module. - Refactored nifi-stateless to make use of nifi-framework-components - Removed requirement for nifi-framework-nar to be provided. - Refactored stateless nifi into api, engine, nar, and bootstrap modules, with a parent 'bundle' module - Creation of nifi-stateless-system-tests - Added unit tests and logging - Changed flow configuration to use properties file instead of json - Allow for -p parameter to [...]
3c9d8a7 is described below

commit 3c9d8a7007cab279b0cc6ebd2f56bce7bcddba80
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Thu Oct 8 09:12:00 2020 -0400

    NIFI-7897: Refactoring to create a new nifi-framework-components module.
    - Refactored nifi-stateless to make use of nifi-framework-components
    - Removed requirement for nifi-framework-nar to be provided.
    - Refactored stateless nifi into api, engine, nar, and bootstrap modules, with a parent 'bundle' module
    - Creation of nifi-stateless-system-tests
    - Added unit tests and logging
    - Changed flow configuration to use properties file instead of json
    - Allow for -p parameter to specify parameters on command line
    - Moved implementations of Authorizer, NiFiUser, and UserGroupProviders to new module named nifi-framework-authorization-providers so that those that depend on nifi-framework-authorization don't have to bring in the providers. This way, we can have stateless not bring in those providers, as we otherwise get warnings on startup about the provider already being registered. Additionally, it avoids needing dependencies on spring-security-core
    - Updated bin/nifi.sh script to run new stateless bootstrap
    - Added Reporting Tasks to stateless.
    - Download bundles as necessary on stateless nifi startup
    
    NIFI-7897: Addressing review feedback
    
    NIFI-7897: Fixed typos in README and also addressed issue that caused parameters with spaces not to be parsed properly
    
    This closes #4669.
    
    Signed-off-by: Bryan Bende <bb...@apache.org>
---
 nifi-assembly/pom.xml                              |  11 +
 .../main/java/org/apache/nifi/util/DomUtils.java   |   0
 nifi-docker/pom.xml                                |  23 +-
 nifi-mock/pom.xml                                  |   2 +-
 .../apache/nifi/util/MockValidationContext.java    |   2 +-
 .../nifi-framework-nar/pom.xml                     |  13 +-
 .../src/main/resources/META-INF/NOTICE             |  39 +-
 .../nifi-framework/nifi-authorizer/pom.xml         |   2 +-
 .../pom.xml                                        |  27 +-
 .../CompositeConfigurableUserGroupProvider.java    |   0
 .../nifi/authorization/CompositeUserAndGroups.java |   0
 .../authorization/CompositeUserGroupProvider.java  |   0
 .../StandardAuthorizerConfigurationContext.java    |   0
 .../StandardAuthorizerInitializationContext.java   |   0
 .../authorization/StandardManagedAuthorizer.java   |   0
 .../nifi/authorization/user/NiFiUserDetails.java   |   0
 .../nifi/authorization/user/NiFiUserUtils.java     |   0
 .../org.apache.nifi.authorization.Authorizer       |   0
 ...org.apache.nifi.authorization.UserGroupProvider |   0
 .../authorization/user/NiFiUserUtilsTest.groovy    |   0
 ...CompositeConfigurableUserGroupProviderTest.java |  22 +-
 .../CompositeUserGroupProviderTest.java            |  22 +-
 .../CompositeUserGroupProviderTestBase.java        |  18 +-
 .../SimpleConfigurableUserGroupProvider.java       |   0
 .../authorization/SimpleUserGroupProvider.java     |   0
 .../StandardManagedAuthorizerTest.java             |   7 +-
 .../nifi-framework-authorization/pom.xml           |  22 +-
 .../nifi/authorization/user/StandardNiFiUser.java  |   4 +-
 .../nifi-framework-components/pom.xml              |  98 ++-
 .../validation/StandardValidationTrigger.java      |   0
 .../org/apache/nifi/connectable/LocalPort.java     |  10 +-
 .../nifi/connectable/StandardConnection.java       |  11 +-
 .../apache/nifi/controller/ProcessorDetails.java   |   0
 .../apache/nifi/controller/StandardCounter.java    |   0
 .../nifi/controller/StandardProcessorNode.java     |   5 +-
 .../nifi/controller/flow/AbstractFlowManager.java  | 433 ++++++++++
 .../nifi/controller/kerberos/KerberosConfig.java   |   0
 .../nifi/controller/label/StandardLabel.java       |   0
 .../nifi/controller/lifecycle/TaskTermination.java |   0
 .../controller/queue/ConnectionEventListener.java  |   0
 .../controller/queue/FlowFileQueueFactory.java     |   0
 .../reporting/AbstractReportingContext.java}       | 110 +--
 .../reporting/AbstractReportingTaskNode.java       |   5 +-
 .../controller/reporting/ReportingTaskDetails.java |   0
 .../StandardReportingInitializationContext.java    |   0
 .../repository/AbstractRepositoryContext.java}     | 142 ++--
 .../repository/ProvenanceEventEnricher.java        |   0
 .../controller/repository/RepositoryContext.java   |  63 ++
 .../repository/StandardCounterRepository.java      |   0
 .../repository/StandardProcessSession.java         | 243 ++++--
 .../repository/StandardProcessSessionFactory.java  |   0
 .../repository/StandardProvenanceReporter.java     |  71 +-
 .../repository/StandardRepositoryStatusReport.java |   0
 .../repository/TransientClaimRepositoryRecord.java |   0
 .../repository/claim/ContentClaimWriteCache.java}  |  28 +-
 .../repository/io/ContentClaimInputStream.java     |   0
 .../repository/io/DisableOnCloseInputStream.java   |   0
 .../repository/io/DisableOnCloseOutputStream.java  |   0
 .../repository/io/FlowFileAccessInputStream.java   |   0
 .../repository/io/FlowFileAccessOutputStream.java  |   0
 .../repository/io/LimitedInputStream.java          |   0
 .../repository/io/TaskTerminationInputStream.java  |   0
 .../repository/io/TaskTerminationOutputStream.java |   0
 .../repository/metrics/EmptyFlowFileEvent.java     |   0
 .../repository/metrics/EventContainer.java         |   0
 .../controller/repository/metrics/EventSum.java    |   0
 .../repository/metrics/EventSumValue.java          |   0
 .../metrics/RingBufferEventRepository.java         |   0
 .../metrics/SecondPrecisionEventContainer.java     |   0
 .../repository/metrics/StandardFlowFileEvent.java  |   0
 .../scheduling/ConnectableProcessContext.java      |   2 +-
 .../service/ControllerServiceDetails.java          |   0
 .../ControllerServiceNotValidException.java        |   0
 .../controller/service/ServiceStateTransition.java |   0
 .../service/StandardConfigurationContext.java      |   0
 ...dardControllerServiceInitializationContext.java |   0
 ...StandardControllerServiceInvocationHandler.java |   0
 .../service/StandardControllerServiceNode.java     |   0
 .../service/StandardControllerServiceProvider.java |  33 +-
 .../StandardControllerServiceReference.java        |   0
 .../controller/state/ConfigParseException.java     |   0
 .../controller/state/StandardStateManager.java     |   0
 .../nifi/controller/state/StandardStateMap.java    |   0
 ...StandardStateProviderInitializationContext.java |   0
 .../state/config/StateManagerConfiguration.java    |   0
 .../state/config/StateProviderConfiguration.java   |   0
 .../manager/StandardStateManagerProvider.java      |   3 +-
 .../apache/nifi/controller/tasks/ActiveTask.java   |   0
 .../apache/nifi/encrypt/EncryptionException.java   |   0
 .../org/apache/nifi/encrypt/StringEncryptor.java   |   0
 .../nifi/events/VolatileBulletinRepository.java    |   0
 .../org/apache/nifi/groups/NoOpBatchCounts.java    |   0
 .../nifi/groups/SingleBatchFlowFileGate.java       |   0
 .../nifi/groups/SingleConcurrencyFlowFileGate.java |   0
 .../apache/nifi/groups/StandardBatchCounts.java    |   0
 .../org/apache/nifi/groups/StandardDataValve.java  |   0
 .../apache/nifi/groups/StandardProcessGroup.java   | 586 +++++++------
 .../nifi/groups/StandardVersionedFlowStatus.java   |   0
 .../apache/nifi/groups/UnboundedFlowFileGate.java  |   0
 .../apache/nifi/groups/VersionControlFields.java   |   0
 .../logging/repository/StandardLogRepository.java  |   0
 .../nifi/parameter/StandardParameterContext.java   |   0
 .../parameter/StandardParameterContextManager.java |   0
 .../StandardParameterReferenceManager.java         |   0
 .../nifi/parameter/StandardParameterUpdate.java    |   0
 .../ComponentSpecificControllerServiceLookup.java  |   0
 .../apache/nifi/processor/SimpleProcessLogger.java |   0
 .../nifi/processor/StandardProcessContext.java     |   0
 .../StandardProcessorInitializationContext.java    |   0
 .../nifi/processor/StandardValidationContext.java  |   2 +-
 .../StandardValidationContextFactory.java          |   0
 .../nifi/registry/flow/RestBasedFlowRegistry.java  |   0
 .../registry/flow/StandardFlowRegistryClient.java  |   0
 .../flow/StandardVersionControlInformation.java    |   0
 .../mapping/InstantiatedConnectableComponent.java  |   0
 .../mapping/InstantiatedVersionedComponent.java    |   0
 .../mapping/InstantiatedVersionedConnection.java   |   0
 .../InstantiatedVersionedControllerService.java    |   0
 .../flow/mapping/InstantiatedVersionedFunnel.java  |   0
 .../flow/mapping/InstantiatedVersionedLabel.java   |   0
 .../flow/mapping/InstantiatedVersionedPort.java    |   0
 .../mapping/InstantiatedVersionedProcessGroup.java |   0
 .../mapping/InstantiatedVersionedProcessor.java    |   0
 .../InstantiatedVersionedRemoteGroupPort.java      |   0
 .../InstantiatedVersionedRemoteProcessGroup.java   |   0
 .../flow/mapping/NiFiRegistryFlowMapper.java       |   0
 .../flow/mapping/StandardComparableDataFlow.java   |   0
 .../registry/variable/MutableVariableRegistry.java |   0
 .../StandardComponentVariableRegistry.java         |   0
 .../org/apache/nifi/remote/RemoteNiFiUtils.java    |   0
 .../nifi/remote/StandardRemoteProcessGroup.java    |  78 +-
 .../StandardRemoteProcessGroupPortDescriptor.java  |   0
 .../nifi/reporting/AbstractEventAccess.java}       | 200 ++---
 .../org/apache/nifi/util/ClassAnnotationPair.java  |   0
 .../java/org/apache/nifi/util/Connectables.java    |   0
 .../apache/nifi/util/FlowDifferenceFilters.java    |   0
 .../java/org/apache/nifi/util/ReflectionUtils.java |  48 +-
 .../java/org/apache/nifi/util/SnippetUtils.java    |   0
 .../java/org/apache/nifi/util/ThreadUtils.java     |   0
 .../apache/nifi/encrypt/StringEncryptorIT.groovy   |   0
 .../apache/nifi/encrypt/StringEncryptorTest.groovy |   0
 .../org/apache/nifi/util/SnippetUtilsSpec.groovy   |   0
 .../repository/TestRingBufferEventRepository.java  |   2 +-
 .../repository/TestStandardFlowFileRecord.java     |   0
 .../repository/TestStandardProvenanceReporter.java |   0
 .../metrics/TestSecondPrecisionEventContainer.java |   0
 ...StandardControllerServiceInvocationHandler.java |   0
 .../nifi/logging/TestStandardLogRepository.java    |   0
 .../parameter/TestStandardParameterContext.java    |   5 +-
 .../nifi/processor/TestSimpleProcessLogger.java    |   0
 .../nifi/processor/TestStandardPropertyValue.java  |   0
 .../org/apache/nifi/util/ReflectionUtilsTest.java  |   0
 .../nifi/util/TestFlowDifferenceFilters.java       |   0
 .../src/test/resources/conf/nifi.properties        | 127 +++
 .../nifi-framework/nifi-framework-core-api/pom.xml |   5 +-
 .../validation}/AbstractValidationContext.java     |   2 +-
 .../nifi/controller/AbstractComponentNode.java     |  16 +-
 .../apache/nifi/controller/ProcessScheduler.java   |   8 +
 .../org/apache/nifi/controller/StandardFunnel.java |  16 +-
 .../service/ControllerServiceProvider.java         |   7 +
 .../java/org/apache/nifi/engine/FlowEngine.java    |   0
 .../java/org/apache/nifi/groups/ProcessGroup.java  |   6 +-
 .../provenance/InternalProvenanceReporter.java     |  51 ++
 .../apache/nifi/controller/TestStandardFunnel.java |  33 +-
 .../nifi-framework/nifi-framework-core/pom.xml     |   9 +-
 .../org/apache/nifi/controller/FlowController.java |   7 +-
 .../nifi/controller/flow/StandardFlowManager.java  | 401 +--------
 .../reporting/StandardReportingContext.java        | 128 +--
 .../repository/StandardRepositoryContext.java      |  37 +
 ...he.java => StandardContentClaimWriteCache.java} |  12 +-
 .../scheduling/EventDrivenSchedulingAgent.java     |   1 +
 .../scheduling/RepositoryContextFactory.java       |   3 +-
 .../scheduling/StandardProcessScheduler.java       |   3 +-
 .../nifi/controller/tasks/ConnectableTask.java     |   2 +-
 .../apache/nifi/reporting/StandardEventAccess.java | 690 +--------------
 .../org/apache/nifi/connectable/TestLocalPort.java | 157 ----
 ...essorNode.java => StandardProcessorNodeIT.java} |  28 +-
 ...sSession.java => StandardProcessSessionIT.java} |   8 +-
 .../claim/TestContentClaimWriteCache.java          |   2 +-
 .../scheduling/TestStandardProcessScheduler.java   |   6 +-
 .../StandardControllerServiceProviderIT.java       |   8 +-
 .../StandardControllerServiceProviderTest.java     |  15 +-
 .../TestStandardControllerServiceProvider.java     |  99 +--
 .../controller/service/mock/MockProcessGroup.java  |  16 +-
 .../nifi/controller/tasks/TestConnectableTask.java |   3 +-
 .../java/org/apache/nifi/nar/ExtensionManager.java |   4 +
 .../nar/StandardExtensionDiscoveringManager.java   |  50 ++
 .../java/org/apache/nifi/nar/NarClassLoaders.java  |   5 +-
 .../main/java/org/apache/nifi/nar/NarUnpacker.java |  58 +-
 .../java/org/apache/nifi/nar/SystemBundle.java     |   4 +
 .../repository/StandardFlowFileRecord.java         |  19 +-
 .../claim/TestStandardResourceClaimManager.java    |   0
 .../nifi-resources/src/main/resources/bin/nifi.sh  |  46 +-
 .../src/main/resources/conf/stateless-logback.xml  |  74 ++
 .../src/main/resources/conf/stateless.properties   |  40 +
 .../nifi-framework/nifi-site-to-site/pom.xml       |  36 -
 .../nifi/remote/StandardRemoteGroupPort.java       |  24 +-
 .../nifi/remote/TestStandardRemoteGroupPort.java   |  47 +-
 .../nifi-framework/nifi-stateless/README.md        | 132 ---
 .../stateless/core/AbstractStatelessComponent.java | 129 ---
 .../nifi/stateless/core/ComponentFactory.java      | 226 -----
 .../nifi/stateless/core/ProvenanceCollector.java   | 550 ------------
 .../nifi/stateless/core/ReflectionUtils.java       | 150 ----
 .../nifi/stateless/core/SLF4JComponentLog.java     | 365 --------
 .../core/StatelessConfigurationContext.java        | 112 ---
 .../StatelessControllerServiceConfiguration.java   |  86 --
 ...lessControllerServiceInitializationContext.java |  81 --
 .../core/StatelessControllerServiceLookup.java     | 224 -----
 .../apache/nifi/stateless/core/StatelessFlow.java  | 527 ------------
 .../nifi/stateless/core/StatelessFlowFile.java     | 347 --------
 .../stateless/core/StatelessParameterContext.java  | 122 ---
 .../core/StatelessPassThroughComponent.java        |  76 --
 .../stateless/core/StatelessProcessContext.java    | 538 ------------
 .../stateless/core/StatelessProcessSession.java    | 945 ---------------------
 .../StatelessProcessorInitializationContext.java   |  80 --
 .../stateless/core/StatelessProcessorWrapper.java  | 370 --------
 .../stateless/core/StatelessPropertyValue.java     | 287 -------
 .../stateless/core/StatelessRemoteInputPort.java   | 131 ---
 .../stateless/core/StatelessRemoteOutputPort.java  | 155 ----
 .../nifi/stateless/core/StatelessStateManager.java |  61 --
 .../nifi/stateless/core/StatelessStateMap.java     |  45 -
 .../stateless/core/StatelessValidationContext.java | 195 -----
 .../core/security/StatelessSecurityUtility.java    | 174 ----
 .../apache/nifi/stateless/runtimes/Program.java    | 213 -----
 .../openwhisk/StatelessNiFiOpenWhiskAction.java    | 196 -----
 .../stateless/runtimes/yarn/YARNServiceUtil.java   | 103 ---
 .../security/StatelessSecurityUtilityTest.groovy   | 222 -----
 .../nifi/stateless/runtimes/ProgramTest.groovy     | 101 ---
 .../org/apache/nifi/stateless/core/BatchTest.java  | 113 ---
 .../apache/nifi/stateless/core/RegistryTest.java   |  44 -
 .../apache/nifi/stateless/core/StreamingIT.java    | 136 ---
 .../org/apache/nifi/web/dao/ProcessGroupDAO.java   |   7 +-
 .../nifi/web/dao/impl/StandardProcessGroupDAO.java |  20 +-
 .../StandardOidcIdentityProviderGroovyTest.groovy  |   5 +-
 .../nifi-framework-bundle/nifi-framework/pom.xml   |   2 +-
 .../nifi-framework-bundle/nifi-server-nar/pom.xml  |   6 +
 .../nifi-stateless-bundle/README.md                | 405 +++++++++
 .../nifi-stateless-api}/pom.xml                    |  29 +-
 .../config/ExtensionClientDefinition.java          |  57 ++
 .../config/ParameterContextDefinition.java}        |  26 +-
 .../stateless/config/ParameterDefinition.java}     |  24 +-
 .../nifi/stateless/config/ParameterOverride.java}  |  39 +-
 .../PropertiesFileEngineConfigurationParser.java   | 199 +++++
 .../stateless/config/ReportingTaskDefinition.java  |  69 ++
 .../stateless/config/SslContextDefinition.java     |  84 ++
 .../config/StatelessConfigurationException.java}   |  14 +-
 .../engine/StatelessEngineConfiguration.java}      |  31 +-
 .../nifi/stateless/flow/DataflowDefinition.java}   |  27 +-
 .../stateless/flow/DataflowDefinitionParser.java}  |  16 +-
 .../flow/FailurePortEncounteredException.java}     |  11 +-
 .../nifi/stateless/flow/StatelessDataflow.java}    |  37 +-
 .../stateless/flow/StatelessDataflowFactory.java}  |  22 +-
 .../flow/StatelessDataflowValidation.java}         |   8 +-
 .../nifi-stateless-bootstrap}/pom.xml              |  85 +-
 .../bootstrap/BootstrapConfiguration.java          | 189 +++++
 .../nifi/stateless/bootstrap/RunStatelessFlow.java | 109 +++
 .../stateless/bootstrap/StatelessBootstrap.java    | 149 ++++
 .../src/main/resources/nifi-stateless.properties   |  15 +-
 .../src/test/resources/nifi-stateless.properties   |  12 +-
 .../nifi-stateless-engine}/pom.xml                 |  77 +-
 .../components/state/HashMapStateProvider.java     | 124 +++
 .../controller/reporting/StatelessEventAccess.java |  50 ++
 .../reporting/StatelessReportingContext.java       |  68 ++
 .../reporting/StatelessReportingTaskNode.java      |  83 ++
 .../nifi/extensions/BundleAvailability.java}       |  11 +-
 .../org/apache/nifi/extensions/DownloadQueue.java  | 256 ++++++
 .../apache/nifi/extensions/ExtensionClient.java}   |  12 +-
 .../nifi/extensions/ExtensionRepository.java}      |  18 +-
 .../extensions/FileSystemExtensionRepository.java  | 131 +++
 .../nifi/extensions/NexusExtensionClient.java      | 137 +++
 .../exception/BundleNotFoundException.java}        |  17 +-
 .../nifi/registry/flow/InMemoryFlowRegistry.java   | 208 +++++
 .../stateless/bootstrap/ExtensionDiscovery.java    |   4 +-
 .../config/PropertiesFileFlowDefinitionParser.java | 335 ++++++++
 .../stateless/config/SslConfigurationUtil.java     |  55 ++
 .../apache/nifi/stateless/core/RegistryUtil.java   |   0
 .../engine/CachingProcessContextFactory.java       |  45 +
 .../nifi/stateless/engine/ComponentBuilder.java    | 286 +++++++
 .../stateless/engine/ProcessContextFactory.java}   |  12 +-
 .../stateless/engine/StandardStatelessEngine.java  | 550 ++++++++++++
 .../nifi/stateless/engine/StatelessAuthorizer.java |  46 +
 .../nifi/stateless/engine/StatelessEngine.java     |  70 ++
 .../StatelessEngineInitializationContext.java      |  53 ++
 .../stateless/engine/StatelessFlowManager.java     | 366 ++++++++
 .../engine/StatelessNodeTypeProvider.java}         |  14 +-
 .../engine/StatelessProcessContextFactory.java     |  54 ++
 .../engine/StatelessProcessScheduler.java          | 286 +++++++
 .../StatelessProvenanceAuthorizableFactory.java}   |  24 +-
 .../stateless/engine/StatelessReloadComponent.java |  48 ++
 .../stateless/engine/StatelessSchedulingAgent.java | 126 +++
 .../stateless/flow/StandardDataflowDefinition.java | 119 +++
 .../flow/StandardStatelessDataflowFactory.java     | 312 +++++++
 .../flow/StandardStatelessDataflowValidation.java  |  54 ++
 .../nifi/stateless/flow/StandardStatelessFlow.java | 461 ++++++++++
 .../stateless/queue/DrainableFlowFileQueue.java}   |  40 +-
 .../stateless/queue/StatelessFlowFileQueue.java    | 349 ++++++++
 .../repository/ByteArrayContentRepository.java     | 361 ++++++++
 .../repository/RepositoryContextFactory.java}      |  26 +-
 .../StatelessContentClaimWriteCache.java           |  75 ++
 .../repository/StatelessFlowFileRepository.java    | 136 +++
 .../repository/StatelessRepositoryContext.java     |  45 +
 .../StatelessRepositoryContextFactory.java         |  88 ++
 .../stateless/session/StatelessProcessSession.java | 172 ++++
 .../session/StatelessProcessSessionFactory.java    |  53 ++
 ...e.nifi.stateless.flow.DataflowDefinitionParser} |   3 +-
 ...e.nifi.stateless.flow.StatelessDataflowFactory} |   3 +-
 .../TestPropertiesFileFlowDefinitionParser.java    |  98 +++
 .../queue/TestStatelessFlowFileQueue.java          | 120 +++
 .../test/resources/flow-configuration.properties   |  29 +-
 .../src/test/resources/flow-snapshot.json          | 484 +++++++++++
 .../nifi-stateless-nar}/pom.xml                    |  31 +-
 .../src/main/resources/META-INF/LICENSE            | 291 +++++++
 .../src/main/resources/META-INF/NOTICE             | 152 ++++
 .../nifi-stateless-bundle}/pom.xml                 |  19 +-
 nifi-nar-bundles/nifi-framework-bundle/pom.xml     |   6 +
 .../provenance/VolatileProvenanceRepository.java   |  13 +-
 .../controller/ControllerStatusReportingTask.java  | 254 ++++--
 .../TestControllerStatusReportingTask.java         |  46 -
 .../pom.xml                                        | 237 +++---
 .../src/test/assembly/dependencies.xml             |  41 +
 .../apache/nifi/stateless/StatelessSystemIT.java   | 145 ++++
 .../nifi/stateless/VersionedFlowBuilder.java       | 295 +++++++
 .../nifi/stateless/basics/CloneFlowFileIT.java     |  85 ++
 .../nifi/stateless/basics/CreatesFlowFileIT.java   |  52 ++
 .../nifi/stateless/basics/InputOutputIT.java       |  99 +++
 .../stateless/basics/RollbackOnExceptionIT.java    |  85 ++
 .../StatelessControllerServiceSystemIT.java        |  81 ++
 .../stateless/parameters/ParameterContextIT.java   | 139 +++
 .../src/test/resources/flows/GenerateFlowFile.json | 163 ++++
 ...keControllerService1.java => CountService.java} |   6 +-
 .../cs/tests/system/FakeControllerService1.java    |   2 +
 .../nifi/cs/tests/system/StandardCountService.java |  50 ++
 .../processors/tests/system/CountFlowFiles.java    |  71 ++
 .../processors/tests/system/ReverseContents.java   |  68 ++
 .../nifi/processors/tests/system/SetAttribute.java |  70 ++
 .../tests/system/ThrowProcessException.java        |  60 ++
 .../nifi/reporting/WriteToFileReportingTask.java   |  72 ++
 .../org.apache.nifi.controller.ControllerService   |   3 +-
 .../services/org.apache.nifi.processor.Processor   |   4 +
 .../org.apache.nifi.reporting.ReportingTask        |   3 +-
 .../cs/tests/system/FakeControllerService2.java    |   2 +
 nifi-system-tests/nifi-system-test-suite/pom.xml   |   6 +
 .../ControllerServiceApiValidationIT.java          |  22 +-
 nifi-system-tests/pom.xml                          |   1 +
 344 files changed, 12451 insertions(+), 10455 deletions(-)

diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index b3f2a8d..a37af44 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -194,6 +194,17 @@ language governing permissions and limitations under the License. -->
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-stateless-nar</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+            <type>nar</type>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-stateless-bootstrap</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-hadoop-libraries-nar</artifactId>
             <version>1.13.0-SNAPSHOT</version>
             <type>nar</type>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/DomUtils.java b/nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/DomUtils.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/DomUtils.java
rename to nifi-commons/nifi-utils/src/main/java/org/apache/nifi/util/DomUtils.java
diff --git a/nifi-docker/pom.xml b/nifi-docker/pom.xml
index 7cdc63d..4d2b5af 100644
--- a/nifi-docker/pom.xml
+++ b/nifi-docker/pom.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- 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 
+<!-- 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. -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
-    
+
     <parent>
         <groupId>org.apache.nifi</groupId>
         <artifactId>nifi</artifactId>
@@ -29,10 +29,9 @@ language governing permissions and limitations under the License. -->
 
     <modules>
         <module>dockermaven</module>
-        <module>dockermaven-stateless</module>
         <module>dockerhub</module>
     </modules>
-    
+
     <build>
         <pluginManagement>
             <plugins>
diff --git a/nifi-mock/pom.xml b/nifi-mock/pom.xml
index abafdb1..523e740 100644
--- a/nifi-mock/pom.xml
+++ b/nifi-mock/pom.xml
@@ -44,7 +44,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-framework-components</artifactId>
+            <artifactId>nifi-framework-core-api</artifactId>
             <version>1.13.0-SNAPSHOT</version>
         </dependency>
         <dependency>
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
index 834833b..f4876c4 100644
--- a/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
@@ -16,7 +16,7 @@
  */
 package org.apache.nifi.util;
 
-import org.apache.nifi.AbstractValidationContext;
+import org.apache.nifi.components.validation.AbstractValidationContext;
 import org.apache.nifi.attribute.expression.language.Query;
 import org.apache.nifi.attribute.expression.language.Query.Range;
 import org.apache.nifi.attribute.expression.language.StandardExpressionLanguageCompiler;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/pom.xml
index 5aa1109..ca8a814 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/pom.xml
@@ -35,6 +35,14 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-authorization-providers</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-file-authorizer</artifactId>
         </dependency>
         <dependency>
@@ -56,11 +64,6 @@
             <version>1.13.0-SNAPSHOT</version>
         </dependency>
         <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-stateless</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
             <groupId>org.glassfish.jersey.core</groupId>
             <artifactId>jersey-server</artifactId>
             <version>${jersey.version}</version>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE
index 511bbcc..967d204 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework-nar/src/main/resources/META-INF/NOTICE
@@ -15,6 +15,11 @@ The following binary components are provided under the Apache Software License v
       Apache Commons IO
       Copyright 2002-2016 The Apache Software Foundation
 
+  (ASLv2) Apache Commons Text
+    The following NOTICE information applies:
+      Apache Commons Text
+      Copyright 2001-2018 The Apache Software Foundation
+
   (ASLv2) Apache Commons Net
     The following NOTICE information applies:
       Apache Commons Net
@@ -23,7 +28,7 @@ The following binary components are provided under the Apache Software License v
   (ASLv2) Apache Commons Collections
     The following NOTICE information applies:
       Apache Commons Collections
-      Copyright 2001-2013 The Apache Software Foundation	
+      Copyright 2001-2013 The Apache Software Foundation
 
   (ASLv2) Apache Commons Compress
     The following NOTICE information applies:
@@ -40,6 +45,14 @@ The following binary components are provided under the Apache Software License v
     The following NOTICE information applies:
          Copyright 2006 Envoi Solutions LLC
 
+  (ASLv2) Apache Commons Configuration
+    The following NOTICE information applies:
+      Apache Commons Configuration
+      Copyright 2001-2017 The Apache Software Foundation
+
+      This product includes software developed at
+      The Apache Software Foundation (http://www.apache.org/).
+
   (ASLv2) Apache Commons Codec
     The following NOTICE information applies:
       Apache Commons Codec
@@ -61,7 +74,7 @@ The following binary components are provided under the Apache Software License v
     The following NOTICE information applies:
       Apache HttpClient
       Copyright 1999-2014 The Apache Software Foundation
-      
+
       Apache HttpCore
       Copyright 2005-2014 The Apache Software Foundation
 
@@ -173,6 +186,27 @@ The following binary components are provided under the Apache Software License v
       Caffeine (caching library)
       Copyright Ben Manes
 
+  (ASLv2) OkHttp (com.squareup.okhttp3:okhttp:jar:3.10.0)
+    The following NOTICE information applies:
+      OkHttp 3.10.0
+      Copyright 2019 Square, Inc.
+
+  (ASLv2) ASM Based Accessors Helper Used By JSON Smart (net.minidev:accessors-smart:jar:1.2 - http://www.minidev.net/)
+    The following NOTICE information applies:
+      ASM Based Accessors Helper Used By JSON Smart 1.2
+      Copyright 2017, Uriel Chemouni
+
+  (ASLv2) BCrypt Password Hashing Function (at.favre.lib:bcrypt:jar:0.9.0 - https://github.com/patrickfav/bcrypt)
+      The following NOTICE information applies:
+        BCrypt Password Hashing Function 0.9.0
+        Copyright 2018 Patrick Favre-Bulle
+
+  (ASLv2) Bytes Utility Library (at.favre.lib:bytes:jar:1.3.0 - https://github.com/patrickfav/bytes-java)
+      The following NOTICE information applies:
+        Bytes Utility Library 1.3.0
+        Copyright 2017 Patrick Favre-Bulle
+
+
 ************************
 Common Development and Distribution License 1.1
 ************************
@@ -203,6 +237,7 @@ The following binary components are provided under the Common Development and Di
     (CDDL 1.1) (GPL2 w/ CPE) MIME Streaming Extension (org.jvnet.mimepull:mimepull:jar:1.9.3 - http://mimepull.java.net)
     (CDDL 1.1) (GPL2 w/ CPE) JavaMail API (compat) (javax.mail:mail:jar:1.4.7 - http://kenai.com/projects/javamail/mail)
     (CDDL 1.1) (GPL2 w/ CPE) javax.ws.rs-api (javax.ws.rs:javax.ws.rs-api:jar:2.1 - http://jax-rs-spec.java.net)
+    (CDDL 1.1) (GPL2 w/ CPE) javax.annotation API (javax.annotation:javax.annotation-api:jar:1.2 - http://jcp.org/en/jsr/detail?id=250)
 
 ************************
 Common Development and Distribution License 1.0
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
index 6461d7b..e8e7d8d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
@@ -142,7 +142,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-framework-authorization</artifactId>
+            <artifactId>nifi-framework-authorization-providers</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/pom.xml
similarity index 81%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/pom.xml
index 6b16055..ec207f3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/pom.xml
@@ -13,14 +13,15 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
-        <groupId>org.apache.nifi</groupId>
         <artifactId>nifi-framework</artifactId>
+        <groupId>org.apache.nifi</groupId>
         <version>1.13.0-SNAPSHOT</version>
     </parent>
-    <artifactId>nifi-framework-authorization</artifactId>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-framework-authorization-providers</artifactId>
 
     <dependencies>
         <dependency>
@@ -33,29 +34,23 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-security-utils</artifactId>
+            <artifactId>nifi-framework-authorization</artifactId>
             <version>1.13.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-expression-language</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-properties</artifactId>
+            <artifactId>nifi-security-utils</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-core</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-mock-authorizer</artifactId>
+            <artifactId>nifi-expression-language</artifactId>
             <version>1.13.0-SNAPSHOT</version>
         </dependency>
     </dependencies>
-</project>
+
+</project>
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardAuthorizerConfigurationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/user/NiFiUserDetails.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserDetails.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/user/NiFiUserDetails.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
similarity index 94%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
index aa96bee..443164f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
@@ -16,9 +16,10 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.parameter.ParameterLookup;
+import org.junit.Assert;
 import org.junit.Test;
 
 import java.util.HashMap;
@@ -29,7 +30,6 @@ import java.util.stream.Stream;
 
 import static org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider.PROP_CONFIGURABLE_USER_GROUP_PROVIDER;
 import static org.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -103,8 +103,8 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         });
 
         // users and groups
-        assertEquals(2, userGroupProvider.getUsers().size());
-        assertEquals(1, userGroupProvider.getGroups().size());
+        Assert.assertEquals(2, userGroupProvider.getUsers().size());
+        Assert.assertEquals(1, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -128,8 +128,8 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         }, getConflictingUserGroupProvider());
 
         // users and groups
-        assertEquals(3, userGroupProvider.getUsers().size());
-        assertEquals(2, userGroupProvider.getGroups().size());
+        Assert.assertEquals(3, userGroupProvider.getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -167,8 +167,8 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         }, getCollaboratingUserGroupProvider());
 
         // users and groups
-        assertEquals(3, userGroupProvider.getUsers().size());
-        assertEquals(2, userGroupProvider.getGroups().size());
+        Assert.assertEquals(3, userGroupProvider.getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -183,7 +183,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
         assertNotNull(user1AndGroups);
         assertNotNull(user1AndGroups.getUser());
-        assertEquals(2, user1AndGroups.getGroups().size()); // from CollaboratingUGP
+        Assert.assertEquals(2, user1AndGroups.getGroups().size()); // from CollaboratingUGP
     }
 
     private UserGroupProvider getConfigurableUserGroupProvider() {
@@ -206,7 +206,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
         assertNotNull(user1AndGroups);
         assertNotNull(user1AndGroups.getUser());
-        assertEquals(1, user1AndGroups.getGroups().size());
+        Assert.assertEquals(1, user1AndGroups.getGroups().size());
 
         assertNotNull(userGroupProvider.getUser(USER_5_IDENTIFIER));
         assertNotNull(userGroupProvider.getUserByIdentity(USER_5_IDENTITY));
@@ -217,6 +217,6 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         assertTrue(user5AndGroups.getGroups().isEmpty());
 
         assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
-        assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
+        Assert.assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
     }
 }
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
similarity index 91%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
index 674808f..b872e54 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
@@ -16,13 +16,13 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.parameter.ParameterLookup;
+import org.junit.Assert;
 import org.junit.Test;
 
 import static org.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -74,8 +74,8 @@ public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTe
         final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null, getUserGroupProviderOne());
 
         // users and groups
-        assertEquals(2, userGroupProvider.getUsers().size());
-        assertEquals(1, userGroupProvider.getGroups().size());
+        Assert.assertEquals(2, userGroupProvider.getUsers().size());
+        Assert.assertEquals(1, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -96,8 +96,8 @@ public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTe
                 getUserGroupProviderOne(), getUserGroupProviderTwo());
 
         // users and groups
-        assertEquals(3, userGroupProvider.getUsers().size());
-        assertEquals(2, userGroupProvider.getGroups().size());
+        Assert.assertEquals(3, userGroupProvider.getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -119,8 +119,8 @@ public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTe
                 getUserGroupProviderOne(), getUserGroupProviderTwo(), getConflictingUserGroupProvider());
 
         // users and groups
-        assertEquals(4, userGroupProvider.getUsers().size());
-        assertEquals(2, userGroupProvider.getGroups().size());
+        Assert.assertEquals(4, userGroupProvider.getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -157,8 +157,8 @@ public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTe
                 getUserGroupProviderOne(), getUserGroupProviderTwo(), getCollaboratingUserGroupProvider());
 
         // users and groups
-        assertEquals(4, userGroupProvider.getUsers().size());
-        assertEquals(2, userGroupProvider.getGroups().size());
+        Assert.assertEquals(4, userGroupProvider.getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -175,6 +175,6 @@ public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTe
         final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
         assertNotNull(user1AndGroups);
         assertNotNull(user1AndGroups.getUser());
-        assertEquals(2, user1AndGroups.getGroups().size()); // from UGP1 and CollaboratingUGP
+        Assert.assertEquals(2, user1AndGroups.getGroups().size()); // from UGP1 and CollaboratingUGP
     }
 }
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
similarity index 94%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
index c11e5e9..6eb650f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
@@ -16,9 +16,10 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
 import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.parameter.ParameterLookup;
+import org.junit.Assert;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -28,7 +29,6 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
@@ -83,7 +83,7 @@ public class CompositeUserGroupProviderTestBase {
         final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
         assertNotNull(user1AndGroups);
         assertNotNull(user1AndGroups.getUser());
-        assertEquals(1, user1AndGroups.getGroups().size());
+        Assert.assertEquals(1, user1AndGroups.getGroups().size());
 
         final UserAndGroups user2AndGroups = userGroupProvider.getUserAndGroups(USER_2_IDENTITY);
         assertNotNull(user2AndGroups);
@@ -92,7 +92,7 @@ public class CompositeUserGroupProviderTestBase {
 
         // groups
         assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
-        assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
+        Assert.assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
     }
 
     protected UserGroupProvider getUserGroupProviderTwo() {
@@ -115,11 +115,11 @@ public class CompositeUserGroupProviderTestBase {
         final UserAndGroups user3AndGroups = userGroupProvider.getUserAndGroups(USER_3_IDENTITY);
         assertNotNull(user3AndGroups);
         assertNotNull(user3AndGroups.getUser());
-        assertEquals(1, user3AndGroups.getGroups().size());
+        Assert.assertEquals(1, user3AndGroups.getGroups().size());
 
         // groups
         assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
-        assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+        Assert.assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
     }
 
     protected UserGroupProvider getConflictingUserGroupProvider() {
@@ -146,7 +146,7 @@ public class CompositeUserGroupProviderTestBase {
         final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
         assertNotNull(user1AndGroups);
         assertNotNull(user1AndGroups.getUser());
-        assertEquals(1, user1AndGroups.getGroups().size());
+        Assert.assertEquals(1, user1AndGroups.getGroups().size());
     }
 
     protected UserGroupProvider getCollaboratingUserGroupProvider() {
@@ -170,11 +170,11 @@ public class CompositeUserGroupProviderTestBase {
         final UserAndGroups user4AndGroups = userGroupProvider.getUserAndGroups(USER_4_IDENTITY);
         assertNotNull(user4AndGroups);
         assertNotNull(user4AndGroups.getUser());
-        assertEquals(1, user4AndGroups.getGroups().size());
+        Assert.assertEquals(1, user4AndGroups.getGroups().size());
 
         // groups
         assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
-        assertEquals(2, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+        Assert.assertEquals(2, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
     }
 
     protected void mockProperties(final AuthorizerConfigurationContext configurationContext) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
index e3cc9e0..ff5299d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization-providers/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
@@ -16,11 +16,10 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
-import org.junit.Assert;
+import org.apache.nifi.parameter.ParameterLookup;
 import org.junit.Test;
 
 import java.util.Collections;
@@ -110,7 +109,7 @@ public class StandardManagedAuthorizerTest {
         when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
 
         final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
-        Assert.assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+        assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
     }
 
     @Test
@@ -123,7 +122,7 @@ public class StandardManagedAuthorizerTest {
         when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
 
         final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
-        Assert.assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+        assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
     }
 
     @Test
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
index 6b16055..c1b6f97 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
@@ -33,29 +33,9 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-security-utils</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-expression-language</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-properties</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-mock-authorizer</artifactId>
             <version>1.13.0-SNAPSHOT</version>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 </project>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
index bc5653f..b58f8e0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
@@ -16,8 +16,6 @@
  */
 package org.apache.nifi.authorization.user;
 
-import org.apache.commons.lang3.StringUtils;
-
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
@@ -130,7 +128,7 @@ public class StandardNiFiUser implements NiFiUser {
         if (groups == null) {
             formattedGroups = "none";
         } else {
-            formattedGroups = StringUtils.join(groups, ", ");
+            formattedGroups = String.join(", ", groups);
         }
 
         return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml
index cb167ec..c0d6149 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/pom.xml
@@ -1,18 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <!--
-      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.
-    -->
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.nifi</groupId>
@@ -27,15 +28,78 @@
             <artifactId>nifi-framework-api</artifactId>
             <scope>compile</scope>
         </dependency>
+        <!-- application dependencies -->
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-framework-core-api</artifactId>
-            <scope>compile</scope>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-site-to-site</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-repository-models</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-data-provenance-utils</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-web-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-nar-utils</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-data-model</artifactId>
+            <version>${nifi.registry.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-flow-diff</artifactId>
+            <version>${nifi.registry.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-client</artifactId>
+            <version>${nifi.registry.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
+            <groupId>org.jasypt</groupId>
+            <artifactId>jasypt</artifactId>
             <scope>test</scope>
         </dependency>
+
     </dependencies>
 </project>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/validation/StandardValidationTrigger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/validation/StandardValidationTrigger.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/validation/StandardValidationTrigger.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/validation/StandardValidationTrigger.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/LocalPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/LocalPort.java
similarity index 96%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/LocalPort.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/LocalPort.java
index 8d5ccda..579d6c9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/LocalPort.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/LocalPort.java
@@ -29,7 +29,6 @@ import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.scheduling.SchedulingStrategy;
-import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,15 +62,14 @@ public class LocalPort extends AbstractPort {
     private final Lock writeLock = rwLock.writeLock();
     private final int maxIterations;
 
-    public LocalPort(final String id, final String name, final ConnectableType type, final ProcessScheduler scheduler, final NiFiProperties nifiProperties) {
+    public LocalPort(final String id, final String name, final ConnectableType type, final ProcessScheduler scheduler, final int maxConcurrentTasks, final int maxBatchSize,
+                     final String boredYieldDuration) {
         super(id, name, type, scheduler);
 
-        int maxConcurrentTasks = Integer.parseInt(nifiProperties.getProperty(MAX_CONCURRENT_TASKS_PROP_NAME, "1"));
         setMaxConcurrentTasks(maxConcurrentTasks);
 
-        int maxTransferredFlowFiles = Integer.parseInt(nifiProperties.getProperty(MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "10000"));
-        maxIterations = Math.max(1, (int) Math.ceil(maxTransferredFlowFiles / 1000.0));
-        setYieldPeriod(nifiProperties.getBoredYieldDuration());
+        maxIterations = Math.max(1, (int) Math.ceil(maxBatchSize / 1000.0));
+        setYieldPeriod(boredYieldDuration);
     }
 
     protected int getMaxIterations() {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/StandardConnection.java
similarity index 97%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/StandardConnection.java
index fe60585..73e8fb1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/connectable/StandardConnection.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/connectable/StandardConnection.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.connectable;
 
-import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
@@ -71,8 +70,6 @@ public final class StandardConnection implements Connection, ConnectionEventList
     private final AtomicLong zIndex = new AtomicLong(0L);
     private final AtomicReference<String> versionedComponentId = new AtomicReference<>();
     private final ProcessScheduler scheduler;
-    private final FlowFileQueueFactory flowFileQueueFactory;
-    private final boolean clustered;
     private final int hashCode;
 
     private volatile FlowFileQueue flowFileQueue;
@@ -86,10 +83,8 @@ public final class StandardConnection implements Connection, ConnectionEventList
         destination = new AtomicReference<>(builder.destination);
         relationships = new AtomicReference<>(Collections.unmodifiableCollection(builder.relationships));
         scheduler = builder.scheduler;
-        flowFileQueueFactory = builder.flowFileQueueFactory;
-        clustered = builder.clustered;
 
-        flowFileQueue = flowFileQueueFactory.createFlowFileQueue(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE, null, this);
+        flowFileQueue = builder.flowFileQueueFactory.createFlowFileQueue(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE, null, this);
         hashCode = new HashCodeBuilder(7, 67).append(id).toHashCode();
     }
 
@@ -131,8 +126,8 @@ public final class StandardConnection implements Connection, ConnectionEventList
                 String name = StandardConnection.this.getName();
 
                 final Collection<Relationship> relationships = getRelationships();
-                if (name == null && CollectionUtils.isNotEmpty(relationships)) {
-                    name = StringUtils.join(relationships.stream().map(relationship -> relationship.getName()).collect(Collectors.toSet()), ", ");
+                if (name == null && relationships != null && !relationships.isEmpty()) {
+                    name = StringUtils.join(relationships.stream().map(Relationship::getName).collect(Collectors.toSet()), ", ");
                 }
 
                 if (name == null) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ProcessorDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/ProcessorDetails.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ProcessorDetails.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/ProcessorDetails.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardCounter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardCounter.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardCounter.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardCounter.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
index 1cc3605..4281e5c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
@@ -74,7 +74,6 @@ import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
 import org.quartz.CronExpression;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
 
 import java.lang.management.ThreadInfo;
 import java.lang.reflect.InvocationTargetException;
@@ -1184,7 +1183,9 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
 
     @Override
     public void setAnnotationData(final String data) {
-        Assert.state(!isRunning(), "Cannot set AnnotationData while processor is running");
+        if (isRunning()) {
+            throw new IllegalStateException("Cannot set AnnotationData while processor is running");
+        }
         super.setAnnotationData(data);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
new file mode 100644
index 0000000..fe4cc69
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
@@ -0,0 +1,433 @@
+/*
+ * 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.controller.flow;
+
+import org.apache.nifi.annotation.lifecycle.OnRemoved;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.Funnel;
+import org.apache.nifi.connectable.Port;
+import org.apache.nifi.controller.ProcessScheduler;
+import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.controller.repository.FlowFileEventRepository;
+import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.controller.service.ControllerServiceProvider;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.logging.LogRepositoryFactory;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarCloseable;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterContext;
+import org.apache.nifi.parameter.ParameterContextManager;
+import org.apache.nifi.parameter.ParameterReferenceManager;
+import org.apache.nifi.parameter.StandardParameterContext;
+import org.apache.nifi.parameter.StandardParameterReferenceManager;
+import org.apache.nifi.registry.flow.FlowRegistryClient;
+import org.apache.nifi.remote.PublicPort;
+import org.apache.nifi.remote.RemoteGroupPort;
+import org.apache.nifi.util.ReflectionUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.BooleanSupplier;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class AbstractFlowManager implements FlowManager {
+    private final ConcurrentMap<String, ProcessGroup> allProcessGroups = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, ProcessorNode> allProcessors = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, Connection> allConnections = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, Port> allInputPorts = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, Port> allOutputPorts = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, Funnel> allFunnels = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, ReportingTaskNode> allReportingTasks = new ConcurrentHashMap<>();
+
+    private final FlowFileEventRepository flowFileEventRepository;
+    private final ParameterContextManager parameterContextManager;
+    private final FlowRegistryClient flowRegistryClient;
+    private final BooleanSupplier flowInitializedCheck;
+
+    private volatile ControllerServiceProvider controllerServiceProvider;
+    private volatile ProcessGroup rootGroup;
+
+    public AbstractFlowManager(final FlowFileEventRepository flowFileEventRepository, final ParameterContextManager parameterContextManager,
+                               final FlowRegistryClient flowRegistryClient, final BooleanSupplier flowInitializedCheck) {
+        this.flowFileEventRepository = flowFileEventRepository;
+        this.parameterContextManager = parameterContextManager;
+        this.flowRegistryClient = flowRegistryClient;
+        this.flowInitializedCheck = flowInitializedCheck;
+    }
+
+    public void initialize(final ControllerServiceProvider controllerServiceProvider) {
+        this.controllerServiceProvider = controllerServiceProvider;
+    }
+
+    public ProcessGroup getGroup(final String id) {
+        return allProcessGroups.get(requireNonNull(id));
+    }
+
+    public void onProcessGroupAdded(final ProcessGroup group) {
+        allProcessGroups.put(group.getIdentifier(), group);
+    }
+
+    public void onProcessGroupRemoved(final ProcessGroup group) {
+        allProcessGroups.remove(group.getIdentifier());
+    }
+
+    public void onProcessorAdded(final ProcessorNode procNode) {
+        allProcessors.put(procNode.getIdentifier(), procNode);
+    }
+
+    public void onProcessorRemoved(final ProcessorNode procNode) {
+        String identifier = procNode.getIdentifier();
+        flowFileEventRepository.purgeTransferEvents(identifier);
+        allProcessors.remove(identifier);
+    }
+
+    public Connectable findConnectable(final String id) {
+        final ProcessorNode procNode = getProcessorNode(id);
+        if (procNode != null) {
+            return procNode;
+        }
+
+        final Port inPort = getInputPort(id);
+        if (inPort != null) {
+            return inPort;
+        }
+
+        final Port outPort = getOutputPort(id);
+        if (outPort != null) {
+            return outPort;
+        }
+
+        final Funnel funnel = getFunnel(id);
+        if (funnel != null) {
+            return funnel;
+        }
+
+        final RemoteGroupPort remoteGroupPort = getRootGroup().findRemoteGroupPort(id);
+        return remoteGroupPort;
+    }
+
+    public ProcessorNode getProcessorNode(final String id) {
+        return allProcessors.get(id);
+    }
+
+    public void onConnectionAdded(final Connection connection) {
+        allConnections.put(connection.getIdentifier(), connection);
+
+        if (isFlowInitialized()) {
+            connection.getFlowFileQueue().startLoadBalancing();
+        }
+    }
+
+    protected boolean isFlowInitialized() {
+        return flowInitializedCheck.getAsBoolean();
+    }
+
+    public void onConnectionRemoved(final Connection connection) {
+        String identifier = connection.getIdentifier();
+        flowFileEventRepository.purgeTransferEvents(identifier);
+        allConnections.remove(identifier);
+    }
+
+    public Connection getConnection(final String id) {
+        return allConnections.get(id);
+    }
+
+    public Set<Connection> findAllConnections() {
+        return new HashSet<>(allConnections.values());
+    }
+
+
+    public void setRootGroup(final ProcessGroup rootGroup) {
+        if (this.rootGroup != null && this.rootGroup.isEmpty()) {
+            allProcessGroups.remove(this.rootGroup.getIdentifier());
+        }
+
+        this.rootGroup = rootGroup;
+        allProcessGroups.put(ROOT_GROUP_ID_ALIAS, rootGroup);
+        allProcessGroups.put(rootGroup.getIdentifier(), rootGroup);
+    }
+
+    public ProcessGroup getRootGroup() {
+        return rootGroup;
+    }
+
+    @Override
+    public String getRootGroupId() {
+        return rootGroup.getIdentifier();
+    }
+
+    public boolean areGroupsSame(final String id1, final String id2) {
+        if (id1 == null || id2 == null) {
+            return false;
+        } else if (id1.equals(id2)) {
+            return true;
+        } else {
+            final String comparable1 = id1.equals(ROOT_GROUP_ID_ALIAS) ? getRootGroupId() : id1;
+            final String comparable2 = id2.equals(ROOT_GROUP_ID_ALIAS) ? getRootGroupId() : id2;
+            return comparable1.equals(comparable2);
+        }
+    }
+
+    @Override
+    public Map<String, Integer> getComponentCounts() {
+        final Map<String, Integer> componentCounts = new LinkedHashMap<>();
+        componentCounts.put("Processors", allProcessors.size());
+        componentCounts.put("Controller Services", getAllControllerServices().size());
+        componentCounts.put("Reporting Tasks", getAllReportingTasks().size());
+        componentCounts.put("Process Groups", allProcessGroups.size() - 2); // -2 to account for the root group because we don't want it in our counts and the 'root group alias' key.
+        componentCounts.put("Remote Process Groups", getRootGroup().findAllRemoteProcessGroups().size());
+
+        int localInputPorts = 0;
+        int publicInputPorts = 0;
+        for (final Port port : allInputPorts.values()) {
+            if (port instanceof PublicPort) {
+                publicInputPorts++;
+            } else {
+                localInputPorts++;
+            }
+        }
+
+        int localOutputPorts = 0;
+        int publicOutputPorts = 0;
+        for (final Port port : allOutputPorts.values()) {
+            if (port instanceof PublicPort) {
+                localOutputPorts++;
+            } else {
+                publicOutputPorts++;
+            }
+        }
+
+        componentCounts.put("Local Input Ports", localInputPorts);
+        componentCounts.put("Local Output Ports", localOutputPorts);
+        componentCounts.put("Public Input Ports", publicInputPorts);
+        componentCounts.put("Public Output Ports", publicOutputPorts);
+
+        return componentCounts;
+    }
+
+    @Override
+    public void purge() {
+        verifyCanPurge();
+
+        final ProcessGroup rootGroup = getRootGroup();
+
+        // Delete templates from all levels first. This allows us to avoid having to purge each individual Process Group recursively
+        // and instead just delete all child Process Groups after removing the connections to/from those Process Groups.
+        for (final ProcessGroup group : rootGroup.findAllProcessGroups()) {
+            group.getTemplates().forEach(group::removeTemplate);
+        }
+        rootGroup.getTemplates().forEach(rootGroup::removeTemplate);
+
+        rootGroup.getConnections().forEach(rootGroup::removeConnection);
+        rootGroup.getProcessors().forEach(rootGroup::removeProcessor);
+        rootGroup.getFunnels().forEach(rootGroup::removeFunnel);
+        rootGroup.getInputPorts().forEach(rootGroup::removeInputPort);
+        rootGroup.getOutputPorts().forEach(rootGroup::removeOutputPort);
+        rootGroup.getLabels().forEach(rootGroup::removeLabel);
+        rootGroup.getRemoteProcessGroups().forEach(rootGroup::removeRemoteProcessGroup);
+
+        rootGroup.getProcessGroups().forEach(rootGroup::removeProcessGroup);
+
+        rootGroup.getControllerServices(false).forEach(controllerServiceProvider::removeControllerService);
+
+        getRootControllerServices().forEach(this::removeRootControllerService);
+        getAllReportingTasks().forEach(this::removeReportingTask);
+
+        for (final String registryId : flowRegistryClient.getRegistryIdentifiers()) {
+            flowRegistryClient.removeFlowRegistry(registryId);
+        }
+
+        for (final ParameterContext parameterContext : parameterContextManager.getParameterContexts()) {
+            parameterContextManager.removeParameterContext(parameterContext.getIdentifier());
+        }
+    }
+
+    private void verifyCanPurge() {
+        for (final ControllerServiceNode serviceNode : getAllControllerServices()) {
+            serviceNode.verifyCanDelete();
+        }
+
+        for (final ReportingTaskNode reportingTask : getAllReportingTasks()) {
+            reportingTask.verifyCanDelete();
+        }
+
+        final ProcessGroup rootGroup = getRootGroup();
+        rootGroup.verifyCanDelete(true, true);
+    }
+
+    public Set<ControllerServiceNode> getAllControllerServices() {
+        final Set<ControllerServiceNode> allServiceNodes = new HashSet<>();
+        allServiceNodes.addAll(controllerServiceProvider.getNonRootControllerServices());
+        allServiceNodes.addAll(getRootControllerServices());
+        return allServiceNodes;
+    }
+
+    public ControllerServiceNode getControllerServiceNode(final String id) {
+        return controllerServiceProvider.getControllerServiceNode(id);
+    }
+
+    public void onInputPortAdded(final Port inputPort) {
+        allInputPorts.put(inputPort.getIdentifier(), inputPort);
+    }
+
+    public void onInputPortRemoved(final Port inputPort) {
+        String identifier = inputPort.getIdentifier();
+        flowFileEventRepository.purgeTransferEvents(identifier);
+        allInputPorts.remove(identifier);
+    }
+
+    public Port getInputPort(final String id) {
+        return allInputPorts.get(id);
+    }
+
+    public void onOutputPortAdded(final Port outputPort) {
+        allOutputPorts.put(outputPort.getIdentifier(), outputPort);
+    }
+
+    public void onOutputPortRemoved(final Port outputPort) {
+        String identifier = outputPort.getIdentifier();
+        flowFileEventRepository.purgeTransferEvents(identifier);
+        allOutputPorts.remove(identifier);
+    }
+
+    public Port getOutputPort(final String id) {
+        return allOutputPorts.get(id);
+    }
+
+    public void onFunnelAdded(final Funnel funnel) {
+        allFunnels.put(funnel.getIdentifier(), funnel);
+    }
+
+    public void onFunnelRemoved(final Funnel funnel) {
+        String identifier = funnel.getIdentifier();
+        flowFileEventRepository.purgeTransferEvents(identifier);
+        allFunnels.remove(identifier);
+    }
+
+    public Funnel getFunnel(final String id) {
+        return allFunnels.get(id);
+    }
+
+    public ProcessorNode createProcessor(final String type, final String id, final BundleCoordinate coordinate) {
+        return createProcessor(type, id, coordinate, true);
+    }
+
+    public ProcessorNode createProcessor(final String type, String id, final BundleCoordinate coordinate, final boolean firstTimeAdded) {
+        return createProcessor(type, id, coordinate, Collections.emptySet(), firstTimeAdded, true);
+    }
+
+    public ReportingTaskNode createReportingTask(final String type, final BundleCoordinate bundleCoordinate) {
+        return createReportingTask(type, bundleCoordinate, true);
+    }
+
+    public ReportingTaskNode createReportingTask(final String type, final BundleCoordinate bundleCoordinate, final boolean firstTimeAdded) {
+        return createReportingTask(type, UUID.randomUUID().toString(), bundleCoordinate, firstTimeAdded);
+    }
+
+    @Override
+    public ReportingTaskNode createReportingTask(final String type, final String id, final BundleCoordinate bundleCoordinate, final boolean firstTimeAdded) {
+        return createReportingTask(type, id, bundleCoordinate, Collections.emptySet(), firstTimeAdded, true);
+    }
+
+    public ReportingTaskNode getReportingTaskNode(final String taskId) {
+        return allReportingTasks.get(taskId);
+    }
+
+    @Override
+    public void removeReportingTask(final ReportingTaskNode reportingTaskNode) {
+        final ReportingTaskNode existing = allReportingTasks.get(reportingTaskNode.getIdentifier());
+        if (existing == null || existing != reportingTaskNode) {
+            throw new IllegalStateException("Reporting Task " + reportingTaskNode + " does not exist in this Flow");
+        }
+
+        reportingTaskNode.verifyCanDelete();
+
+        final Class<?> taskClass = reportingTaskNode.getReportingTask().getClass();
+        try (final NarCloseable x = NarCloseable.withComponentNarLoader(getExtensionManager(), taskClass, reportingTaskNode.getReportingTask().getIdentifier())) {
+            ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnRemoved.class, reportingTaskNode.getReportingTask(), reportingTaskNode.getConfigurationContext());
+        }
+
+        for (final Map.Entry<PropertyDescriptor, String> entry : reportingTaskNode.getEffectivePropertyValues().entrySet()) {
+            final PropertyDescriptor descriptor = entry.getKey();
+            if (descriptor.getControllerServiceDefinition() != null) {
+                final String value = entry.getValue() == null ? descriptor.getDefaultValue() : entry.getValue();
+                if (value != null) {
+                    final ControllerServiceNode serviceNode = controllerServiceProvider.getControllerServiceNode(value);
+                    if (serviceNode != null) {
+                        serviceNode.removeReference(reportingTaskNode, descriptor);
+                    }
+                }
+            }
+        }
+
+        allReportingTasks.remove(reportingTaskNode.getIdentifier());
+        LogRepositoryFactory.removeRepository(reportingTaskNode.getIdentifier());
+        getProcessScheduler().onReportingTaskRemoved(reportingTaskNode);
+
+        getExtensionManager().removeInstanceClassLoader(reportingTaskNode.getIdentifier());
+    }
+
+    public void onReportingTaskAdded(final ReportingTaskNode taskNode) {
+        allReportingTasks.put(taskNode.getIdentifier(), taskNode);
+    }
+
+    protected abstract ExtensionManager getExtensionManager();
+
+    protected abstract ProcessScheduler getProcessScheduler();
+
+    @Override
+    public Set<ReportingTaskNode> getAllReportingTasks() {
+        return new HashSet<>(allReportingTasks.values());
+    }
+
+    @Override
+    public ParameterContextManager getParameterContextManager() {
+        return parameterContextManager;
+    }
+
+    @Override
+    public ParameterContext createParameterContext(final String id, final String name, final Map<String, Parameter> parameters) {
+        final boolean namingConflict = parameterContextManager.getParameterContexts().stream()
+            .anyMatch(paramContext -> paramContext.getName().equals(name));
+
+        if (namingConflict) {
+            throw new IllegalStateException("Cannot create Parameter Context with name '" + name + "' because a Parameter Context already exists with that name");
+        }
+
+        final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(this);
+        final ParameterContext parameterContext = new StandardParameterContext(id, name, referenceManager, getParameterContextParent());
+        parameterContext.setParameters(parameters);
+        parameterContextManager.addParameterContext(parameterContext);
+        return parameterContext;
+    }
+
+    protected abstract Authorizable getParameterContextParent();
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/kerberos/KerberosConfig.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/kerberos/KerberosConfig.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/kerberos/KerberosConfig.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/kerberos/KerberosConfig.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/label/StandardLabel.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/label/StandardLabel.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/label/StandardLabel.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/label/StandardLabel.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/lifecycle/TaskTermination.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/lifecycle/TaskTermination.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/lifecycle/TaskTermination.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/lifecycle/TaskTermination.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/ConnectionEventListener.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/queue/ConnectionEventListener.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/ConnectionEventListener.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/queue/ConnectionEventListener.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/FlowFileQueueFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/queue/FlowFileQueueFactory.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/FlowFileQueueFactory.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/queue/FlowFileQueueFactory.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingContext.java
similarity index 60%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingContext.java
index 6793a4a..a844cc2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingContext.java
@@ -14,26 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.nifi.controller.reporting;
 
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.attribute.expression.language.PreparedQuery;
 import org.apache.nifi.attribute.expression.language.Query;
 import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
-import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.PropertyValue;
-import org.apache.nifi.components.state.StateManager;
 import org.apache.nifi.connectable.Connectable;
-import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.ControllerServiceLookup;
-import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.events.BulletinFactory;
+import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.registry.VariableRegistry;
 import org.apache.nifi.reporting.Bulletin;
 import org.apache.nifi.reporting.BulletinRepository;
-import org.apache.nifi.reporting.EventAccess;
 import org.apache.nifi.reporting.ReportingContext;
 import org.apache.nifi.reporting.ReportingTask;
 import org.apache.nifi.reporting.Severity;
@@ -42,34 +39,27 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Set;
-
-public class StandardReportingContext implements ReportingContext, ControllerServiceLookup {
 
-    private final FlowController flowController;
-    private final EventAccess eventAccess;
+public abstract class AbstractReportingContext implements ReportingContext {
     private final ReportingTask reportingTask;
     private final BulletinRepository bulletinRepository;
     private final ControllerServiceProvider serviceProvider;
     private final Map<PropertyDescriptor, String> properties;
     private final Map<PropertyDescriptor, PreparedQuery> preparedQueries;
-    private final VariableRegistry variableRegistry;
     private final ParameterLookup parameterLookup;
-    private final boolean analyticsEnabled;
+    private final VariableRegistry variableRegistry;
+
+    public AbstractReportingContext(final ReportingTask reportingTask, final BulletinRepository bulletinRepository,
+                                    final Map<PropertyDescriptor, String> properties, final ControllerServiceProvider controllerServiceProvider,
+                                    final ParameterLookup parameterLookup, final VariableRegistry variableRegistry) {
 
-    public StandardReportingContext(final FlowController flowController, final BulletinRepository bulletinRepository,
-                                    final Map<PropertyDescriptor, String> properties, final ReportingTask reportingTask,
-                                    final VariableRegistry variableRegistry, final ParameterLookup parameterLookup) {
-        this.flowController = flowController;
-        this.eventAccess = flowController.getEventAccess();
         this.bulletinRepository = bulletinRepository;
         this.properties = Collections.unmodifiableMap(properties);
-        this.serviceProvider = flowController.getControllerServiceProvider();
+        this.serviceProvider = controllerServiceProvider;
         this.reportingTask = reportingTask;
-        this.variableRegistry = variableRegistry;
         this.parameterLookup = parameterLookup;
-        this.analyticsEnabled = flowController.getStatusAnalyticsEngine() != null;
-        preparedQueries = new HashMap<>();
+        this.variableRegistry = variableRegistry;
+        this.preparedQueries = new HashMap<>();
 
         for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
             final PropertyDescriptor desc = entry.getKey();
@@ -83,9 +73,8 @@ public class StandardReportingContext implements ReportingContext, ControllerSer
         }
     }
 
-    @Override
-    public EventAccess getEventAccess() {
-        return eventAccess;
+    protected ReportingTask getReportingTask() {
+        return reportingTask;
     }
 
     @Override
@@ -94,20 +83,6 @@ public class StandardReportingContext implements ReportingContext, ControllerSer
     }
 
     @Override
-    public Bulletin createBulletin(final String category, final Severity severity, final String message) {
-        return BulletinFactory.createBulletin(category, severity.name(), message);
-    }
-
-    @Override
-    public Bulletin createBulletin(final String componentId, final String category, final Severity severity, final String message) {
-        final Connectable connectable = flowController.getFlowManager().findConnectable(componentId);
-        if (connectable == null) {
-            throw new IllegalStateException("Cannot create Component-Level Bulletin because no component can be found with ID " + componentId);
-        }
-        return BulletinFactory.createBulletin(connectable, category, severity.name(), message);
-    }
-
-    @Override
     public Map<PropertyDescriptor, String> getProperties() {
         return Collections.unmodifiableMap(properties);
     }
@@ -129,62 +104,27 @@ public class StandardReportingContext implements ReportingContext, ControllerSer
         }
 
         final String configuredValue = properties.get(property);
-        return new StandardPropertyValue(configuredValue == null ? descriptor.getDefaultValue() : configuredValue, this, parameterLookup, preparedQueries.get(property), variableRegistry);
-    }
-
-    @Override
-    public ControllerService getControllerService(final String serviceIdentifier) {
-        return serviceProvider.getControllerService(serviceIdentifier);
-    }
-
-    @Override
-    public Set<String> getControllerServiceIdentifiers(final Class<? extends ControllerService> serviceType) {
-        return serviceProvider.getControllerServiceIdentifiers(serviceType, null);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final ControllerService service) {
-        return serviceProvider.isControllerServiceEnabled(service);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final String serviceIdentifier) {
-        return serviceProvider.isControllerServiceEnabled(serviceIdentifier);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabling(final String serviceIdentifier) {
-        return serviceProvider.isControllerServiceEnabling(serviceIdentifier);
+        return new StandardPropertyValue(configuredValue == null ? descriptor.getDefaultValue() : configuredValue, serviceProvider, parameterLookup, preparedQueries.get(property), variableRegistry);
     }
 
     @Override
     public ControllerServiceLookup getControllerServiceLookup() {
-        return this;
-    }
-
-    @Override
-    public String getControllerServiceName(final String serviceIdentifier) {
-        return serviceProvider.getControllerServiceName(serviceIdentifier);
-    }
-
-    @Override
-    public StateManager getStateManager() {
-        return flowController.getStateManagerProvider().getStateManager(reportingTask.getIdentifier());
+        return serviceProvider;
     }
 
     @Override
-    public boolean isClustered() {
-        return flowController.isConfiguredForClustering();
+    public Bulletin createBulletin(final String category, final Severity severity, final String message) {
+        return BulletinFactory.createBulletin(category, severity.name(), message);
     }
 
     @Override
-    public String getClusterNodeIdentifier() {
-        final NodeIdentifier nodeId = flowController.getNodeId();
-        return nodeId == null ? null : nodeId.getId();
+    public Bulletin createBulletin(final String componentId, final String category, final Severity severity, final String message) {
+        final Connectable connectable = getFlowManager().findConnectable(componentId);
+        if (connectable == null) {
+            throw new IllegalStateException("Cannot create Component-Level Bulletin because no component can be found with ID " + componentId);
+        }
+        return BulletinFactory.createBulletin(connectable, category, severity.name(), message);
     }
 
-    @Override
-    public boolean isAnalyticsEnabled() {
-        return this.analyticsEnabled;
-    }
+    protected abstract FlowManager getFlowManager();
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
similarity index 98%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
index d43c049..60a1943 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
@@ -17,7 +17,6 @@
 package org.apache.nifi.controller.reporting;
 
 import org.apache.nifi.annotation.configuration.DefaultSchedule;
-import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.ConfigurableComponent;
 import org.apache.nifi.components.ValidationResult;
@@ -37,6 +36,7 @@ import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.service.StandardConfigurationContext;
 import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.registry.ComponentVariableRegistry;
 import org.apache.nifi.reporting.ReportingTask;
 import org.apache.nifi.scheduling.SchedulingStrategy;
@@ -45,7 +45,6 @@ import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.core.annotation.AnnotationUtils;
 
 import java.net.URL;
 import java.util.Collection;
@@ -92,7 +91,7 @@ public abstract class AbstractReportingTaskNode extends AbstractComponentNode im
 
         final Class<?> reportingClass = reportingTask.getComponent().getClass();
 
-        DefaultSchedule dsc = AnnotationUtils.findAnnotation(reportingClass, DefaultSchedule.class);
+        final DefaultSchedule dsc = reportingClass.getAnnotation(DefaultSchedule.class);
         if(dsc != null) {
             try {
                 this.setSchedulingStrategy(dsc.strategy());
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/ReportingTaskDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/ReportingTaskDetails.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/ReportingTaskDetails.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/ReportingTaskDetails.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/StandardReportingInitializationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingInitializationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/StandardReportingInitializationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
similarity index 70%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
index 44675ea..8f3c427 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/AbstractRepositoryContext.java
@@ -14,26 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.nifi.controller.repository;
 
 import org.apache.nifi.connectable.Connectable;
 import org.apache.nifi.connectable.ConnectableType;
 import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.provenance.InternalProvenanceReporter;
+import org.apache.nifi.provenance.ProvenanceEventBuilder;
+import org.apache.nifi.provenance.ProvenanceEventRecord;
 import org.apache.nifi.provenance.ProvenanceEventRepository;
+import org.apache.nifi.provenance.StandardProvenanceEventRecord;
 import org.apache.nifi.util.Connectables;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
 
-public class RepositoryContext {
-
+public abstract class AbstractRepositoryContext implements RepositoryContext {
     private final Connectable connectable;
     private final ContentRepository contentRepo;
     private final FlowFileRepository flowFileRepo;
@@ -42,9 +46,9 @@ public class RepositoryContext {
     private final ProvenanceEventRepository provenanceRepo;
     private final AtomicLong connectionIndex;
 
-    public RepositoryContext(final Connectable connectable, final AtomicLong connectionIndex, final ContentRepository contentRepository,
-            final FlowFileRepository flowFileRepository, final FlowFileEventRepository flowFileEventRepository,
-            final CounterRepository counterRepository, final ProvenanceEventRepository provenanceRepository) {
+    public AbstractRepositoryContext(final Connectable connectable, final AtomicLong connectionIndex, final ContentRepository contentRepository,
+                                     final FlowFileRepository flowFileRepository, final FlowFileEventRepository flowFileEventRepository,
+                                     final CounterRepository counterRepository, final ProvenanceEventRepository provenanceRepository) {
         this.connectable = connectable;
         contentRepo = contentRepository;
         flowFileRepo = flowFileRepository;
@@ -55,7 +59,8 @@ public class RepositoryContext {
         this.connectionIndex = connectionIndex;
     }
 
-    Connectable getConnectable() {
+    @Override
+    public Connectable getConnectable() {
         return connectable;
     }
 
@@ -64,7 +69,8 @@ public class RepositoryContext {
      * @param relationship relationship
      * @return connections for relationship
      */
-    Collection<Connection> getConnections(final Relationship relationship) {
+    @Override
+    public Collection<Connection> getConnections(final Relationship relationship) {
         Collection<Connection> collection = connectable.getConnections(relationship);
         if (collection == null) {
             collection = new ArrayList<>();
@@ -75,7 +81,8 @@ public class RepositoryContext {
     /**
      * @return an unmodifiable list containing a copy of all incoming connections for the processor from which FlowFiles are allowed to be pulled
      */
-    List<Connection> getPollableConnections() {
+    @Override
+    public List<Connection> getPollableConnections() {
         if (pollFromSelfLoopsOnly()) {
             final List<Connection> selfLoops = new ArrayList<>();
             for (final Connection connection : connectable.getIncomingConnections()) {
@@ -120,7 +127,8 @@ public class RepositoryContext {
         return false;
     }
 
-    void adjustCounter(final String name, final long delta) {
+    @Override
+    public void adjustCounter(final String name, final long delta) {
         final String localContext = connectable.getName() + " (" + connectable.getIdentifier() + ")";
         final String globalContext = "All " + connectable.getComponentType() + "'s";
 
@@ -128,79 +136,37 @@ public class RepositoryContext {
         counterRepo.adjustCounter(globalContext, name, delta);
     }
 
+    @Override
     public ContentRepository getContentRepository() {
         return contentRepo;
     }
 
+    @Override
     public FlowFileRepository getFlowFileRepository() {
         return flowFileRepo;
     }
 
+    @Override
     public FlowFileEventRepository getFlowFileEventRepository() {
         return flowFileEventRepo;
     }
 
+    @Override
     public ProvenanceEventRepository getProvenanceRepository() {
         return provenanceRepo;
     }
 
-    long getNextFlowFileSequence() {
+    @Override
+    public long getNextFlowFileSequence() {
         return flowFileRepo.getNextFlowFileSequence();
     }
 
-    int getNextIncomingConnectionIndex() {
+    @Override
+    public int getNextIncomingConnectionIndex() {
         final int numIncomingConnections = connectable.getIncomingConnections().size();
         return (int) (connectionIndex.getAndIncrement() % Math.max(1, numIncomingConnections));
     }
 
-    public boolean isAnyRelationshipAvailable() {
-        for (final Relationship relationship : getConnectable().getRelationships()) {
-            final Collection<Connection> connections = getConnections(relationship);
-
-            boolean available = true;
-            for (final Connection connection : connections) {
-                if (connection.getFlowFileQueue().isFull()) {
-                    available = false;
-                    break;
-                }
-            }
-
-            if (available) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public int getAvailableRelationshipCount() {
-        int count = 0;
-        for (final Relationship relationship : connectable.getRelationships()) {
-            final Collection<Connection> connections = connectable.getConnections(relationship);
-            if (connections == null || connections.isEmpty()) {
-                count++;
-            } else {
-                boolean available = true;
-                for (final Connection connection : connections) {
-                    // consider self-loops available
-                    if (connection.getSource() == connection.getDestination()) {
-                        continue;
-                    }
-
-                    if (connection.getFlowFileQueue().isFull()) {
-                        available = false;
-                        break;
-                    }
-                }
-
-                if (available) {
-                    count++;
-                }
-            }
-        }
-
-        return count;
-    }
 
     /**
      * A Relationship is said to be Available if and only if all Connections for that Relationship are either self-loops or have non-full queues.
@@ -208,6 +174,7 @@ public class RepositoryContext {
      * @param requiredNumber minimum number of relationships that must have availability
      * @return Checks if at least <code>requiredNumber</code> of Relationationships are "available." If so, returns <code>true</code>, otherwise returns <code>false</code>
      */
+    @Override
     public boolean isRelationshipAvailabilitySatisfied(final int requiredNumber) {
         int unavailable = 0;
 
@@ -245,26 +212,43 @@ public class RepositoryContext {
         return true;
     }
 
-    public Set<Relationship> getAvailableRelationships() {
-        final Set<Relationship> set = new HashSet<>();
-        for (final Relationship relationship : getConnectable().getRelationships()) {
-            final Collection<Connection> connections = getConnections(relationship);
-            if (connections.isEmpty()) {
-                set.add(relationship);
-            } else {
-                boolean available = true;
-                for (final Connection connection : connections) {
-                    if (connection.getFlowFileQueue().isFull()) {
-                        available = false;
-                    }
-                }
+    protected String getProvenanceComponentDescription() {
+        switch (connectable.getConnectableType()) {
+            case PROCESSOR:
+                final ProcessorNode procNode = (ProcessorNode) connectable;
+                return procNode.getComponentType();
+            case INPUT_PORT:
+                return "Input Port";
+            case OUTPUT_PORT:
+                return "Output Port";
+            case REMOTE_INPUT_PORT:
+                return ProvenanceEventRecord.REMOTE_INPUT_PORT_TYPE;
+            case REMOTE_OUTPUT_PORT:
+                return ProvenanceEventRecord.REMOTE_OUTPUT_PORT_TYPE;
+            case FUNNEL:
+                return "Funnel";
+            default:
+                throw new AssertionError("Connectable type is " + connectable.getConnectableType());
+        }
+    }
 
-                if (available) {
-                    set.add(relationship);
-                }
-            }
+    @Override
+    public String getConnectableDescription() {
+        if (connectable.getConnectableType() == ConnectableType.PROCESSOR) {
+            return ((ProcessorNode) connectable).getProcessor().toString();
         }
 
-        return set;
+        return connectable.toString();
+    }
+
+    @Override
+    public ProvenanceEventBuilder createProvenanceEventBuilder() {
+        return new StandardProvenanceEventRecord.Builder();
+    }
+
+    @Override
+    public InternalProvenanceReporter createProvenanceReporter(final Predicate<FlowFile> flowfileKnownCheck, final ProvenanceEventEnricher eventEnricher) {
+        final String componentType = getProvenanceComponentDescription();
+        return new StandardProvenanceReporter(flowfileKnownCheck, getConnectable().getIdentifier(), componentType, getProvenanceRepository(), eventEnricher);
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/ProvenanceEventEnricher.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/ProvenanceEventEnricher.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/ProvenanceEventEnricher.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/ProvenanceEventEnricher.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
new file mode 100644
index 0000000..d5aa9f2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/RepositoryContext.java
@@ -0,0 +1,63 @@
+/*
+ * 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.controller.repository;
+
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.controller.repository.claim.ContentClaimWriteCache;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.provenance.InternalProvenanceReporter;
+import org.apache.nifi.provenance.ProvenanceEventBuilder;
+import org.apache.nifi.provenance.ProvenanceEventRepository;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+
+public interface RepositoryContext {
+    Connectable getConnectable();
+
+    Collection<Connection> getConnections(Relationship relationship);
+
+    List<Connection> getPollableConnections();
+
+    ContentRepository getContentRepository();
+
+    FlowFileRepository getFlowFileRepository();
+
+    FlowFileEventRepository getFlowFileEventRepository();
+
+    ProvenanceEventRepository getProvenanceRepository();
+
+    boolean isRelationshipAvailabilitySatisfied(int requiredNumber);
+
+    ContentClaimWriteCache createContentClaimWriteCache();
+
+    InternalProvenanceReporter createProvenanceReporter(Predicate<FlowFile> flowfileKnownCheck, ProvenanceEventEnricher eventEnricher);
+
+    String getConnectableDescription();
+
+    int getNextIncomingConnectionIndex();
+
+    long getNextFlowFileSequence();
+
+    void adjustCounter(String name, long delta);
+
+    ProvenanceEventBuilder createProvenanceEventBuilder();
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardCounterRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardCounterRepository.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardCounterRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardCounterRepository.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
similarity index 94%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
index 84c8b1f..1e3af37 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
@@ -47,12 +47,12 @@ import org.apache.nifi.processor.exception.TerminatedTaskException;
 import org.apache.nifi.processor.io.InputStreamCallback;
 import org.apache.nifi.processor.io.OutputStreamCallback;
 import org.apache.nifi.processor.io.StreamCallback;
+import org.apache.nifi.provenance.InternalProvenanceReporter;
 import org.apache.nifi.provenance.ProvenanceEventBuilder;
 import org.apache.nifi.provenance.ProvenanceEventRecord;
 import org.apache.nifi.provenance.ProvenanceEventRepository;
 import org.apache.nifi.provenance.ProvenanceEventType;
 import org.apache.nifi.provenance.ProvenanceReporter;
-import org.apache.nifi.provenance.StandardProvenanceEventRecord;
 import org.apache.nifi.stream.io.ByteCountingInputStream;
 import org.apache.nifi.stream.io.ByteCountingOutputStream;
 import org.apache.nifi.stream.io.StreamUtils;
@@ -101,13 +101,7 @@ import java.util.stream.Collectors;
  * </p>
  * <p/>
  */
-public final class StandardProcessSession implements ProcessSession, ProvenanceEventEnricher {
-    private static final int SOURCE_EVENT_BIT_INDEXES = (1 << ProvenanceEventType.CREATE.ordinal())
-        | (1 << ProvenanceEventType.FORK.ordinal())
-        | (1 << ProvenanceEventType.JOIN.ordinal())
-        | (1 << ProvenanceEventType.RECEIVE.ordinal())
-        | (1 << ProvenanceEventType.FETCH.ordinal());
-
+public class StandardProcessSession implements ProcessSession, ProvenanceEventEnricher {
     private static final AtomicLong idGenerator = new AtomicLong(0L);
     private static final AtomicLong enqueuedIndex = new AtomicLong(0L);
 
@@ -137,7 +131,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
     private final Set<String> removedFlowFiles = new HashSet<>();
     private final Set<String> createdFlowFiles = new HashSet<>();
 
-    private final StandardProvenanceReporter provenanceReporter;
+    private final InternalProvenanceReporter provenanceReporter;
 
     private int removedCount = 0; // number of flowfiles removed in this session
     private long removedBytes = 0L; // size of all flowfiles removed in this session
@@ -171,40 +165,10 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         this.context = context;
         this.taskTermination = taskTermination;
 
-        final Connectable connectable = context.getConnectable();
-        final String componentType;
-
-        String description = connectable.toString();
-        switch (connectable.getConnectableType()) {
-            case PROCESSOR:
-                final ProcessorNode procNode = (ProcessorNode) connectable;
-                componentType = procNode.getComponentType();
-                description = procNode.getProcessor().toString();
-                break;
-            case INPUT_PORT:
-                componentType = "Input Port";
-                break;
-            case OUTPUT_PORT:
-                componentType = "Output Port";
-                break;
-            case REMOTE_INPUT_PORT:
-                componentType = ProvenanceEventRecord.REMOTE_INPUT_PORT_TYPE;
-                break;
-            case REMOTE_OUTPUT_PORT:
-                componentType = ProvenanceEventRecord.REMOTE_OUTPUT_PORT_TYPE;
-                break;
-            case FUNNEL:
-                componentType = "Funnel";
-                break;
-            default:
-                throw new AssertionError("Connectable type is " + connectable.getConnectableType());
-        }
-
-        this.provenanceReporter = new StandardProvenanceReporter(this, connectable.getIdentifier(), componentType,
-            context.getProvenanceRepository(), this);
+        this.provenanceReporter = context.createProvenanceReporter(this::isFlowFileKnown, this);
         this.sessionId = idGenerator.getAndIncrement();
-        this.connectableDescription = description;
-        this.claimCache = new ContentClaimWriteCache(context.getContentRepository());
+        this.connectableDescription = context.getConnectableDescription();
+        this.claimCache = context.createContentClaimWriteCache();
         LOG.trace("Session {} created for {}", this, connectableDescription);
         processingStartTime = System.nanoTime();
     }
@@ -216,7 +180,15 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         }
     }
 
+    protected long getSessionId() {
+        return sessionId;
+    }
+
     private void closeStreams(final Map<FlowFile, ? extends Closeable> streamMap, final String action, final String streamType) {
+        if (streamMap.isEmpty()) {
+            return;
+        }
+
         final Map<FlowFile, ? extends Closeable> openStreamCopy = new HashMap<>(streamMap); // avoid ConcurrentModificationException by creating a copy of the List
         for (final Map.Entry<FlowFile, ? extends Closeable> entry : openStreamCopy.entrySet()) {
             final FlowFile flowFile = entry.getKey();
@@ -234,6 +206,11 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
     }
 
     public void checkpoint() {
+        checkpoint(true);
+        resetState();
+    }
+
+    private void checkpoint(final boolean copyCollections) {
         verifyTaskActive();
         resetWriteClaims(false);
 
@@ -253,6 +230,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
 
         if (records.isEmpty() && (countersOnCommit == null || countersOnCommit.isEmpty())) {
             LOG.trace("{} checkpointed, but no events were performed by this ProcessSession", this);
+            checkpoint.checkpoint(this, Collections.emptyList(), copyCollections);
             return;
         }
 
@@ -336,20 +314,23 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         records.putAll(toAdd);
         toAdd.clear();
 
-        checkpoint.checkpoint(this, autoTerminatedEvents);
-        resetState();
+        checkpoint.checkpoint(this, autoTerminatedEvents, copyCollections);
     }
 
     @Override
     public synchronized void commit() {
         verifyTaskActive();
-        checkpoint();
+        checkpoint(this.checkpoint != null); // If a checkpoint already exists, we need to copy the collection
         commit(this.checkpoint);
+
+        acknowledgeRecords();
+        resetState();
+
         this.checkpoint = null;
     }
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    private void commit(final Checkpoint checkpoint) {
+    protected void commit(final Checkpoint checkpoint) {
         try {
             final long commitStartNanos = System.nanoTime();
 
@@ -444,9 +425,6 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
                 context.adjustCounter(entry.getKey(), entry.getValue());
             }
 
-            acknowledgeRecords();
-            resetState();
-
             if (LOG.isDebugEnabled()) {
                 final StringBuilder timingInfo = new StringBuilder();
                 timingInfo.append("Session commit for ").append(this).append(" [").append(connectableDescription).append("]").append(" took ");
@@ -553,7 +531,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         return records.get(flowFile.getId());
     }
 
-    private void updateProvenanceRepo(final Checkpoint checkpoint) {
+    protected void updateProvenanceRepo(final Checkpoint checkpoint) {
         // Update Provenance Repository
         final ProvenanceEventRepository provenanceRepo = context.getProvenanceRepository();
 
@@ -775,7 +753,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
     }
 
     @Override
-    public StandardProvenanceEventRecord enrich(final ProvenanceEventRecord rawEvent, final FlowFile flowFile, final long commitNanos) {
+    public ProvenanceEventRecord enrich(final ProvenanceEventRecord rawEvent, final FlowFile flowFile, final long commitNanos) {
         verifyTaskActive();
 
         final StandardRepositoryRecord repoRecord = getRecord(flowFile);
@@ -783,7 +761,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
             throw new FlowFileHandlingException(flowFile + " is not known in this session (" + toString() + ")");
         }
 
-        final StandardProvenanceEventRecord.Builder recordBuilder = new StandardProvenanceEventRecord.Builder().fromEvent(rawEvent);
+        final ProvenanceEventBuilder recordBuilder = context.createProvenanceEventBuilder().fromEvent(rawEvent);
         if (repoRecord.getCurrent() != null && repoRecord.getCurrentClaim() != null) {
             final ContentClaim currentClaim = repoRecord.getCurrentClaim();
             final long currentOffset = repoRecord.getCurrentClaimOffset();
@@ -814,10 +792,10 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         return recordBuilder.build();
     }
 
-    private StandardProvenanceEventRecord enrich(
+    private ProvenanceEventRecord enrich(
         final ProvenanceEventRecord rawEvent, final Map<String, FlowFileRecord> flowFileRecordMap, final Map<Long, StandardRepositoryRecord> records,
         final boolean updateAttributes, final long commitNanos) {
-        final StandardProvenanceEventRecord.Builder recordBuilder = new StandardProvenanceEventRecord.Builder().fromEvent(rawEvent);
+        final ProvenanceEventBuilder recordBuilder = context.createProvenanceEventBuilder().fromEvent(rawEvent);
         final FlowFileRecord eventFlowFile = flowFileRecordMap.get(rawEvent.getFlowFileUuid());
         if (eventFlowFile != null) {
             final StandardRepositoryRecord repoRecord = records.get(eventFlowFile.getId());
@@ -925,7 +903,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         verifyTaskActive();
     }
 
-    private synchronized void rollback(final boolean penalize, final boolean rollbackCheckpoint) {
+    protected synchronized void rollback(final boolean penalize, final boolean rollbackCheckpoint) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} session rollback called, FlowFile records are {} {}",
                     this, loggableFlowfileInfo(), new Throwable("Stack Trace on rollback"));
@@ -942,8 +920,13 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
             LOG.warn("{} Attempted to close Output Stream for {} due to session rollback but close failed", this, this.connectableDescription, e1);
         }
 
-        final Set<StandardRepositoryRecord> recordsToHandle = new HashSet<>();
-        recordsToHandle.addAll(records.values());
+        // Gather all of the StandardRepositoryRecords that we need to operate on.
+        // If we are rolling back the checkpoint, we must create a copy of the Collection so that we can merge the
+        // session's records with the checkpoint's. Otherwise, we can operate on the session's records directly.
+        // Because every session is rolled back, we want to avoid creating this defensive copy of the HashSet if
+        // we don't need to.
+        final Collection<StandardRepositoryRecord> recordValues = records.values();
+        final Collection<StandardRepositoryRecord> recordsToHandle = rollbackCheckpoint ? new HashSet<>(recordValues) : recordValues;
         if (rollbackCheckpoint) {
             final Checkpoint existingCheckpoint = this.checkpoint;
             this.checkpoint = null;
@@ -980,18 +963,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
 
         // Put the FlowFiles that are not marked for abort back to their original queues
         for (final StandardRepositoryRecord record : transferRecords) {
-            if (record.getOriginal() != null) {
-                final FlowFileQueue originalQueue = record.getOriginalQueue();
-                if (originalQueue != null) {
-                    if (penalize) {
-                        final long expirationEpochMillis = System.currentTimeMillis() + context.getConnectable().getPenalizationPeriod(TimeUnit.MILLISECONDS);
-                        final FlowFileRecord newFile = new StandardFlowFileRecord.Builder().fromFlowFile(record.getOriginal()).penaltyExpirationTime(expirationEpochMillis).build();
-                        originalQueue.put(newFile);
-                    } else {
-                        originalQueue.put(record.getOriginal());
-                    }
-                }
-            }
+            rollbackRecord(record, penalize);
         }
 
         if (!abortedRecords.isEmpty()) {
@@ -1042,6 +1014,28 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         resetState();
     }
 
+    /**
+     * Rolls back the Record in a manner that is appropriate for the context. The default implementation
+     * is to place the queue back on its original queue, if it exists, or just ignore it if it has no original queue.
+     * However, subclasses may wish to change the behavior for how Records are handled when a rollback occurs.
+     * @param record the Record that is to be rolled back
+     * @param penalize whether or not the Record should be penalized
+     */
+    protected void rollbackRecord(final StandardRepositoryRecord record, final boolean penalize) {
+        if (record.getOriginal() != null) {
+            final FlowFileQueue originalQueue = record.getOriginalQueue();
+            if (originalQueue != null) {
+                if (penalize) {
+                    final long expirationEpochMillis = System.currentTimeMillis() + context.getConnectable().getPenalizationPeriod(TimeUnit.MILLISECONDS);
+                    final FlowFileRecord newFile = new StandardFlowFileRecord.Builder().fromFlowFile(record.getOriginal()).penaltyExpirationTime(expirationEpochMillis).build();
+                    originalQueue.put(newFile);
+                } else {
+                    originalQueue.put(record.getOriginal());
+                }
+            }
+        }
+    }
+
     private String loggableFlowfileInfo() {
         final StringBuilder details = new StringBuilder(1024).append("[");
         final int initLen = details.length();
@@ -1536,6 +1530,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         final int numConnections = connections.size();
         for (int numAttempts = 0; numAttempts < numConnections; numAttempts++) {
             final Connection conn = connections.get(context.getNextIncomingConnectionIndex() % numConnections);
+            // TODO: We create this Set<FlowFileRecord> every time. Instead, add FlowFileQueue.isExpirationConfigured(). If false, pass Collections.emptySet(). Same for all get() methods.
             final Set<FlowFileRecord> expired = new HashSet<>();
             final FlowFileRecord flowFile = conn.poll(expired);
             removeExpired(expired, conn);
@@ -1869,6 +1864,10 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
     public FlowFile putAllAttributes(FlowFile flowFile, final Map<String, String> attributes) {
         verifyTaskActive();
 
+        if (attributes.isEmpty()) {
+            return flowFile;
+        }
+
         flowFile = validateRecordState(flowFile);
         final StandardRepositoryRecord record = getRecord(flowFile);
 
@@ -2143,8 +2142,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
 
         final Connectable connectable = context.getConnectable();
         final String processorType = connectable.getComponentType();
-        final StandardProvenanceReporter expiredReporter = new StandardProvenanceReporter(this, connectable.getIdentifier(),
-            processorType, context.getProvenanceRepository(), this);
+        final InternalProvenanceReporter expiredReporter = context.createProvenanceReporter(this::isFlowFileKnown, this);
 
         final Map<String, FlowFileRecord> recordIdMap = new HashMap<>();
         for (final FlowFileRecord flowFile : flowFiles) {
@@ -2174,7 +2172,7 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
                         @Override
                         public ProvenanceEventRecord next() {
                             final ProvenanceEventRecord event = expiredEventIterator.next();
-                            final StandardProvenanceEventRecord.Builder enriched = new StandardProvenanceEventRecord.Builder().fromEvent(event);
+                            final ProvenanceEventBuilder enriched = context.createProvenanceEventBuilder().fromEvent(event);
                             final FlowFileRecord record = recordIdMap.get(event.getFlowFileUuid());
                             if (record == null) {
                                 return null;
@@ -3337,24 +3335,24 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         List<FlowFileRecord> poll(Connection connection, Set<FlowFileRecord> expiredRecords);
     }
 
-    private static class Checkpoint {
+    protected static class Checkpoint {
 
         private long processingTime = 0L;
 
-        private final Map<FlowFile, List<ProvenanceEventRecord>> generatedProvenanceEvents = new HashMap<>();
-        private final Map<FlowFile, ProvenanceEventBuilder> forkEventBuilders = new HashMap<>();
-        private final List<ProvenanceEventRecord> autoTerminatedEvents = new ArrayList<>();
-        private final Set<ProvenanceEventRecord> reportedEvents = new LinkedHashSet<>();
+        private Map<FlowFile, List<ProvenanceEventRecord>> generatedProvenanceEvents;
+        private Map<FlowFile, ProvenanceEventBuilder> forkEventBuilders;
+        private List<ProvenanceEventRecord> autoTerminatedEvents;
+        private Set<ProvenanceEventRecord> reportedEvents;
 
-        private final Map<Long, StandardRepositoryRecord> records = new ConcurrentHashMap<>();
-        private final Map<String, StandardFlowFileEvent> connectionCounts = new ConcurrentHashMap<>();
+        private Map<Long, StandardRepositoryRecord> records;
+        private Map<String, StandardFlowFileEvent> connectionCounts;
 
-        private Map<String, Long> countersOnCommit = new HashMap<>();
-        private Map<String, Long> immediateCounters = new HashMap<>();
+        private Map<String, Long> countersOnCommit;
+        private Map<String, Long> immediateCounters;
 
-        private final Map<FlowFile, Path> deleteOnCommit = new HashMap<>();
-        private final Set<String> removedFlowFiles = new HashSet<>();
-        private final Set<String> createdFlowFiles = new HashSet<>();
+        private Map<FlowFile, Path> deleteOnCommit;
+        private Set<String> removedFlowFiles;
+        private Set<String> createdFlowFiles;
 
         private int removedCount = 0; // number of flowfiles removed in this session
         private long removedBytes = 0L; // size of all flowfiles removed in this session
@@ -3365,7 +3363,82 @@ public final class StandardProcessSession implements ProcessSession, ProvenanceE
         private int flowFilesReceived = 0, flowFilesSent = 0;
         private long bytesReceived = 0L, bytesSent = 0L;
 
-        private void checkpoint(final StandardProcessSession session, final List<ProvenanceEventRecord> autoTerminatedEvents) {
+        private boolean initialized = false;
+
+        private void initializeForCopy() {
+            if (initialized) {
+                return;
+            }
+
+            generatedProvenanceEvents = new HashMap<>();
+            forkEventBuilders = new HashMap<>();
+            autoTerminatedEvents = new ArrayList<>();
+            reportedEvents = new LinkedHashSet<>();
+
+            records = new ConcurrentHashMap<>();
+            connectionCounts = new ConcurrentHashMap<>();
+
+            countersOnCommit = new HashMap<>();
+            immediateCounters = new HashMap<>();
+
+            deleteOnCommit = new HashMap<>();
+            removedFlowFiles = new HashSet<>();
+            createdFlowFiles = new HashSet<>();
+
+            initialized = true;
+        }
+
+        private void checkpoint(final StandardProcessSession session, final List<ProvenanceEventRecord> autoTerminatedEvents, final boolean copy) {
+            if (copy) {
+                copyCheckpoint(session, autoTerminatedEvents);
+            } else {
+                directCheckpoint(session, autoTerminatedEvents);
+            }
+        }
+
+        /**
+         * Checkpoints the Process Session by adding references to the existing collections within the Process Session. Any modifications made to the Checkpoint's
+         * Collections or the Process Session's collections will be reflected by the other. I.e., this is a copy-by-reference.
+         */
+        private void directCheckpoint(final StandardProcessSession session, final List<ProvenanceEventRecord> autoTerminatedEvents) {
+            this.processingTime = System.nanoTime() - session.processingStartTime;
+
+            this.generatedProvenanceEvents = session.generatedProvenanceEvents;
+            this.forkEventBuilders = session.forkEventBuilders;
+            this.autoTerminatedEvents = autoTerminatedEvents;
+            this.reportedEvents = session.provenanceReporter.getEvents();
+
+            this.records = session.records;
+
+            this.connectionCounts = session.connectionCounts;
+            this.countersOnCommit = session.countersOnCommit == null ? Collections.emptyMap() : session.countersOnCommit;
+            this.immediateCounters = session.immediateCounters == null ? Collections.emptyMap() : session.immediateCounters;
+
+            this.deleteOnCommit = session.deleteOnCommit;
+            this.removedFlowFiles = session.removedFlowFiles;
+            this.createdFlowFiles = session.createdFlowFiles;
+
+            this.removedCount = session.removedCount;
+            this.removedBytes = session.removedBytes;
+            this.bytesRead = session.bytesRead;
+            this.bytesWritten = session.bytesWritten;
+            this.flowFilesIn = session.flowFilesIn;
+            this.flowFilesOut = session.flowFilesOut;
+            this.contentSizeIn = session.contentSizeIn;
+            this.contentSizeOut = session.contentSizeOut;
+            this.flowFilesReceived = session.provenanceReporter.getFlowFilesReceived() + session.provenanceReporter.getFlowFilesFetched();
+            this.bytesReceived = session.provenanceReporter.getBytesReceived() + session.provenanceReporter.getBytesFetched();
+            this.flowFilesSent = session.provenanceReporter.getFlowFilesSent();
+            this.bytesSent = session.provenanceReporter.getBytesSent();
+        }
+
+        /**
+         * Checkpoints the Process Session by copying all information from the session's collections into this Checkpoint's collections.
+         * This is necessary if multiple Process Sessions are to be batched together. I.e., this is a copy-by-value
+         */
+        private void copyCheckpoint(final StandardProcessSession session, final List<ProvenanceEventRecord> autoTerminatedEvents) {
+            initializeForCopy();
+
             this.processingTime += System.nanoTime() - session.processingStartTime;
 
             this.generatedProvenanceEvents.putAll(session.generatedProvenanceEvents);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSessionFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSessionFactory.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProcessSessionFactory.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSessionFactory.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java
similarity index 91%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java
index cbe1c66..7c31f9c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProvenanceReporter.java
@@ -16,23 +16,24 @@
  */
 package org.apache.nifi.controller.repository;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.FlowFileHandlingException;
+import org.apache.nifi.provenance.InternalProvenanceReporter;
 import org.apache.nifi.provenance.ProvenanceEventBuilder;
 import org.apache.nifi.provenance.ProvenanceEventRecord;
 import org.apache.nifi.provenance.ProvenanceEventRepository;
 import org.apache.nifi.provenance.ProvenanceEventType;
-import org.apache.nifi.provenance.ProvenanceReporter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class StandardProvenanceReporter implements ProvenanceReporter {
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public class StandardProvenanceReporter implements InternalProvenanceReporter {
 
     private final Logger logger = LoggerFactory.getLogger(StandardProvenanceReporter.class);
     private final String processorId;
@@ -40,7 +41,7 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
     private final Set<ProvenanceEventRecord> events = new LinkedHashSet<>();
     private final ProvenanceEventRepository repository;
     private final ProvenanceEventEnricher eventEnricher;
-    private final StandardProcessSession session;
+    private final Predicate<FlowFile> flowfileKnownCheck;
     private long bytesSent = 0L;
     private long bytesReceived = 0L;
     private int flowFilesSent = 0;
@@ -48,16 +49,17 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
     private int flowFilesFetched = 0;
     private long bytesFetched = 0L;
 
-    public StandardProvenanceReporter(final StandardProcessSession session, final String processorId, final String processorType,
+    public StandardProvenanceReporter(final Predicate<FlowFile> flowfileKnownCheck, final String processorId, final String processorType,
         final ProvenanceEventRepository repository, final ProvenanceEventEnricher enricher) {
-        this.session = session;
+        this.flowfileKnownCheck = flowfileKnownCheck;
         this.processorId = processorId;
         this.processorType = processorType;
         this.repository = repository;
         this.eventEnricher = enricher;
     }
 
-    Set<ProvenanceEventRecord> getEvents() {
+    @Override
+    public Set<ProvenanceEventRecord> getEvents() {
         return Collections.unmodifiableSet(events);
     }
 
@@ -66,15 +68,27 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
      *
      * @param event event
      */
-    void remove(final ProvenanceEventRecord event) {
+    @Override
+    public void remove(final ProvenanceEventRecord event) {
         events.remove(event);
     }
 
-    void clear() {
+    @Override
+    public void clear() {
         events.clear();
+
+        flowFilesSent = 0;
+        bytesSent = 0;
+
+        flowFilesReceived = 0;
+        bytesReceived = 0;
+
+        flowFilesFetched = 0;
+        bytesFetched = 0;
     }
 
-    void migrate(final StandardProvenanceReporter newOwner, final Collection<String> flowFileIds) {
+    @Override
+    public void migrate(final InternalProvenanceReporter newOwner, final Collection<String> flowFileIds) {
         final Set<ProvenanceEventRecord> toMove = new LinkedHashSet<>();
         for (final ProvenanceEventRecord event : events) {
             if (flowFileIds.contains(event.getFlowFileUuid())) {
@@ -82,8 +96,13 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
             }
         }
 
+        newOwner.receiveMigration(events);
         events.removeAll(toMove);
-        newOwner.events.addAll(toMove);
+    }
+
+    @Override
+    public void receiveMigration(final Set<ProvenanceEventRecord> events) {
+        this.events.addAll(events);
     }
 
     /**
@@ -94,7 +113,8 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
      * @param child child
      * @return record
      */
-    ProvenanceEventRecord generateJoinEvent(final Collection<FlowFile> parents, final FlowFile child) {
+    @Override
+    public ProvenanceEventRecord generateJoinEvent(final Collection<FlowFile> parents, final FlowFile child) {
         final ProvenanceEventBuilder eventBuilder = build(child, ProvenanceEventType.JOIN);
         eventBuilder.addChildFlowFile(child);
 
@@ -105,13 +125,14 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
         return eventBuilder.build();
     }
 
-    ProvenanceEventRecord generateDropEvent(final FlowFile flowFile, final String details) {
+    @Override
+    public ProvenanceEventRecord generateDropEvent(final FlowFile flowFile, final String details) {
         return build(flowFile, ProvenanceEventType.DROP).setDetails(details).build();
     }
 
     private void verifyFlowFileKnown(final FlowFile flowFile) {
-        if (session != null && !session.isFlowFileKnown(flowFile)) {
-            throw new FlowFileHandlingException(flowFile + " is not known to " + session);
+        if (flowfileKnownCheck != null && !flowfileKnownCheck.test(flowFile)) {
+            throw new FlowFileHandlingException(flowFile + " is not known");
         }
     }
 
@@ -292,7 +313,8 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
         }
     }
 
-    ProvenanceEventRecord drop(final FlowFile flowFile, final String reason) {
+    @Override
+    public ProvenanceEventRecord drop(final FlowFile flowFile, final String reason) {
         try {
             final ProvenanceEventBuilder builder = build(flowFile, ProvenanceEventType.DROP);
             if (reason != null) {
@@ -310,7 +332,8 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
         }
     }
 
-    void expire(final FlowFile flowFile, final String details) {
+    @Override
+    public void expire(final FlowFile flowFile, final String details) {
         try {
             final ProvenanceEventRecord record = build(flowFile, ProvenanceEventType.EXPIRE).setDetails(details).build();
             events.add(record);
@@ -407,7 +430,8 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
         clone(parent, child, true);
     }
 
-    void clone(final FlowFile parent, final FlowFile child, final boolean verifyFlowFile) {
+    @Override
+    public void clone(final FlowFile parent, final FlowFile child, final boolean verifyFlowFile) {
         if (verifyFlowFile) {
             verifyFlowFileKnown(child);
         }
@@ -525,7 +549,8 @@ public class StandardProvenanceReporter implements ProvenanceReporter {
         }
     }
 
-    ProvenanceEventBuilder build(final FlowFile flowFile, final ProvenanceEventType eventType) {
+    @Override
+    public ProvenanceEventBuilder build(final FlowFile flowFile, final ProvenanceEventType eventType) {
         final ProvenanceEventBuilder builder = repository.eventBuilder();
         builder.setEventType(eventType);
         builder.fromFlowFile(flowFile);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryStatusReport.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryStatusReport.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryStatusReport.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryStatusReport.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/TransientClaimRepositoryRecord.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/TransientClaimRepositoryRecord.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/TransientClaimRepositoryRecord.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/TransientClaimRepositoryRecord.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java
similarity index 66%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java
index 614995b..ada8c4c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java
@@ -15,25 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.nifi.controller.tasks;
+package org.apache.nifi.controller.repository.claim;
 
-public class ActiveTask {
-    private final long startTime;
-    private volatile boolean terminated;
+import java.io.IOException;
+import java.io.OutputStream;
 
-    public ActiveTask(final long startTime) {
-        this.startTime = startTime;
-    }
+public interface ContentClaimWriteCache {
+    void reset() throws IOException;
 
-    public long getStartTime() {
-        return startTime;
-    }
+    ContentClaim getContentClaim() throws IOException;
 
-    public boolean isTerminated() {
-        return terminated;
-    }
+    OutputStream write(ContentClaim claim) throws IOException;
 
-    public void terminate() {
-        this.terminated = true;
-    }
+    void flush(ContentClaim contentClaim) throws IOException;
+
+    void flush(ResourceClaim claim) throws IOException;
+
+    void flush() throws IOException;
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/ContentClaimInputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseInputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseInputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseInputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseInputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseOutputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseOutputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseOutputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/DisableOnCloseOutputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessInputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessInputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessInputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessInputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessOutputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessOutputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessOutputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/FlowFileAccessOutputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/LimitedInputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/LimitedInputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/LimitedInputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/LimitedInputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationInputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationInputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationInputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationInputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationOutputStream.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationOutputStream.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationOutputStream.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/io/TaskTerminationOutputStream.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EmptyFlowFileEvent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EmptyFlowFileEvent.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EmptyFlowFileEvent.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EmptyFlowFileEvent.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventContainer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventContainer.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventContainer.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventContainer.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventSum.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventSum.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventSum.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventSum.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventSumValue.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventSumValue.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/EventSumValue.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/EventSumValue.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/RingBufferEventRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/RingBufferEventRepository.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/RingBufferEventRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/RingBufferEventRepository.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/SecondPrecisionEventContainer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/SecondPrecisionEventContainer.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/SecondPrecisionEventContainer.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/SecondPrecisionEventContainer.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/StandardFlowFileEvent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/StandardFlowFileEvent.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/metrics/StandardFlowFileEvent.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/metrics/StandardFlowFileEvent.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/ConnectableProcessContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/scheduling/ConnectableProcessContext.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/ConnectableProcessContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/scheduling/ConnectableProcessContext.java
index 8d9a9ff..902af8c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/ConnectableProcessContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/scheduling/ConnectableProcessContext.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.controller.scheduling;
+package org.apache.nifi.controller.repository.scheduling;
 
 import java.util.Collection;
 import java.util.Collections;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ControllerServiceDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ControllerServiceDetails.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ControllerServiceDetails.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ControllerServiceDetails.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ControllerServiceNotValidException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ControllerServiceNotValidException.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ControllerServiceNotValidException.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ControllerServiceNotValidException.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/ServiceStateTransition.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInitializationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInitializationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInitializationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
similarity index 96%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
index 0a8837f..4cdd49c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
@@ -19,12 +19,11 @@ package org.apache.nifi.controller.service;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.controller.ComponentNode;
 import org.apache.nifi.controller.ControllerService;
-import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.ReportingTaskNode;
 import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.controller.scheduling.StandardProcessScheduler;
 import org.apache.nifi.events.BulletinFactory;
 import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.logging.LogRepositoryFactory;
@@ -57,18 +56,18 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
 
     private static final Logger logger = LoggerFactory.getLogger(StandardControllerServiceProvider.class);
 
-    private final StandardProcessScheduler processScheduler;
+    private final ProcessScheduler processScheduler;
     private final BulletinRepository bulletinRepo;
-    private final FlowController flowController;
     private final FlowManager flowManager;
+    private final ExtensionManager extensionManager;
 
     private final ConcurrentMap<String, ControllerServiceNode> serviceCache = new ConcurrentHashMap<>();
 
-    public StandardControllerServiceProvider(final FlowController flowController, final StandardProcessScheduler scheduler, final BulletinRepository bulletinRepo) {
-        this.flowController = flowController;
+    public StandardControllerServiceProvider(final ProcessScheduler scheduler, final BulletinRepository bulletinRepo, final FlowManager flowManager, final ExtensionManager extensionManager) {
         this.processScheduler = scheduler;
         this.bulletinRepo = bulletinRepo;
-        this.flowManager = flowController.getFlowManager();
+        this.flowManager = flowManager;
+        this.extensionManager = extensionManager;
     }
 
     @Override
@@ -201,7 +200,7 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
             for (ControllerServiceNode controllerServiceNode : serviceNodes) {
                 try {
                     if (!controllerServiceNode.isActive()) {
-                        final Future<Void> future = enableControllerServiceDependenciesFirst(controllerServiceNode);
+                        final Future<Void> future = enableControllerServiceAndDependencies(controllerServiceNode);
 
                         future.get(30, TimeUnit.SECONDS);
                         logger.debug("Successfully enabled {}; service state = {}", controllerServiceNode, controllerServiceNode.getState());
@@ -251,7 +250,7 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
 
             try {
                 if (!controllerServiceNode.isActive()) {
-                    final Future<Void> future = enableControllerServiceDependenciesFirst(controllerServiceNode);
+                    final Future<Void> future = enableControllerServiceAndDependencies(controllerServiceNode);
 
                     while (true) {
                         try {
@@ -285,13 +284,20 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
         }
     }
 
-    private Future<Void> enableControllerServiceDependenciesFirst(ControllerServiceNode serviceNode) {
+    @Override
+    public Future<Void> enableControllerServiceAndDependencies(final ControllerServiceNode serviceNode) {
+        final ControllerServiceState currentState = serviceNode.getState();
+        if (currentState == ControllerServiceState.ENABLED) {
+            logger.debug("Enabling of Controller Service {} triggered but service already enabled", serviceNode);
+            return CompletableFuture.completedFuture(null);
+        }
+
         final Map<ControllerServiceNode, Future<Void>> futures = new HashMap<>();
 
         for (ControllerServiceNode depNode : serviceNode.getRequiredControllerServices()) {
             if (!depNode.isActive()) {
                 logger.debug("Before enabling {}, will enable dependent Controller Service {}", serviceNode, depNode);
-                futures.put(depNode, this.enableControllerServiceDependenciesFirst(depNode));
+                futures.put(depNode, this.enableControllerServiceAndDependencies(depNode));
             }
         }
 
@@ -430,7 +436,7 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
         if (procNode == null) {
             final ControllerServiceNode serviceNode = getControllerServiceNode(componentId);
             if (serviceNode == null) {
-                final ReportingTaskNode taskNode = flowController.getReportingTaskNode(componentId);
+                final ReportingTaskNode taskNode = flowManager.getReportingTaskNode(componentId);
                 if (taskNode == null) {
                     throw new IllegalStateException("Could not find any Processor, Reporting Task, or Controller Service with identifier " + componentId);
                 }
@@ -532,7 +538,6 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
 
         group.removeControllerService(serviceNode);
         LogRepositoryFactory.removeRepository(serviceNode.getIdentifier());
-        final ExtensionManager extensionManager = flowController.getExtensionManager();
         extensionManager.removeInstanceClassLoader(serviceNode.getIdentifier());
         serviceCache.remove(serviceNode.getIdentifier());
     }
@@ -634,6 +639,6 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
 
     @Override
     public ExtensionManager getExtensionManager() {
-        return flowController.getExtensionManager();
+        return extensionManager;
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceReference.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceReference.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceReference.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceReference.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/ConfigParseException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/ConfigParseException.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/ConfigParseException.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/ConfigParseException.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateManager.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateManager.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateManager.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateMap.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateMap.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateMap.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateMap.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateProviderInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateProviderInitializationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/StandardStateProviderInitializationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/StandardStateProviderInitializationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/config/StateManagerConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/config/StateManagerConfiguration.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/config/StateManagerConfiguration.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/config/StateManagerConfiguration.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/config/StateProviderConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/config/StateProviderConfiguration.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/config/StateProviderConfiguration.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/config/StateProviderConfiguration.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
index 20e1628..8fa89b6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
@@ -75,8 +75,7 @@ public class StandardStateManagerProvider implements StateManagerProvider {
     private final StateProvider localStateProvider;
     private final StateProvider clusterStateProvider;
 
-
-    private StandardStateManagerProvider(final StateProvider localStateProvider, final StateProvider clusterStateProvider) {
+    public StandardStateManagerProvider(final StateProvider localStateProvider, final StateProvider clusterStateProvider) {
         this.localStateProvider = localStateProvider;
         this.clusterStateProvider = clusterStateProvider;
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/tasks/ActiveTask.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/EncryptionException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/encrypt/EncryptionException.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/EncryptionException.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/encrypt/EncryptionException.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/encrypt/StringEncryptor.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/NoOpBatchCounts.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/NoOpBatchCounts.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/NoOpBatchCounts.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/NoOpBatchCounts.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/SingleBatchFlowFileGate.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/SingleBatchFlowFileGate.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/SingleBatchFlowFileGate.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/SingleBatchFlowFileGate.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/SingleConcurrencyFlowFileGate.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/SingleConcurrencyFlowFileGate.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/SingleConcurrencyFlowFileGate.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/SingleConcurrencyFlowFileGate.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardBatchCounts.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardBatchCounts.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardBatchCounts.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardBatchCounts.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardDataValve.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardDataValve.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardDataValve.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardDataValve.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
similarity index 91%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index ab94234..2c0d993 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -45,9 +45,11 @@ import org.apache.nifi.connectable.Size;
 import org.apache.nifi.controller.ComponentNode;
 import org.apache.nifi.controller.ConfigurationContext;
 import org.apache.nifi.controller.ControllerService;
-import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.NodeTypeProvider;
+import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.PropertyConfiguration;
+import org.apache.nifi.controller.ReloadComponent;
 import org.apache.nifi.controller.ReportingTaskNode;
 import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.Snippet;
@@ -63,7 +65,6 @@ import org.apache.nifi.controller.queue.FlowFileQueue;
 import org.apache.nifi.controller.queue.LoadBalanceCompression;
 import org.apache.nifi.controller.queue.LoadBalanceStrategy;
 import org.apache.nifi.controller.queue.QueueSize;
-import org.apache.nifi.controller.scheduling.StandardProcessScheduler;
 import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.service.ControllerServiceReference;
@@ -74,6 +75,7 @@ import org.apache.nifi.flowfile.FlowFilePrioritizer;
 import org.apache.nifi.logging.LogLevel;
 import org.apache.nifi.logging.LogRepository;
 import org.apache.nifi.logging.LogRepositoryFactory;
+import org.apache.nifi.nar.ExtensionManager;
 import org.apache.nifi.nar.NarCloseable;
 import org.apache.nifi.parameter.Parameter;
 import org.apache.nifi.parameter.ParameterContext;
@@ -131,7 +133,6 @@ import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
 import org.apache.nifi.scheduling.ExecutionNode;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.util.FlowDifferenceFilters;
-import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.ReflectionUtils;
 import org.apache.nifi.util.SnippetUtils;
 import org.apache.nifi.web.ResourceNotFoundException;
@@ -159,6 +160,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
@@ -189,10 +191,13 @@ public final class StandardProcessGroup implements ProcessGroup {
     private final AtomicReference<StandardVersionControlInformation> versionControlInfo = new AtomicReference<>();
     private static final SecureRandom randomGenerator = new SecureRandom();
 
-    private final StandardProcessScheduler scheduler;
+    private final ProcessScheduler scheduler;
     private final ControllerServiceProvider controllerServiceProvider;
-    private final FlowController flowController;
     private final FlowManager flowManager;
+    private final ExtensionManager extensionManager;
+    private final StateManagerProvider stateManagerProvider;
+    private final FlowRegistryClient flowRegistryClient;
+    private final ReloadComponent reloadComponent;
 
     private final Map<String, Port> inputPorts = new HashMap<>();
     private final Map<String, Port> outputPorts = new HashMap<>();
@@ -208,6 +213,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     private final MutableVariableRegistry variableRegistry;
     private final VersionControlFields versionControlFields = new VersionControlFields();
     private volatile ParameterContext parameterContext;
+    private final NodeTypeProvider nodeTypeProvider;
 
     private FlowFileConcurrency flowFileConcurrency = FlowFileConcurrency.UNBOUNDED;
     private volatile FlowFileGate flowFileGate = new UnboundedFlowFileGate();
@@ -221,23 +227,28 @@ public final class StandardProcessGroup implements ProcessGroup {
 
     private static final Logger LOG = LoggerFactory.getLogger(StandardProcessGroup.class);
 
-    public StandardProcessGroup(final String id, final ControllerServiceProvider serviceProvider, final StandardProcessScheduler scheduler,
-                                final NiFiProperties nifiProps, final StringEncryptor encryptor, final FlowController flowController,
-                                final MutableVariableRegistry variableRegistry) {
+    public StandardProcessGroup(final String id, final ControllerServiceProvider serviceProvider, final ProcessScheduler scheduler,
+                                final StringEncryptor encryptor, final ExtensionManager extensionManager,
+                                final StateManagerProvider stateManagerProvider, final FlowManager flowManager, final FlowRegistryClient flowRegistryClient,
+                                final ReloadComponent reloadComponent, final MutableVariableRegistry variableRegistry, final NodeTypeProvider nodeTypeProvider) {
         this.id = id;
         this.controllerServiceProvider = serviceProvider;
         this.parent = new AtomicReference<>();
         this.scheduler = scheduler;
         this.comments = new AtomicReference<>("");
         this.encryptor = encryptor;
-        this.flowController = flowController;
+        this.extensionManager = extensionManager;
+        this.stateManagerProvider = stateManagerProvider;
         this.variableRegistry = variableRegistry;
-        this.flowManager = flowController.getFlowManager();
+        this.flowManager = flowManager;
+        this.flowRegistryClient = flowRegistryClient;
+        this.reloadComponent = reloadComponent;
+        this.nodeTypeProvider = nodeTypeProvider;
 
         name = new AtomicReference<>();
         position = new AtomicReference<>(new Position(0D, 0D));
 
-        final StateManager dataValveStateManager = flowController.getStateManagerProvider().getStateManager(id + "-DataValve");
+        final StateManager dataValveStateManager = stateManagerProvider.getStateManager(id + "-DataValve");
         dataValve = new StandardDataValve(this, dataValveStateManager);
     }
 
@@ -465,8 +476,8 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
 
         return new ProcessGroupCounts(localInputPortCount, localOutputPortCount, publicInputPortCount, publicOutputPortCount,
-                running, stopped, invalid, disabled, activeRemotePorts,
-                inactiveRemotePorts, upToDate, locallyModified, stale, locallyModifiedAndStale, syncFailure);
+            running, stopped, invalid, disabled, activeRemotePorts,
+            inactiveRemotePorts, upToDate, locallyModified, stale, locallyModifiedAndStale, syncFailure);
     }
 
     @Override
@@ -521,14 +532,14 @@ public final class StandardProcessGroup implements ProcessGroup {
     }
 
     private StateManager getStateManager(final String componentId) {
-        return flowController.getStateManagerProvider().getStateManager(componentId);
+        return stateManagerProvider.getStateManager(componentId);
     }
 
     private void shutdown(final ProcessGroup procGroup) {
         for (final ProcessorNode node : procGroup.getProcessors()) {
-            try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(), node.getProcessor().getClass(), node.getIdentifier())) {
-                final StandardProcessContext processContext = new StandardProcessContext(
-                        node, controllerServiceProvider, encryptor, getStateManager(node.getIdentifier()), () -> false, flowController);
+            try (final NarCloseable x = NarCloseable.withComponentNarLoader(extensionManager, node.getProcessor().getClass(), node.getIdentifier())) {
+                final StandardProcessContext processContext = new StandardProcessContext(node, controllerServiceProvider, encryptor,
+                    getStateManager(node.getIdentifier()), () -> false, nodeTypeProvider);
                 ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnShutdown.class, node.getProcessor(), processContext);
             }
         }
@@ -831,7 +842,7 @@ public final class StandardProcessGroup implements ProcessGroup {
 
         for (final ControllerServiceNode cs : group.getControllerServices(false)) {
             // Must go through Controller Service here because we need to ensure that it is removed from the cache
-            flowController.getControllerServiceProvider().removeControllerService(cs);
+            controllerServiceProvider.removeControllerService(cs);
         }
 
         for (final ProcessGroup childGroup : new ArrayList<>(group.getProcessGroups())) {
@@ -904,7 +915,6 @@ public final class StandardProcessGroup implements ProcessGroup {
             remoteGroup.getInputPorts().forEach(scheduler::onPortRemoved);
             remoteGroup.getOutputPorts().forEach(scheduler::onPortRemoved);
 
-            final StateManagerProvider stateManagerProvider = flowController.getStateManagerProvider();
             scheduler.submitFrameworkTask(new Runnable() {
                 @Override
                 public void run() {
@@ -993,9 +1003,9 @@ public final class StandardProcessGroup implements ProcessGroup {
                 conn.verifyCanDelete();
             }
 
-            try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(), processor.getProcessor().getClass(), processor.getIdentifier())) {
-                final StandardProcessContext processContext = new StandardProcessContext(
-                        processor, controllerServiceProvider, encryptor, getStateManager(processor.getIdentifier()), () -> false, flowController);
+            try (final NarCloseable x = NarCloseable.withComponentNarLoader(extensionManager, processor.getProcessor().getClass(), processor.getIdentifier())) {
+                final StandardProcessContext processContext = new StandardProcessContext(processor, controllerServiceProvider, encryptor,
+                    getStateManager(processor.getIdentifier()), () -> false, nodeTypeProvider);
                 ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnRemoved.class, processor.getProcessor(), processContext);
             } catch (final Exception e) {
                 throw new ComponentLifeCycleException("Failed to invoke 'OnRemoved' methods of processor with id " + processor.getIdentifier(), e);
@@ -1025,7 +1035,6 @@ public final class StandardProcessGroup implements ProcessGroup {
                 logRepository.removeAllObservers();
             }
 
-            final StateManagerProvider stateManagerProvider = flowController.getStateManagerProvider();
             scheduler.submitFrameworkTask(new Runnable() {
                 @Override
                 public void run() {
@@ -1045,7 +1054,7 @@ public final class StandardProcessGroup implements ProcessGroup {
             if (removed) {
                 try {
                     LogRepositoryFactory.removeRepository(processor.getIdentifier());
-                    flowController.getExtensionManager().removeInstanceClassLoader(id);
+                    extensionManager.removeInstanceClassLoader(id);
                 } catch (Throwable t) {
                 }
             }
@@ -1149,11 +1158,11 @@ public final class StandardProcessGroup implements ProcessGroup {
                 } else if (isInputPort(destination)) {
                     if (!processGroups.containsKey(destinationGroup.getIdentifier())) {
                         throw new IllegalStateException("Cannot add Connection to Process Group because its destination is an Input "
-                                + "Port but the Input Port does not belong to a child Process Group");
+                            + "Port but the Input Port does not belong to a child Process Group");
                     }
                 } else if (destinationGroup != this) {
                     throw new IllegalStateException("Cannot add Connection between " + source.getIdentifier() + " and " + destination.getIdentifier()
-                            + " because they are in different Process Groups and neither is an Input Port or Output Port");
+                        + " because they are in different Process Groups and neither is an Input Port or Output Port");
                 }
             }
 
@@ -1270,7 +1279,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return null;
     }
 
-
     @Override
     public List<Connection> findAllConnections() {
         return findAllConnections(this);
@@ -1423,7 +1431,7 @@ public final class StandardProcessGroup implements ProcessGroup {
         readLock.lock();
         try {
             return inputPorts.isEmpty() && outputPorts.isEmpty() && connections.isEmpty()
-                    && processGroups.isEmpty() && labels.isEmpty() && processors.isEmpty() && remoteGroups.isEmpty();
+                && processGroups.isEmpty() && labels.isEmpty() && processors.isEmpty() && remoteGroups.isEmpty();
         } finally {
             readLock.unlock();
         }
@@ -1440,7 +1448,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     }
 
     @Override
-    public CompletableFuture<Void> startProcessor(final ProcessorNode processor, final boolean failIfStopping) {
+    public Future<Void> startProcessor(final ProcessorNode processor, final boolean failIfStopping) {
         readLock.lock();
         try {
             if (getProcessor(processor.getIdentifier()) == null) {
@@ -1522,7 +1530,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     }
 
     @Override
-    public CompletableFuture<Void> stopProcessor(final ProcessorNode processor) {
+    public Future<Void> stopProcessor(final ProcessorNode processor) {
         readLock.lock();
         try {
             if (!processors.containsKey(processor.getIdentifier())) {
@@ -1560,7 +1568,6 @@ public final class StandardProcessGroup implements ProcessGroup {
 
     }
 
-
     @Override
     public void stopInputPort(final Port port) {
         readLock.lock();
@@ -1692,7 +1699,7 @@ public final class StandardProcessGroup implements ProcessGroup {
         controllerServiceProvider.enableControllerServices(controllerServices.values());
 
         // Enable all controller services for child process groups
-        for(ProcessGroup pg : processGroups.values()) {
+        for (ProcessGroup pg : processGroups.values()) {
             pg.enableAllControllerServices();
         }
     }
@@ -1902,7 +1909,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return allNodes;
     }
 
-
     @Override
     public RemoteGroupPort findRemoteGroupPort(final String identifier) {
         readLock.lock();
@@ -2139,7 +2145,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return null;
     }
 
-
     @Override
     public ControllerServiceNode findControllerService(final String id, final boolean includeDescendants, final boolean includeAncestors) {
         ControllerServiceNode serviceNode;
@@ -2306,7 +2311,7 @@ public final class StandardProcessGroup implements ProcessGroup {
 
             service.verifyCanDelete();
 
-            try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(), service.getControllerServiceImplementation().getClass(), service.getIdentifier())) {
+            try (final NarCloseable x = NarCloseable.withComponentNarLoader(extensionManager, service.getControllerServiceImplementation().getClass(), service.getIdentifier())) {
                 final ConfigurationContext configurationContext = new StandardConfigurationContext(service, controllerServiceProvider, null, variableRegistry);
                 ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnRemoved.class, service.getControllerServiceImplementation(), configurationContext);
             }
@@ -2331,23 +2336,23 @@ public final class StandardProcessGroup implements ProcessGroup {
             // and notify the Process Group that a component has been modified. This way, we know to re-calculate
             // whether or not the Process Group has local modifications.
             service.getReferences().getReferencingComponents().stream()
-                    .map(ComponentNode::getProcessGroupIdentifier)
-                    .filter(id -> !id.equals(getIdentifier()))
-                    .forEach(groupId -> {
-                        final ProcessGroup descendant = findProcessGroup(groupId);
-                        if (descendant != null) {
-                            descendant.onComponentModified();
-                        }
-                    });
+                .map(ComponentNode::getProcessGroupIdentifier)
+                .filter(id -> !id.equals(getIdentifier()))
+                .forEach(groupId -> {
+                    final ProcessGroup descendant = findProcessGroup(groupId);
+                    if (descendant != null) {
+                        descendant.onComponentModified();
+                    }
+                });
 
-            flowController.getStateManagerProvider().onComponentRemoved(service.getIdentifier());
+            stateManagerProvider.onComponentRemoved(service.getIdentifier());
 
             removed = true;
             LOG.info("{} removed from {}", service, this);
         } finally {
             if (removed) {
                 try {
-                    flowController.getExtensionManager().removeInstanceClassLoader(service.getIdentifier());
+                    extensionManager.removeInstanceClassLoader(service.getIdentifier());
                 } catch (Throwable t) {
                 }
             }
@@ -2465,7 +2470,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 for (final Connection conn : connectable.getConnections()) {
                     if (!connections.containsKey(conn.getIdentifier())) {
                         throw new IllegalStateException("Connectable component " + connectable.getIdentifier()
-                                + " cannot be removed because it has incoming connections from the parent Process Group");
+                            + " cannot be removed because it has incoming connections from the parent Process Group");
                     }
                     connectionIdsToRemove.add(conn.getIdentifier());
                 }
@@ -2494,7 +2499,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 for (final Connection conn : connectable.getIncomingConnections()) {
                     if (!connectionIds.contains(conn.getIdentifier()) && !connectables.contains(conn.getSource())) {
                         throw new IllegalStateException("Connectable component " + connectable.getIdentifier() + " cannot be removed because it has incoming connections "
-                                + "that are not selected to be deleted");
+                            + "that are not selected to be deleted");
                     }
                 }
             }
@@ -2683,9 +2688,10 @@ public final class StandardProcessGroup implements ProcessGroup {
      * {@link IllegalStateException}.
      *
      * @param snippet the snippet
-     * @throws NullPointerException  if the argument is null
+     *
+     * @throws NullPointerException if the argument is null
      * @throws IllegalStateException if the snippet contains an ID that
-     *                               references a component that is not part of this ProcessGroup
+     * references a component that is not part of this ProcessGroup
      */
     private void verifyContents(final Snippet snippet) throws NullPointerException, IllegalStateException {
         requireNonNull(snippet);
@@ -2703,8 +2709,9 @@ public final class StandardProcessGroup implements ProcessGroup {
     /**
      * Verifies that a move request cannot attempt to move a process group into itself.
      *
-     * @param snippet     the snippet
+     * @param snippet the snippet
      * @param destination the destination
+     *
      * @throws IllegalStateException if the snippet contains an ID that is equal to the identifier of the destination
      */
     private void verifyDestinationNotInSnippet(final Snippet snippet, final ProcessGroup destination) throws IllegalStateException {
@@ -2729,8 +2736,8 @@ public final class StandardProcessGroup implements ProcessGroup {
      * If the ids given are null, will do no validation.
      * </p>
      *
-     * @param ids           ids
-     * @param map           map
+     * @param ids ids
+     * @param map map
      * @param componentType type
      */
     private void verifyAllKeysExist(final Set<String> ids, final Map<String, ?> map, final String componentType) {
@@ -2811,7 +2818,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                             connection.verifyCanDelete();
                         } else {
                             throw new IllegalStateException("Cannot delete Process Group because Input Port " + port.getIdentifier()
-                                    + " has at least one incoming connection from a component outside of the Process Group. Delete this connection first.");
+                                + " has at least one incoming connection from a component outside of the Process Group. Delete this connection first.");
                         }
                     }
                 }
@@ -2822,7 +2829,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                             connection.verifyCanDelete();
                         } else {
                             throw new IllegalStateException("Cannot delete Process Group because Output Port " + port.getIdentifier()
-                                    + " has at least one outgoing connection to a component outside of the Process Group. Delete this connection first.");
+                                + " has at least one outgoing connection to a component outside of the Process Group. Delete this connection first.");
                         }
                     }
                 }
@@ -3012,7 +3019,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                             // ensure the configured service is an allowed service if it's still a valid service
                             if (currentControllerServiceIds.contains(serviceId) && !proposedControllerServiceIds.contains(serviceId)) {
                                 throw new IllegalStateException("Cannot perform Move Operation because Processor with ID " + processorNode.getIdentifier()
-                                        + " references a service that is not available in the destination Process Group");
+                                    + " references a service that is not available in the destination Process Group");
                             }
                         }
                     }
@@ -3035,8 +3042,8 @@ public final class StandardProcessGroup implements ProcessGroup {
         final Set<ProcessorNode> processors = new HashSet<>();
 
         snippet.getProcessors().keySet().stream()
-                .map(this::getProcessor)
-                .forEach(processors::add);
+            .map(this::getProcessor)
+            .forEach(processors::add);
 
         for (final String groupId : snippet.getProcessGroups().keySet()) {
             processors.addAll(getProcessGroup(groupId).findAllProcessors());
@@ -3137,7 +3144,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return updatedParameters;
     }
 
-
     @Override
     public void verifyCanSetParameterContext(final ParameterContext parameterContext) {
         readLock.lock();
@@ -3362,7 +3368,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return affected;
     }
 
-
     private Set<String> getUpdatedVariables(final Map<String, String> newVariableValues) {
         final Set<String> updatedVariableNames = new HashSet<>();
 
@@ -3382,12 +3387,12 @@ public final class StandardProcessGroup implements ProcessGroup {
 
     private List<VariableImpact> getVariableImpact(final ComponentNode component) {
         return component.getEffectivePropertyValues().keySet().stream()
-                .map(descriptor -> {
-                    final String configuredVal = component.getEffectivePropertyValue(descriptor);
-                    return configuredVal == null ? descriptor.getDefaultValue() : configuredVal;
-                })
-                .map(propVal -> Query.prepare(propVal).getVariableImpact())
-                .collect(Collectors.toList());
+            .map(descriptor -> {
+                final String configuredVal = component.getEffectivePropertyValue(descriptor);
+                return configuredVal == null ? descriptor.getDefaultValue() : configuredVal;
+            })
+            .map(propVal -> Query.prepare(propVal).getVariableImpact())
+            .collect(Collectors.toList());
     }
 
     @Override
@@ -3434,22 +3439,21 @@ public final class StandardProcessGroup implements ProcessGroup {
         versionControlFields.setFlowDifferences(null);
     }
 
-
     @Override
     public void setVersionControlInformation(final VersionControlInformation versionControlInformation, final Map<String, String> versionedComponentIds) {
         final StandardVersionControlInformation svci = new StandardVersionControlInformation(
-                versionControlInformation.getRegistryIdentifier(),
-                versionControlInformation.getRegistryName(),
-                versionControlInformation.getBucketIdentifier(),
-                versionControlInformation.getFlowIdentifier(),
-                versionControlInformation.getVersion(),
-                stripContentsFromRemoteDescendantGroups(versionControlInformation.getFlowSnapshot(), true),
-                versionControlInformation.getStatus()) {
+            versionControlInformation.getRegistryIdentifier(),
+            versionControlInformation.getRegistryName(),
+            versionControlInformation.getBucketIdentifier(),
+            versionControlInformation.getFlowIdentifier(),
+            versionControlInformation.getVersion(),
+            stripContentsFromRemoteDescendantGroups(versionControlInformation.getFlowSnapshot(), true),
+            versionControlInformation.getStatus()) {
 
             @Override
             public String getRegistryName() {
                 final String registryId = versionControlInformation.getRegistryIdentifier();
-                final FlowRegistry registry = flowController.getFlowRegistryClient().getFlowRegistry(registryId);
+                final FlowRegistry registry = flowRegistryClient.getFlowRegistry(registryId);
                 return registry == null ? registryId : registry.getName();
             }
 
@@ -3529,7 +3533,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 parent.onComponentModified();
             }
 
-            scheduler.submitFrameworkTask(() -> synchronizeWithFlowRegistry(flowController.getFlowRegistryClient()));
+            scheduler.submitFrameworkTask(() -> synchronizeWithFlowRegistry(flowRegistryClient));
         } finally {
             writeLock.unlock();
         }
@@ -3619,32 +3623,31 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-
     private void applyVersionedComponentIds(final ProcessGroup processGroup, final Function<String, String> lookup) {
         processGroup.setVersionedComponentId(lookup.apply(processGroup.getIdentifier()));
 
         processGroup.getConnections()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getProcessors()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getInputPorts()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getOutputPorts()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getLabels()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getFunnels()
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
         processGroup.getControllerServices(false)
-                .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
+            .forEach(component -> component.setVersionedComponentId(lookup.apply(component.getIdentifier())));
 
         processGroup.getRemoteProcessGroups()
-                .forEach(rpg -> {
-                    rpg.setVersionedComponentId(lookup.apply(rpg.getIdentifier()));
+            .forEach(rpg -> {
+                rpg.setVersionedComponentId(lookup.apply(rpg.getIdentifier()));
 
-                    rpg.getInputPorts().forEach(port -> port.setVersionedComponentId(lookup.apply(port.getIdentifier())));
-                    rpg.getOutputPorts().forEach(port -> port.setVersionedComponentId(lookup.apply(port.getIdentifier())));
-                });
+                rpg.getInputPorts().forEach(port -> port.setVersionedComponentId(lookup.apply(port.getIdentifier())));
+                rpg.getOutputPorts().forEach(port -> port.setVersionedComponentId(lookup.apply(port.getIdentifier())));
+            });
 
         for (final ProcessGroup childGroup : processGroup.getProcessGroups()) {
             if (childGroup.getVersionControlInformation() == null) {
@@ -3655,7 +3658,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-
     @Override
     public void synchronizeWithFlowRegistry(final FlowRegistryClient flowRegistryClient) {
         final StandardVersionControlInformation vci = versionControlInfo.get();
@@ -3667,11 +3669,11 @@ public final class StandardProcessGroup implements ProcessGroup {
         final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(registryId);
         if (flowRegistry == null) {
             final String message = String.format("Unable to synchronize Process Group with Flow Registry because Process Group was placed under Version Control using Flow Registry "
-                    + "with identifier %s but cannot find any Flow Registry with this identifier", registryId);
+                + "with identifier %s but cannot find any Flow Registry with this identifier", registryId);
             versionControlFields.setSyncFailureExplanation(message);
 
             LOG.error("Unable to synchronize {} with Flow Registry because Process Group was placed under Version Control using Flow Registry "
-                    + "with identifier {} but cannot find any Flow Registry with this identifier", this, registryId);
+                + "with identifier {} but cannot find any Flow Registry with this identifier", this, registryId);
             return;
         }
 
@@ -3685,7 +3687,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 vci.setFlowSnapshot(registryFlow);
             } catch (final IOException | NiFiRegistryException e) {
                 final String message = String.format("Failed to synchronize Process Group with Flow Registry because could not retrieve version %s of flow with identifier %s in bucket %s",
-                        vci.getVersion(), vci.getFlowIdentifier(), vci.getBucketIdentifier());
+                    vci.getVersion(), vci.getFlowIdentifier(), vci.getBucketIdentifier());
                 versionControlFields.setSyncFailureExplanation(message);
 
                 final String logErrorMessage = "Failed to synchronize {} with Flow Registry because could not retrieve version {} of flow with identifier {} in bucket {}";
@@ -3718,7 +3720,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 }
             } else {
                 LOG.info("{} is not the most recent version of the flow that is under Version Control; current version is {}; most recent version is {}",
-                        this, vci.getVersion(), latestVersion);
+                    this, vci.getVersion(), latestVersion);
                 versionControlFields.setStale(true);
             }
 
@@ -3731,7 +3733,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-
     @Override
     public void updateFlow(final VersionedFlowSnapshot proposedSnapshot, final String componentIdSeed, final boolean verifyNotDirty, final boolean updateSettings,
                            final boolean updateDescendantVersionedFlows) {
@@ -3739,8 +3740,8 @@ public final class StandardProcessGroup implements ProcessGroup {
         try {
             verifyCanUpdate(proposedSnapshot, true, verifyNotDirty);
 
-            final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(flowController.getExtensionManager());
-            final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(this, controllerServiceProvider, flowController.getFlowRegistryClient(), true);
+            final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(extensionManager);
+            final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(this, controllerServiceProvider, flowRegistryClient, true);
 
             final ComparableDataFlow localFlow = new StandardComparableDataFlow("Current Flow", versionedGroup);
             final ComparableDataFlow proposedFlow = new StandardComparableDataFlow("New Flow", proposedSnapshot.getFlowContents());
@@ -3786,14 +3787,14 @@ public final class StandardProcessGroup implements ProcessGroup {
 
             if (LOG.isInfoEnabled()) {
                 final String differencesByLine = flowComparison.getDifferences().stream()
-                        .map(FlowDifference::toString)
-                        .collect(Collectors.joining("\n"));
+                    .map(FlowDifference::toString)
+                    .collect(Collectors.joining("\n"));
 
                 // TODO: Until we move to NiFi Registry 0.6.0, avoid using proposedSnapshot.toString() because it throws a NullPointerException
                 final String proposedSnapshotDetails = "VersionedFlowSnapshot[flowContentsId=" + proposedSnapshot.getFlowContents().getIdentifier()
-                        + ", flowContentsName=" + proposedSnapshot.getFlowContents().getName() + ", NoMetadataAvailable]";
+                    + ", flowContentsName=" + proposedSnapshot.getFlowContents().getName() + ", NoMetadataAvailable]";
                 LOG.info("Updating {} to {}; there are {} differences to take into account:\n{}", this, proposedSnapshotDetails,
-                        flowComparison.getDifferences().size(), differencesByLine);
+                    flowComparison.getDifferences().size(), differencesByLine);
             }
 
             final Set<String> knownVariables = getKnownVariableNames();
@@ -3834,9 +3835,9 @@ public final class StandardProcessGroup implements ProcessGroup {
             // If it does not have one, we want to generate it in the same way that our Flow Mapper does
             // because this allows us to find the Controller Service when doing a Flow Diff.
             ancestorServiceIds = parentGroup.getControllerServices(true).stream()
-                    .map(cs -> cs.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(cs.getIdentifier())))
-                    .collect(Collectors.toSet());
+                .map(cs -> cs.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(cs.getIdentifier())))
+                .collect(Collectors.toSet());
         }
 
         return ancestorServiceIds;
@@ -3849,7 +3850,7 @@ public final class StandardProcessGroup implements ProcessGroup {
 
         for (final ControllerServiceNode serviceNode : group.getControllerServices(false)) {
             final String serviceNodeVersionedComponentId = serviceNode.getVersionedComponentId().orElse(
-                    NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
+                NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
             if (serviceNodeVersionedComponentId.equals(versionedComponentId)) {
                 return serviceNode;
             }
@@ -3866,8 +3867,8 @@ public final class StandardProcessGroup implements ProcessGroup {
 
     private void populateKnownVariableNames(final ProcessGroup group, final Set<String> knownVariables) {
         group.getVariableRegistry().getVariableMap().keySet().stream()
-                .map(VariableDescriptor::getName)
-                .forEach(knownVariables::add);
+            .map(VariableDescriptor::getName)
+            .forEach(knownVariables::add);
 
         final ProcessGroup parent = group.getParent();
         if (parent != null) {
@@ -3875,7 +3876,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-
     private void updateProcessGroup(final ProcessGroup group, final VersionedProcessGroup proposed, final String componentIdSeed,
                                     final Set<String> updatedVersionedComponentIds, final boolean updatePosition, final boolean updateName, final boolean updateDescendantVersionedGroups,
                                     final Set<String> variablesToSkip, final Map<String, VersionedParameterContext> versionedParameterContexts) throws ProcessorInstantiationException {
@@ -3913,32 +3913,31 @@ public final class StandardProcessGroup implements ProcessGroup {
         if (remoteCoordinates == null) {
             group.disconnectVersionControl(false);
         } else {
-            final String registryId = flowController.getFlowRegistryClient().getFlowRegistryId(remoteCoordinates.getRegistryUrl());
+            final String registryId = flowRegistryClient.getFlowRegistryId(remoteCoordinates.getRegistryUrl());
             final String bucketId = remoteCoordinates.getBucketId();
             final String flowId = remoteCoordinates.getFlowId();
             final int version = remoteCoordinates.getVersion();
 
-            final FlowRegistry flowRegistry = flowController.getFlowRegistryClient().getFlowRegistry(registryId);
+            final FlowRegistry flowRegistry = flowRegistryClient.getFlowRegistry(registryId);
             final String registryName = flowRegistry == null ? registryId : flowRegistry.getName();
 
             final VersionedFlowState flowState = remoteCoordinates.getLatest() ? VersionedFlowState.UP_TO_DATE : VersionedFlowState.STALE;
 
             final VersionControlInformation vci = new StandardVersionControlInformation.Builder()
-                    .registryId(registryId)
-                    .registryName(registryName)
-                    .bucketId(bucketId)
-                    .bucketName(bucketId)
-                    .flowId(flowId)
-                    .flowName(flowId)
-                    .version(version)
-                    .flowSnapshot(proposed)
-                    .status(new StandardVersionedFlowStatus(flowState, flowState.getDescription()))
-                    .build();
+                .registryId(registryId)
+                .registryName(registryName)
+                .bucketId(bucketId)
+                .bucketName(bucketId)
+                .flowId(flowId)
+                .flowName(flowId)
+                .version(version)
+                .flowSnapshot(proposed)
+                .status(new StandardVersionedFlowStatus(flowState, flowState.getDescription()))
+                .build();
 
             group.setVersionControlInformation(vci, Collections.emptyMap());
         }
 
-
         // Controller Services
         // Controller Services have to be handled a bit differently than other components. This is because Processors and Controller
         // Services may reference other Controller Services. Since we may be adding Service A, which depends on Service B, before adding
@@ -3946,8 +3945,8 @@ public final class StandardProcessGroup implements ProcessGroup {
         // Controller Service. This way, we ensure that all services have been created before setting the properties. This allows us to
         // properly obtain the correct mapping of Controller Service VersionedComponentID to Controller Service instance id.
         final Map<String, ControllerServiceNode> servicesByVersionedId = group.getControllerServices(false).stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
 
         final Set<String> controllerServicesRemoved = new HashSet<>(servicesByVersionedId.keySet());
 
@@ -4012,11 +4011,10 @@ public final class StandardProcessGroup implements ProcessGroup {
             flowManager.onConnectionRemoved(connection);
         }
 
-
         // Child groups
         final Map<String, ProcessGroup> childGroupsByVersionedId = group.getProcessGroups().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> childGroupsRemoved = new HashSet<>(childGroupsByVersionedId.keySet());
 
         for (final VersionedProcessGroup proposedChildGroup : proposed.getProcessGroups()) {
@@ -4046,8 +4044,8 @@ public final class StandardProcessGroup implements ProcessGroup {
 
         // Funnels
         final Map<String, Funnel> funnelsByVersionedId = group.getFunnels().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> funnelsRemoved = new HashSet<>(funnelsByVersionedId.keySet());
 
         for (final VersionedFunnel proposedFunnel : proposed.getFunnels()) {
@@ -4066,11 +4064,10 @@ public final class StandardProcessGroup implements ProcessGroup {
             funnelsRemoved.remove(proposedFunnel.getIdentifier());
         }
 
-
         // Input Ports
         final Map<String, Port> inputPortsByVersionedId = group.getInputPorts().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> inputPortsRemoved = new HashSet<>(inputPortsByVersionedId.keySet());
 
         for (final VersionedPort proposedPort : proposed.getInputPorts()) {
@@ -4095,8 +4092,8 @@ public final class StandardProcessGroup implements ProcessGroup {
 
         // Output Ports
         final Map<String, Port> outputPortsByVersionedId = group.getOutputPorts().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> outputPortsRemoved = new HashSet<>(outputPortsByVersionedId.keySet());
 
         for (final VersionedPort proposedPort : proposed.getOutputPorts()) {
@@ -4119,11 +4116,10 @@ public final class StandardProcessGroup implements ProcessGroup {
             outputPortsRemoved.remove(proposedPort.getIdentifier());
         }
 
-
         // Labels
         final Map<String, Label> labelsByVersionedId = group.getLabels().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> labelsRemoved = new HashSet<>(labelsByVersionedId.keySet());
 
         for (final VersionedLabel proposedLabel : proposed.getLabels()) {
@@ -4141,11 +4137,10 @@ public final class StandardProcessGroup implements ProcessGroup {
             labelsRemoved.remove(proposedLabel.getIdentifier());
         }
 
-
         // Processors
         final Map<String, ProcessorNode> processorsByVersionedId = group.getProcessors().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> processorsRemoved = new HashSet<>(processorsByVersionedId.keySet());
         final Map<ProcessorNode, Set<Relationship>> autoTerminatedRelationships = new HashMap<>();
 
@@ -4181,11 +4176,10 @@ public final class StandardProcessGroup implements ProcessGroup {
             processorsRemoved.remove(proposedProcessor.getIdentifier());
         }
 
-
         // Remote Groups
         final Map<String, RemoteProcessGroup> rpgsByVersionedId = group.getRemoteProcessGroups().stream()
-                .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
+                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
         final Set<String> rpgsRemoved = new HashSet<>(rpgsByVersionedId.keySet());
 
         for (final VersionedRemoteProcessGroup proposedRpg : proposed.getRemoteProcessGroups()) {
@@ -4203,7 +4197,6 @@ public final class StandardProcessGroup implements ProcessGroup {
             rpgsRemoved.remove(proposedRpg.getIdentifier());
         }
 
-
         // Add and update Connections
         for (final VersionedConnection proposedConnection : proposed.getConnections()) {
             final Connection connection = connectionsByVersionedId.get(proposedConnection.getIdentifier());
@@ -4233,7 +4226,7 @@ public final class StandardProcessGroup implements ProcessGroup {
             final ControllerServiceNode service = servicesByVersionedId.get(removedVersionedId);
             LOG.info("Removing {} from {}", service, group);
             // Must remove Controller Service through Flow Controller in order to remove from cache
-            flowController.getControllerServiceProvider().removeControllerService(service);
+            controllerServiceProvider.removeControllerService(service);
         }
 
         for (final String removedVersionedId : funnelsRemoved) {
@@ -4297,9 +4290,7 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-    private Map<String,VersionedParameterContext> getVersionedParameterContexts(final VersionedFlowCoordinates versionedFlowCoordinates) {
-        final FlowRegistryClient flowRegistryClient = flowController.getFlowRegistryClient();
-
+    private Map<String, VersionedParameterContext> getVersionedParameterContexts(final VersionedFlowCoordinates versionedFlowCoordinates) {
         final String registryId = flowRegistryClient.getFlowRegistryId(versionedFlowCoordinates.getRegistryUrl());
         if (registryId == null) {
             throw new ResourceNotFoundException("Could not find any Flow Registry registered with url: " + versionedFlowCoordinates.getRegistryUrl());
@@ -4316,13 +4307,13 @@ public final class StandardProcessGroup implements ProcessGroup {
 
         try {
             final VersionedFlowSnapshot childSnapshot = flowRegistry.getFlowContents(bucketId, flowId, flowVersion, false);
-            return  childSnapshot.getParameterContexts();
+            return childSnapshot.getParameterContexts();
         } catch (final NiFiRegistryException e) {
             throw new IllegalArgumentException("The Flow Registry with ID " + registryId + " reports that no Flow exists with Bucket "
-                    + bucketId + ", Flow " + flowId + ", Version " + flowVersion, e);
+                + bucketId + ", Flow " + flowId + ", Version " + flowVersion, e);
         } catch (final IOException ioe) {
             throw new IllegalStateException(
-                    "Failed to communicate with Flow Registry when attempting to retrieve a versioned flow");
+                "Failed to communicate with Flow Registry when attempting to retrieve a versioned flow");
         }
     }
 
@@ -4339,7 +4330,7 @@ public final class StandardProcessGroup implements ProcessGroup {
             parameters.put(versionedParameter.getName(), parameter);
         }
 
-        return flowController.getFlowManager().createParameterContext(parameterContextId, versionedParameterContext.getName(), parameters);
+        return flowManager.createParameterContext(parameterContextId, versionedParameterContext.getName(), parameters);
     }
 
     private void addMissingParameters(final VersionedParameterContext versionedParameterContext, final ParameterContext currentParameterContext) {
@@ -4365,7 +4356,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     }
 
     private ParameterContext getParameterContextByName(final String contextName) {
-        return flowController.getFlowManager().getParameterContextManager().getParameterContexts().stream()
+        return flowManager.getParameterContextManager().getParameterContexts().stream()
             .filter(context -> context.getName().equals(contextName))
             .findAny()
             .orElse(null);
@@ -4385,7 +4376,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 if (versionedParameterContext == null) {
                     final String paramContextNames = StringUtils.join(versionedParameterContexts.keySet());
                     throw new IllegalStateException("Proposed parameter context name '" + proposedParameterContextName
-                            + "' does not exist in set of available parameter contexts [" + paramContextNames + "]");
+                        + "' does not exist in set of available parameter contexts [" + paramContextNames + "]");
                 }
 
                 final ParameterContext contextByName = getParameterContextByName(versionedParameterContext.getName());
@@ -4428,7 +4419,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         group.setVariables(updatedVariableMap);
     }
 
-
     private String getPublicPortFinalName(final PublicPort publicPort, final String proposedFinalName) {
         final Optional<Port> existingPublicPort;
         if (TransferDirection.RECEIVE == publicPort.getDirection()) {
@@ -4485,10 +4475,9 @@ public final class StandardProcessGroup implements ProcessGroup {
         return uuid.toString();
     }
 
-
     private ProcessGroup addProcessGroup(final ProcessGroup destination, final VersionedProcessGroup proposed, final String componentIdSeed, final Set<String> variablesToSkip,
                                          final Map<String, VersionedParameterContext> versionedParameterContexts)
-            throws ProcessorInstantiationException {
+        throws ProcessorInstantiationException {
         final ProcessGroup group = flowManager.createProcessGroup(generateUuid(proposed.getIdentifier(), destination.getIdentifier(), componentIdSeed));
         group.setVersionedComponentId(proposed.getIdentifier());
         group.setParent(destination);
@@ -4499,16 +4488,16 @@ public final class StandardProcessGroup implements ProcessGroup {
 
     private void updateConnection(final Connection connection, final VersionedConnection proposed) {
         connection.setBendPoints(proposed.getBends() == null ? Collections.emptyList() :
-                proposed.getBends().stream()
-                        .map(pos -> new Position(pos.getX(), pos.getY()))
-                        .collect(Collectors.toList()));
+            proposed.getBends().stream()
+                .map(pos -> new Position(pos.getX(), pos.getY()))
+                .collect(Collectors.toList()));
 
         connection.setDestination(getConnectable(connection.getProcessGroup(), proposed.getDestination()));
         connection.setLabelIndex(proposed.getLabelIndex());
         connection.setName(proposed.getName());
         connection.setRelationships(proposed.getSelectedRelationships().stream()
-                .map(name -> new Relationship.Builder().name(name).build())
-                .collect(Collectors.toSet()));
+            .map(name -> new Relationship.Builder().name(name).build())
+            .collect(Collectors.toSet()));
         connection.setZIndex(proposed.getzIndex());
 
         final FlowFileQueue queue = connection.getFlowFileQueue();
@@ -4517,14 +4506,14 @@ public final class StandardProcessGroup implements ProcessGroup {
         queue.setFlowFileExpiration(proposed.getFlowFileExpiration());
 
         final List<FlowFilePrioritizer> prioritizers = proposed.getPrioritizers() == null ? Collections.emptyList() : proposed.getPrioritizers().stream()
-                .map(prioritizerName -> {
-                    try {
-                        return flowManager.createPrioritizer(prioritizerName);
-                    } catch (final Exception e) {
-                        throw new IllegalStateException("Failed to create Prioritizer of type " + prioritizerName + " for Connection with ID " + connection.getIdentifier());
-                    }
-                })
-                .collect(Collectors.toList());
+            .map(prioritizerName -> {
+                try {
+                    return flowManager.createPrioritizer(prioritizerName);
+                } catch (final Exception e) {
+                    throw new IllegalStateException("Failed to create Prioritizer of type " + prioritizerName + " for Connection with ID " + connection.getIdentifier());
+                }
+            })
+            .collect(Collectors.toList());
 
         queue.setPriorities(prioritizers);
 
@@ -4550,17 +4539,17 @@ public final class StandardProcessGroup implements ProcessGroup {
         final Connectable source = getConnectable(destinationGroup, proposed.getSource());
         if (source == null) {
             throw new IllegalArgumentException("Connection has a source with identifier " + proposed.getIdentifier()
-                    + " but no component could be found in the Process Group with a corresponding identifier");
+                + " but no component could be found in the Process Group with a corresponding identifier");
         }
 
         final Connectable destination = getConnectable(destinationGroup, proposed.getDestination());
         if (destination == null) {
             throw new IllegalArgumentException("Connection has a destination with identifier " + proposed.getDestination().getId()
-                    + " but no component could be found in the Process Group with a corresponding identifier");
+                + " but no component could be found in the Process Group with a corresponding identifier");
         }
 
-        final Connection connection = flowController.createConnection(generateUuid(proposed.getIdentifier(), destination.getIdentifier(), componentIdSeed), proposed.getName(), source, destination,
-                proposed.getSelectedRelationships());
+        final Connection connection = flowManager.createConnection(generateUuid(proposed.getIdentifier(), destination.getIdentifier(), componentIdSeed), proposed.getName(), source, destination,
+            proposed.getSelectedRelationships());
         connection.setVersionedComponentId(proposed.getIdentifier());
         destinationGroup.addConnection(connection);
         updateConnection(connection, proposed);
@@ -4575,15 +4564,15 @@ public final class StandardProcessGroup implements ProcessGroup {
         switch (connectableComponent.getType()) {
             case FUNNEL:
                 return group.getFunnels().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny()
-                        .orElse(null);
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny()
+                    .orElse(null);
             case INPUT_PORT: {
                 final Optional<Port> port = group.getInputPorts().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (port.isPresent()) {
                     return port.get();
@@ -4591,34 +4580,34 @@ public final class StandardProcessGroup implements ProcessGroup {
 
                 // Attempt to locate child group by versioned component id
                 final Optional<ProcessGroup> optionalSpecifiedGroup = group.getProcessGroups().stream()
-                        .filter(child -> child.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(child.getIdentifier())).equals(connectableComponent.getGroupId()))
-                        .findFirst();
+                    .filter(child -> child.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(child.getIdentifier())).equals(connectableComponent.getGroupId()))
+                    .findFirst();
 
                 if (optionalSpecifiedGroup.isPresent()) {
                     final ProcessGroup specifiedGroup = optionalSpecifiedGroup.get();
                     return specifiedGroup.getInputPorts().stream()
-                            .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                    NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                            .findAny()
-                            .orElse(null);
+                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                            NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                        .findAny()
+                        .orElse(null);
                 }
 
                 // If no child group matched the versioned component id, then look at all child groups. This is done because
                 // in older versions, we did not properly map Versioned Component ID's to Ports' parent groups. As a result,
                 // if the flow doesn't contain the properly mapped group id, we need to search all child groups.
                 return group.getProcessGroups().stream()
-                        .flatMap(gr -> gr.getInputPorts().stream())
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny()
-                        .orElse(null);
+                    .flatMap(gr -> gr.getInputPorts().stream())
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny()
+                    .orElse(null);
             }
             case OUTPUT_PORT: {
                 final Optional<Port> port = group.getOutputPorts().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (port.isPresent()) {
                     return port.get();
@@ -4626,88 +4615,88 @@ public final class StandardProcessGroup implements ProcessGroup {
 
                 // Attempt to locate child group by versioned component id
                 final Optional<ProcessGroup> optionalSpecifiedGroup = group.getProcessGroups().stream()
-                        .filter(child -> child.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(child.getIdentifier())).equals(connectableComponent.getGroupId()))
-                        .findFirst();
+                    .filter(child -> child.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(child.getIdentifier())).equals(connectableComponent.getGroupId()))
+                    .findFirst();
 
                 if (optionalSpecifiedGroup.isPresent()) {
                     final ProcessGroup specifiedGroup = optionalSpecifiedGroup.get();
                     return specifiedGroup.getOutputPorts().stream()
-                            .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                    NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                            .findAny()
-                            .orElse(null);
+                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                            NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                        .findAny()
+                        .orElse(null);
                 }
 
                 // If no child group matched the versioned component id, then look at all child groups. This is done because
                 // in older versions, we did not properly map Versioned Component ID's to Ports' parent groups. As a result,
                 // if the flow doesn't contain the properly mapped group id, we need to search all child groups.
                 return group.getProcessGroups().stream()
-                        .flatMap(gr -> gr.getOutputPorts().stream())
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny()
-                        .orElse(null);
+                    .flatMap(gr -> gr.getOutputPorts().stream())
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny()
+                    .orElse(null);
             }
             case PROCESSOR:
                 return group.getProcessors().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny()
-                        .orElse(null);
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny()
+                    .orElse(null);
             case REMOTE_INPUT_PORT: {
                 final String rpgId = connectableComponent.getGroupId();
                 final Optional<RemoteProcessGroup> rpgOption = group.getRemoteProcessGroups().stream()
-                        .filter(component -> rpgId.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> rpgId.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (!rpgOption.isPresent()) {
                     throw new IllegalArgumentException("Connection refers to a Port with ID " + id + " within Remote Process Group with ID "
-                            + rpgId + " but could not find a Remote Process Group corresponding to that ID");
+                        + rpgId + " but could not find a Remote Process Group corresponding to that ID");
                 }
 
                 final RemoteProcessGroup rpg = rpgOption.get();
                 final Optional<RemoteGroupPort> portByIdOption = rpg.getInputPorts().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (portByIdOption.isPresent()) {
                     return portByIdOption.get();
                 }
 
                 return rpg.getInputPorts().stream()
-                        .filter(component -> connectableComponent.getName().equals(component.getName()))
-                        .findAny()
-                        .orElse(null);
+                    .filter(component -> connectableComponent.getName().equals(component.getName()))
+                    .findAny()
+                    .orElse(null);
             }
             case REMOTE_OUTPUT_PORT: {
                 final String rpgId = connectableComponent.getGroupId();
                 final Optional<RemoteProcessGroup> rpgOption = group.getRemoteProcessGroups().stream()
-                        .filter(component -> rpgId.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> rpgId.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (!rpgOption.isPresent()) {
                     throw new IllegalArgumentException("Connection refers to a Port with ID " + id + " within Remote Process Group with ID "
-                            + rpgId + " but could not find a Remote Process Group corresponding to that ID");
+                        + rpgId + " but could not find a Remote Process Group corresponding to that ID");
                 }
 
                 final RemoteProcessGroup rpg = rpgOption.get();
                 final Optional<RemoteGroupPort> portByIdOption = rpg.getOutputPorts().stream()
-                        .filter(component -> id.equals(component.getVersionedComponentId().orElse(
-                                NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
-                        .findAny();
+                    .filter(component -> id.equals(component.getVersionedComponentId().orElse(
+                        NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+                    .findAny();
 
                 if (portByIdOption.isPresent()) {
                     return portByIdOption.get();
                 }
 
                 return rpg.getOutputPorts().stream()
-                        .filter(component -> connectableComponent.getName().equals(component.getName()))
-                        .findAny()
-                        .orElse(null);
+                    .filter(component -> connectableComponent.getName().equals(component.getName()))
+                    .findAny()
+                    .orElse(null);
             }
         }
 
@@ -4728,7 +4717,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 final BundleCoordinate newBundleCoordinate = toCoordinate(proposed.getBundle());
                 final List<PropertyDescriptor> descriptors = new ArrayList<>(service.getRawPropertyValues().keySet());
                 final Set<URL> additionalUrls = service.getAdditionalClasspathResources(descriptors);
-                flowController.getReloadComponent().reload(service, proposed.getType(), newBundleCoordinate, additionalUrls);
+                reloadComponent.reload(service, proposed.getType(), newBundleCoordinate, additionalUrls);
             }
         } finally {
             service.resumeValidationTrigger();
@@ -4881,14 +4870,13 @@ public final class StandardProcessGroup implements ProcessGroup {
                 final BundleCoordinate newBundleCoordinate = toCoordinate(proposed.getBundle());
                 final List<PropertyDescriptor> descriptors = new ArrayList<>(processor.getProperties().keySet());
                 final Set<URL> additionalUrls = processor.getAdditionalClasspathResources(descriptors);
-                flowController.getReloadComponent().reload(processor, proposed.getType(), newBundleCoordinate, additionalUrls);
+                reloadComponent.reload(processor, proposed.getType(), newBundleCoordinate, additionalUrls);
             }
         } finally {
             processor.resumeValidationTrigger();
         }
     }
 
-
     private Map<String, String> populatePropertiesMap(final ComponentNode componentNode, final Map<String, String> proposedProperties,
                                                       final Map<String, VersionedPropertyDescriptor> proposedDescriptors, final ProcessGroup group) {
 
@@ -4978,7 +4966,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     private String getServiceInstanceId(final String serviceVersionedComponentId, final ProcessGroup group) {
         for (final ControllerServiceNode serviceNode : group.getControllerServices(false)) {
             final String versionedId = serviceNode.getVersionedComponentId().orElse(
-                    NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
+                NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
             if (versionedId.equals(serviceVersionedComponentId)) {
                 return serviceNode.getIdentifier();
             }
@@ -5007,13 +4995,13 @@ public final class StandardProcessGroup implements ProcessGroup {
         rpg.setComments(proposed.getComments());
         rpg.setCommunicationsTimeout(proposed.getCommunicationsTimeout());
         rpg.setInputPorts(proposed.getInputPorts() == null ? Collections.emptySet() : proposed.getInputPorts().stream()
-                .map(port -> createPortDescriptor(port, componentIdSeed, rpg.getIdentifier()))
-                .collect(Collectors.toSet()), false);
+            .map(port -> createPortDescriptor(port, componentIdSeed, rpg.getIdentifier()))
+            .collect(Collectors.toSet()), false);
         rpg.setName(proposed.getName());
         rpg.setNetworkInterface(proposed.getLocalNetworkInterface());
         rpg.setOutputPorts(proposed.getOutputPorts() == null ? Collections.emptySet() : proposed.getOutputPorts().stream()
-                .map(port -> createPortDescriptor(port, componentIdSeed, rpg.getIdentifier()))
-                .collect(Collectors.toSet()), false);
+            .map(port -> createPortDescriptor(port, componentIdSeed, rpg.getIdentifier()))
+            .collect(Collectors.toSet()), false);
         rpg.setPosition(new Position(proposed.getPosition().getX(), proposed.getPosition().getY()));
         rpg.setProxyHost(proposed.getProxyHost());
         rpg.setProxyPort(proposed.getProxyPort());
@@ -5043,7 +5031,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         return descriptor;
     }
 
-
     private Set<FlowDifference> getModifications() {
         final StandardVersionControlInformation vci = versionControlInfo.get();
 
@@ -5066,8 +5053,8 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
 
         try {
-            final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(flowController.getExtensionManager());
-            final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(this, controllerServiceProvider, flowController.getFlowRegistryClient(), false);
+            final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(extensionManager);
+            final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(this, controllerServiceProvider, flowRegistryClient, false);
 
             final ComparableDataFlow currentFlow = new StandardComparableDataFlow("Local Flow", versionedGroup);
             final ComparableDataFlow snapshotFlow = new StandardComparableDataFlow("Versioned Flow", vci.getFlowSnapshot());
@@ -5091,7 +5078,6 @@ public final class StandardProcessGroup implements ProcessGroup {
         }
     }
 
-
     @Override
     public void verifyCanUpdate(final VersionedFlowSnapshot updatedFlow, final boolean verifyConnectionRemoval, final boolean verifyNotDirty) {
         readLock.lock();
@@ -5111,17 +5097,17 @@ public final class StandardProcessGroup implements ProcessGroup {
 
                     if (modified) {
                         final String changes = modifications.stream()
-                                .map(FlowDifference::toString)
-                                .collect(Collectors.joining("\n"));
+                            .map(FlowDifference::toString)
+                            .collect(Collectors.joining("\n"));
 
                         LOG.error("Cannot change the Version of the flow for {} because the Process Group has been modified ({} modifications) "
-                                        + "since it was last synchronized with the Flow Registry. The following differences were found:\n{}",
-                                this, modifications.size(), changes);
+                                + "since it was last synchronized with the Flow Registry. The following differences were found:\n{}",
+                            this, modifications.size(), changes);
 
                         throw new IllegalStateException("Cannot change the Version of the flow for " + this
-                                + " because the Process Group has been modified (" + modifications.size()
-                                + " modifications) since it was last synchronized with the Flow Registry. The Process Group must be"
-                                + " reverted to its original form before changing the version.");
+                            + " because the Process Group has been modified (" + modifications.size()
+                            + " modifications) since it was last synchronized with the Flow Registry. The Process Group must be"
+                            + " reverted to its original form before changing the version.");
                     }
                 }
 
@@ -5138,37 +5124,37 @@ public final class StandardProcessGroup implements ProcessGroup {
 
             // Determine which input ports were removed from this process group
             final Map<String, Port> removedInputPortsByVersionId = new HashMap<>();
-            getInputPorts().stream()
-                    .forEach(port -> removedInputPortsByVersionId.put(port.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
+            getInputPorts()
+                .forEach(port -> removedInputPortsByVersionId.put(port.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
             flowContents.getInputPorts().stream()
-                    .map(VersionedPort::getIdentifier)
-                    .forEach(removedInputPortsByVersionId::remove);
+                .map(VersionedPort::getIdentifier)
+                .forEach(removedInputPortsByVersionId::remove);
 
             // Ensure that there are no incoming connections for any Input Port that was removed.
             for (final Port inputPort : removedInputPortsByVersionId.values()) {
                 final List<Connection> incomingConnections = inputPort.getIncomingConnections();
                 if (!incomingConnections.isEmpty()) {
                     throw new IllegalStateException(this + " cannot be updated to the proposed flow because the proposed flow "
-                            + "does not contain the Input Port " + inputPort + " and the Input Port currently has an incoming connection");
+                        + "does not contain the Input Port " + inputPort + " and the Input Port currently has an incoming connection");
                 }
             }
 
             // Determine which output ports were removed from this process group
             final Map<String, Port> removedOutputPortsByVersionId = new HashMap<>();
-            getOutputPorts().stream()
-                    .forEach(port -> removedOutputPortsByVersionId.put(port.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
+            getOutputPorts()
+                .forEach(port -> removedOutputPortsByVersionId.put(port.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
             flowContents.getOutputPorts().stream()
-                    .map(VersionedPort::getIdentifier)
-                    .forEach(removedOutputPortsByVersionId::remove);
+                .map(VersionedPort::getIdentifier)
+                .forEach(removedOutputPortsByVersionId::remove);
 
             // Ensure that there are no outgoing connections for any Output Port that was removed.
             for (final Port outputPort : removedOutputPortsByVersionId.values()) {
                 final Set<Connection> outgoingConnections = outputPort.getConnections();
                 if (!outgoingConnections.isEmpty()) {
                     throw new IllegalStateException(this + " cannot be updated to the proposed flow because the proposed flow "
-                            + "does not contain the Output Port " + outputPort + " and the Output Port currently has an outgoing connection");
+                        + "does not contain the Output Port " + outputPort + " and the Output Port currently has an outgoing connection");
                 }
             }
 
@@ -5176,18 +5162,19 @@ public final class StandardProcessGroup implements ProcessGroup {
             final Map<String, VersionedProcessor> proposedProcessors = new HashMap<>();
             findAllProcessors(flowContents, proposedProcessors);
 
-            findAllProcessors().stream()
-                    .forEach(proc -> proposedProcessors.remove(proc.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(proc.getIdentifier()))));
+            findAllProcessors()
+                .forEach(proc -> proposedProcessors.remove(proc.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(proc.getIdentifier()))));
 
             for (final VersionedProcessor processorToAdd : proposedProcessors.values()) {
                 final String processorToAddClass = processorToAdd.getType();
                 final BundleCoordinate processorToAddCoordinate = toCoordinate(processorToAdd.getBundle());
 
-                final boolean bundleExists = flowController.getExtensionManager().getBundles(processorToAddClass).stream()
-                        .anyMatch(b -> processorToAddCoordinate.equals(b.getBundleDetails().getCoordinate()));
+                final List<org.apache.nifi.bundle.Bundle> possibleBundles = extensionManager.getBundles(processorToAddClass);
+                final boolean bundleExists = possibleBundles.stream()
+                    .anyMatch(b -> processorToAddCoordinate.equals(b.getBundleDetails().getCoordinate()));
 
-                if (!bundleExists) {
+                if (!bundleExists && possibleBundles.size() != 1) {
                     throw new IllegalArgumentException("Unknown bundle " + processorToAddCoordinate.toString() + " for processor type " + processorToAddClass);
                 }
             }
@@ -5196,18 +5183,19 @@ public final class StandardProcessGroup implements ProcessGroup {
             final Map<String, VersionedControllerService> proposedServices = new HashMap<>();
             findAllControllerServices(flowContents, proposedServices);
 
-            findAllControllerServices().stream()
-                    .forEach(service -> proposedServices.remove(service.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(service.getIdentifier()))));
+            findAllControllerServices()
+                .forEach(service -> proposedServices.remove(service.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(service.getIdentifier()))));
 
             for (final VersionedControllerService serviceToAdd : proposedServices.values()) {
                 final String serviceToAddClass = serviceToAdd.getType();
                 final BundleCoordinate serviceToAddCoordinate = toCoordinate(serviceToAdd.getBundle());
 
-                final boolean bundleExists = flowController.getExtensionManager().getBundles(serviceToAddClass).stream()
-                        .anyMatch(b -> serviceToAddCoordinate.equals(b.getBundleDetails().getCoordinate()));
+                final List<org.apache.nifi.bundle.Bundle> possibleBundles = extensionManager.getBundles(serviceToAddClass);
+                final boolean bundleExists = possibleBundles.stream()
+                    .anyMatch(b -> serviceToAddCoordinate.equals(b.getBundleDetails().getCoordinate()));
 
-                if (!bundleExists) {
+                if (!bundleExists && possibleBundles.size() != 1) {
                     throw new IllegalArgumentException("Unknown bundle " + serviceToAddCoordinate.toString() + " for service type " + serviceToAddClass);
                 }
             }
@@ -5219,9 +5207,9 @@ public final class StandardProcessGroup implements ProcessGroup {
             final Map<String, VersionedConnection> proposedConnections = new HashMap<>();
             findAllConnections(flowContents, proposedConnections);
 
-            findAllConnections().stream()
-                    .forEach(conn -> proposedConnections.remove(conn.getVersionedComponentId().orElse(
-                            NiFiRegistryFlowMapper.generateVersionedComponentId(conn.getIdentifier()))));
+            findAllConnections()
+                .forEach(conn -> proposedConnections.remove(conn.getVersionedComponentId().orElse(
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(conn.getIdentifier()))));
 
             for (final VersionedConnection connectionToAdd : proposedConnections.values()) {
                 if (connectionToAdd.getPrioritizers() != null) {
@@ -5240,7 +5228,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                         LoadBalanceStrategy.valueOf(loadBalanceStrategyName);
                     } catch (final IllegalArgumentException iae) {
                         throw new IllegalArgumentException("Unable to create Connection with Load Balance Strategy of '" + loadBalanceStrategyName
-                                + "' because this is not a known Load Balance Strategy");
+                            + "' because this is not a known Load Balance Strategy");
                     }
                 }
             }
@@ -5285,55 +5273,55 @@ public final class StandardProcessGroup implements ProcessGroup {
      * Disallow removal of groups with attached templates. Optionally also check for removed connections with data in their
      * queue, either because the connections were removed from a matched process group or their group itself was removed.
      *
-     * @param processGroup            the current process group to examine
-     * @param proposedGroup           the proposed versioned process group to match with
+     * @param processGroup the current process group to examine
+     * @param proposedGroup the proposed versioned process group to match with
      * @param verifyConnectionRemoval whether or not to verify that connections that are not present in the proposed flow can be removed
      */
     private void verifyCanRemoveMissingComponents(final ProcessGroup processGroup, final VersionedProcessGroup proposedGroup,
                                                   final boolean verifyConnectionRemoval) {
         if (verifyConnectionRemoval) {
             final Map<String, VersionedConnection> proposedConnectionsByVersionedId = proposedGroup.getConnections().stream()
-                    .collect(Collectors.toMap(component -> component.getIdentifier(), Function.identity()));
+                .collect(Collectors.toMap(component -> component.getIdentifier(), Function.identity()));
 
             // match group's current connections to proposed connections to determine if they've been removed
             for (final Connection connection : processGroup.getConnections()) {
                 final String versionedId = connection.getVersionedComponentId().orElse(
-                        NiFiRegistryFlowMapper.generateVersionedComponentId(connection.getIdentifier()));
+                    NiFiRegistryFlowMapper.generateVersionedComponentId(connection.getIdentifier()));
                 final VersionedConnection proposedConnection = proposedConnectionsByVersionedId.get(versionedId);
                 if (proposedConnection == null) {
                     // connection doesn't exist in proposed connections, make sure it doesn't have any data in it
                     final FlowFileQueue flowFileQueue = connection.getFlowFileQueue();
                     if (!flowFileQueue.isEmpty()) {
                         throw new IllegalStateException(this + " cannot be updated to the proposed flow because the proposed flow "
-                                + "does not contain a match for " + connection + " and the connection currently has data in the queue.");
+                            + "does not contain a match for " + connection + " and the connection currently has data in the queue.");
                     }
                 }
             }
         }
 
         final Map<String, VersionedProcessGroup> proposedGroupsByVersionedId = proposedGroup.getProcessGroups().stream()
-                .collect(Collectors.toMap(component -> component.getIdentifier(), Function.identity()));
+            .collect(Collectors.toMap(component -> component.getIdentifier(), Function.identity()));
 
         // match current child groups to proposed child groups to determine if they've been removed
         for (final ProcessGroup childGroup : processGroup.getProcessGroups()) {
             final String versionedId = childGroup.getVersionedComponentId().orElse(
-                    NiFiRegistryFlowMapper.generateVersionedComponentId(childGroup.getIdentifier()));
+                NiFiRegistryFlowMapper.generateVersionedComponentId(childGroup.getIdentifier()));
             final VersionedProcessGroup proposedChildGroup = proposedGroupsByVersionedId.get(versionedId);
             if (proposedChildGroup == null) {
                 // child group will be removed, check group and descendants for attached templates
                 final Template removedTemplate = findAllTemplates(childGroup).stream().findFirst().orElse(null);
                 if (removedTemplate != null) {
                     throw new IllegalStateException(this + " cannot be updated to the proposed flow because the child " + removedTemplate.getProcessGroup()
-                            + " that exists locally has one or more Templates, and the proposed flow does not contain these templates. "
-                            + "A Process Group cannot be deleted while it contains Templates. Please remove the Templates before re-attempting.");
+                        + " that exists locally has one or more Templates, and the proposed flow does not contain these templates. "
+                        + "A Process Group cannot be deleted while it contains Templates. Please remove the Templates before re-attempting.");
                 }
                 if (verifyConnectionRemoval) {
                     // check removed group and its descendants for connections with data in the queue
                     final Connection removedConnection = findAllConnections(childGroup).stream()
-                            .filter(connection -> !connection.getFlowFileQueue().isEmpty()).findFirst().orElse(null);
+                        .filter(connection -> !connection.getFlowFileQueue().isEmpty()).findFirst().orElse(null);
                     if (removedConnection != null) {
                         throw new IllegalStateException(this + " cannot be updated to the proposed flow because the proposed flow "
-                                + "does not contain a match for " + removedConnection + " and the connection currently has data in the queue.");
+                            + "does not contain a match for " + removedConnection + " and the connection currently has data in the queue.");
                     }
                 }
             } else {
@@ -5354,22 +5342,22 @@ public final class StandardProcessGroup implements ProcessGroup {
                 // In order to do this, we have to ensure that the Process Group is 'current'.
                 final VersionedFlowState state = vci.getStatus().getState();
                 if (state == VersionedFlowState.STALE
-                        || (state == VersionedFlowState.LOCALLY_MODIFIED_AND_STALE && VersionedFlowDTO.COMMIT_ACTION.equals(saveAction))) {
+                    || (state == VersionedFlowState.LOCALLY_MODIFIED_AND_STALE && VersionedFlowDTO.COMMIT_ACTION.equals(saveAction))) {
                     throw new IllegalStateException("Cannot update Version Control Information for Process Group with ID " + getIdentifier()
-                            + " because the Process Group in the flow is not synchronized with the most recent version of the Flow in the Flow Registry. "
-                            + "In order to publish a new version of the Flow, the Process Group must first be in synch with the latest version in the Flow Registry.");
+                        + " because the Process Group in the flow is not synchronized with the most recent version of the Flow in the Flow Registry. "
+                        + "In order to publish a new version of the Flow, the Process Group must first be in synch with the latest version in the Flow Registry.");
                 }
 
                 // Flow ID matches. We want to publish the Process Group as the next version of the Flow, so we must
                 // ensure that all other parameters match as well.
                 if (!bucketId.equals(vci.getBucketIdentifier())) {
                     throw new IllegalStateException("Cannot update Version Control Information for Process Group with ID " + getIdentifier()
-                            + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
+                        + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
                 }
 
                 if (!registryId.equals(vci.getRegistryIdentifier())) {
                     throw new IllegalStateException("Cannot update Version Control Information for Process Group with ID " + getIdentifier()
-                            + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
+                        + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
                 }
             } else if (flowId != null) {
                 // Flow ID is specified but different. This is not allowed, because Flow ID's are automatically generated,
@@ -5378,7 +5366,7 @@ public final class StandardProcessGroup implements ProcessGroup {
                 // not allowed because the Process Group must be in synch with the latest version of the flow before that
                 // can be done.
                 throw new IllegalStateException("Cannot update Version Control Information for Process Group with ID " + getIdentifier()
-                        + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
+                    + " because the Process Group is currently synchronized with a different Versioned Flow than the one specified in the request.");
             }
         }
     }
@@ -5407,14 +5395,14 @@ public final class StandardProcessGroup implements ProcessGroup {
 
                 if (modified) {
                     throw new IllegalStateException("Process Group cannot " + action + " because it contains a child or descendant Process Group that is under Version Control and "
-                            + "has local modifications. Each descendant Process Group that is under Version Control must first be reverted or have its changes pushed to the Flow Registry before "
-                            + "this action can be performed on the parent Process Group.");
+                        + "has local modifications. Each descendant Process Group that is under Version Control must first be reverted or have its changes pushed to the Flow Registry before "
+                        + "this action can be performed on the parent Process Group.");
                 }
 
                 if (flowState == VersionedFlowState.SYNC_FAILURE) {
                     throw new IllegalStateException("Process Group cannot " + action + " because it contains a child or descendant Process Group that is under Version Control and "
-                            + "is not synchronized with the Flow Registry. Each descendant Process Group must first be synchronized with the Flow Registry before this action can be "
-                            + "performed on the parent Process Group. NiFi will continue to attempt to communicate with the Flow Registry periodically in the background.");
+                        + "is not synchronized with the Flow Registry. Each descendant Process Group must first be synchronized with the Flow Registry before this action can be "
+                        + "performed on the parent Process Group. NiFi will continue to attempt to communicate with the Flow Registry periodically in the background.");
                 }
             }
         }
@@ -5517,7 +5505,7 @@ public final class StandardProcessGroup implements ProcessGroup {
     private synchronized void setBatchCounts(final FlowFileOutboundPolicy outboundPolicy, final FlowFileConcurrency flowFileConcurrency) {
         if (outboundPolicy == FlowFileOutboundPolicy.BATCH_OUTPUT && flowFileConcurrency == FlowFileConcurrency.SINGLE_FLOWFILE_PER_NODE) {
             if (batchCounts instanceof NoOpBatchCounts) {
-                final StateManager stateManager = flowController.getStateManagerProvider().getStateManager(getIdentifier());
+                final StateManager stateManager = stateManagerProvider.getStateManager(getIdentifier());
                 batchCounts = new StandardBatchCounts(this, stateManager);
             }
         } else {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardVersionedFlowStatus.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardVersionedFlowStatus.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardVersionedFlowStatus.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardVersionedFlowStatus.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/UnboundedFlowFileGate.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/UnboundedFlowFileGate.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/UnboundedFlowFileGate.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/UnboundedFlowFileGate.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/VersionControlFields.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/VersionControlFields.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/VersionControlFields.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/VersionControlFields.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/repository/StandardLogRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/repository/StandardLogRepository.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/logging/repository/StandardLogRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/repository/StandardLogRepository.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContextManager.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterUpdate.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterUpdate.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterUpdate.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterUpdate.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/ComponentSpecificControllerServiceLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/ComponentSpecificControllerServiceLookup.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/ComponentSpecificControllerServiceLookup.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/ComponentSpecificControllerServiceLookup.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardProcessContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardProcessorInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessorInitializationContext.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardProcessorInitializationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessorInitializationContext.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
index 174eb93..91a8156 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
@@ -17,7 +17,7 @@
  */
 package org.apache.nifi.processor;
 
-import org.apache.nifi.AbstractValidationContext;
+import org.apache.nifi.components.validation.AbstractValidationContext;
 import org.apache.nifi.attribute.expression.language.PreparedQuery;
 import org.apache.nifi.attribute.expression.language.Query;
 import org.apache.nifi.attribute.expression.language.Query.Range;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/RestBasedFlowRegistry.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/RestBasedFlowRegistry.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/RestBasedFlowRegistry.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/RestBasedFlowRegistry.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/StandardFlowRegistryClient.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/StandardFlowRegistryClient.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/StandardFlowRegistryClient.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/StandardFlowRegistryClient.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/StandardVersionControlInformation.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/StandardVersionControlInformation.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/StandardVersionControlInformation.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/StandardVersionControlInformation.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedConnectableComponent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedConnectableComponent.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedConnectableComponent.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedConnectableComponent.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedComponent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedComponent.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedComponent.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedComponent.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedConnection.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedConnection.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedConnection.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedConnection.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedControllerService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedControllerService.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedControllerService.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedControllerService.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedFunnel.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedFunnel.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedFunnel.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedFunnel.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedLabel.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedLabel.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedLabel.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedLabel.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedPort.java
similarity index 100%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedPort.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedPort.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessGroup.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessGroup.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessGroup.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessor.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessor.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedProcessor.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteGroupPort.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteGroupPort.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteGroupPort.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteProcessGroup.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteProcessGroup.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/InstantiatedVersionedRemoteProcessGroup.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/StandardComparableDataFlow.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/StandardComparableDataFlow.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/mapping/StandardComparableDataFlow.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/StandardComparableDataFlow.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/variable/MutableVariableRegistry.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/variable/MutableVariableRegistry.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/variable/MutableVariableRegistry.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/variable/MutableVariableRegistry.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/variable/StandardComponentVariableRegistry.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/variable/StandardComponentVariableRegistry.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/variable/StandardComponentVariableRegistry.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/variable/StandardComponentVariableRegistry.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/RemoteNiFiUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/RemoteNiFiUtils.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/RemoteNiFiUtils.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/RemoteNiFiUtils.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
similarity index 98%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
index 9ca4dad..9bdde83 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
@@ -16,37 +16,6 @@
  */
 package org.apache.nifi.remote;
 
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.net.ssl.SSLContext;
-import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Resource;
 import org.apache.nifi.authorization.resource.Authorizable;
@@ -75,12 +44,43 @@ import org.apache.nifi.reporting.BulletinRepository;
 import org.apache.nifi.reporting.ComponentType;
 import org.apache.nifi.reporting.Severity;
 import org.apache.nifi.util.FormatUtils;
-import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.api.dto.ControllerDTO;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.requireNonNull;
+
 /**
  * Represents the Root Process Group of a remote NiFi Instance. Holds
  * information about that remote instance, as well as Incoming Ports and
@@ -99,7 +99,6 @@ public class StandardRemoteProcessGroup implements RemoteProcessGroup {
     private volatile String targetUris;
     private final ProcessScheduler scheduler;
     private final EventReporter eventReporter;
-    private final NiFiProperties nifiProperties;
     private final StateManager stateManager;
     private final long remoteContentsCacheExpiration;
     private volatile boolean initialized = false;
@@ -147,9 +146,8 @@ public class StandardRemoteProcessGroup implements RemoteProcessGroup {
     private final ScheduledExecutorService backgroundThreadExecutor;
 
     public StandardRemoteProcessGroup(final String id, final String targetUris, final ProcessGroup processGroup, final ProcessScheduler processScheduler,
-                                      final BulletinRepository bulletinRepository, final SSLContext sslContext, final NiFiProperties nifiProperties,
-                                      final StateManager stateManager) {
-        this.nifiProperties = nifiProperties;
+                                      final BulletinRepository bulletinRepository, final SSLContext sslContext, final StateManager stateManager,
+                                      final long remoteContentsCacheExpirationMillis) {
         this.stateManager = stateManager;
         this.id = requireNonNull(id);
 
@@ -159,9 +157,7 @@ public class StandardRemoteProcessGroup implements RemoteProcessGroup {
         this.sslContext = sslContext;
         this.scheduler = processScheduler;
         this.authorizationIssue = "Establishing connection to " + targetUris;
-
-        final String expirationPeriod = nifiProperties.getProperty(NiFiProperties.REMOTE_CONTENTS_CACHE_EXPIRATION, "30 secs");
-        remoteContentsCacheExpiration = FormatUtils.getTimeDuration(expirationPeriod, TimeUnit.MILLISECONDS);
+        this.remoteContentsCacheExpiration = remoteContentsCacheExpirationMillis;
 
         eventReporter = new EventReporter() {
             private static final long serialVersionUID = 1L;
@@ -691,7 +687,7 @@ public class StandardRemoteProcessGroup implements RemoteProcessGroup {
             }
 
             final StandardRemoteGroupPort port = new StandardRemoteGroupPort(descriptor.getId(), descriptor.getTargetId(), descriptor.getName(),
-                    this, TransferDirection.RECEIVE, ConnectableType.REMOTE_OUTPUT_PORT, sslContext, scheduler, nifiProperties);
+                    this, TransferDirection.RECEIVE, ConnectableType.REMOTE_OUTPUT_PORT, sslContext, scheduler);
             port.setProcessGroup(getProcessGroup());
             outputPorts.put(descriptor.getId(), port);
 
@@ -773,7 +769,7 @@ public class StandardRemoteProcessGroup implements RemoteProcessGroup {
             // unique for each Remote Group Port, so that if we have multiple RPG's pointing
             // to the same target, we have unique ID's for each of those ports.
             final StandardRemoteGroupPort port = new StandardRemoteGroupPort(descriptor.getId(), descriptor.getTargetId(), descriptor.getName(), this,
-                    TransferDirection.SEND, ConnectableType.REMOTE_INPUT_PORT, sslContext, scheduler, nifiProperties);
+                    TransferDirection.SEND, ConnectableType.REMOTE_INPUT_PORT, sslContext, scheduler);
             port.setProcessGroup(getProcessGroup());
 
             if (descriptor.getConcurrentlySchedulableTaskCount() != null) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroupPortDescriptor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroupPortDescriptor.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroupPortDescriptor.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroupPortDescriptor.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
similarity index 83%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
index 483c776..1a9db78 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/reporting/AbstractEventAccess.java
@@ -14,32 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.reporting;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
+package org.apache.nifi.reporting;
 
-import org.apache.commons.collections4.Predicate;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.action.Action;
-import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
-import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.components.validation.ValidationStatus;
 import org.apache.nifi.connectable.Connectable;
 import org.apache.nifi.connectable.ConnectableType;
 import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.connectable.Funnel;
 import org.apache.nifi.connectable.Port;
-import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.controller.queue.QueueSize;
 import org.apache.nifi.controller.repository.FlowFileEvent;
 import org.apache.nifi.controller.repository.FlowFileEventRepository;
@@ -57,10 +46,8 @@ import org.apache.nifi.controller.status.analytics.StatusAnalytics;
 import org.apache.nifi.controller.status.analytics.StatusAnalyticsEngine;
 import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.groups.RemoteProcessGroup;
-import org.apache.nifi.history.History;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.provenance.ProvenanceEventRecord;
-import org.apache.nifi.provenance.ProvenanceRepository;
 import org.apache.nifi.registry.flow.VersionControlInformation;
 import org.apache.nifi.registry.flow.VersionedFlowState;
 import org.apache.nifi.registry.flow.VersionedFlowStatus;
@@ -69,28 +56,29 @@ import org.apache.nifi.remote.RemoteGroupPort;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class StandardEventAccess implements UserAwareEventAccess {
-    private static final Logger logger = LoggerFactory.getLogger(StandardEventAccess.class);
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+public abstract class AbstractEventAccess implements EventAccess {
+    private static final Logger logger = LoggerFactory.getLogger(AbstractEventAccess.class);
 
-    private final FlowFileEventRepository flowFileEventRepository;
-    private final FlowController flowController;
+    private final ProcessScheduler processScheduler;
     private final StatusAnalyticsEngine statusAnalyticsEngine;
+    private final FlowManager flowManager;
+    private final FlowFileEventRepository flowFileEventRepository;
 
-    public StandardEventAccess(final FlowController flowController, final FlowFileEventRepository flowFileEventRepository) {
-        this.flowController = flowController;
+    public AbstractEventAccess(final ProcessScheduler processScheduler, final StatusAnalyticsEngine analyticsEngine, final FlowManager flowManager,
+                               final FlowFileEventRepository flowFileEventRepository) {
+        this.processScheduler = processScheduler;
+        this.statusAnalyticsEngine = analyticsEngine;
+        this.flowManager = flowManager;
         this.flowFileEventRepository = flowFileEventRepository;
-        this.statusAnalyticsEngine = flowController.getStatusAnalyticsEngine();
-    }
-
-    /**
-     * Returns the status of all components in the controller. This request is
-     * not in the context of a user so the results will be unfiltered.
-     *
-     * @return the component status
-     */
-    @Override
-    public ProcessGroupStatus getControllerStatus() {
-        return getGroupStatus(flowController.getFlowManager().getRootGroupId());
     }
 
     /**
@@ -116,113 +104,16 @@ public class StandardEventAccess implements UserAwareEventAccess {
      * @return the component status
      */
     public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
+        final ProcessGroup group = flowManager.getGroup(groupId);
 
         // this was invoked with no user context so the results will be unfiltered... necessary for aggregating status history
         return getGroupStatus(group, statusReport, authorizable -> true, Integer.MAX_VALUE, 1);
     }
 
-
-    @Override
-    public List<ProvenanceEventRecord> getProvenanceEvents(final long firstEventId, final int maxRecords) throws IOException {
-        return new ArrayList<>(getProvenanceRepository().getEvents(firstEventId, maxRecords));
-    }
-
-    @Override
-    public List<Action> getFlowChanges(final int firstActionId, final int maxActions) {
-        final History history = flowController.getAuditService().getActions(firstActionId, maxActions);
-        return new ArrayList<>(history.getActions());
-    }
-
-    @Override
-    public ProvenanceRepository getProvenanceRepository() {
-        return flowController.getProvenanceRepository();
-    }
-
-
-    private RepositoryStatusReport generateRepositoryStatusReport() {
+    protected RepositoryStatusReport generateRepositoryStatusReport() {
         return flowFileEventRepository.reportTransferEvents(System.currentTimeMillis());
     }
 
-    @Override
-    public ProcessorStatus getProcessorStatus(final String processorId, final NiFiUser user) {
-        final ProcessorNode procNode = flowController.getFlowManager().getProcessorNode(processorId);
-        if (procNode == null) {
-            return null;
-        }
-
-        FlowFileEvent flowFileEvent = flowFileEventRepository.reportTransferEvents(processorId, System.currentTimeMillis());
-        if (flowFileEvent == null) {
-            flowFileEvent = EmptyFlowFileEvent.INSTANCE;
-        }
-
-        final Predicate<Authorizable> authorizer = authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user);
-        return getProcessorStatus(flowFileEvent, procNode, authorizer);
-    }
-
-    /**
-     * Returns the status for components in the specified group. This request is
-     * made by the specified user so the results will be filtered accordingly.
-     *
-     * @param groupId group id
-     * @param user user making request
-     * @return the component status
-     */
-    public ProcessGroupStatus getGroupStatus(final String groupId, final NiFiUser user, final int recursiveStatusDepth) {
-        final RepositoryStatusReport repoStatusReport = generateRepositoryStatusReport();
-        return getGroupStatus(groupId, repoStatusReport, user, recursiveStatusDepth);
-    }
-
-
-    /**
-     * Returns the status for the components in the specified group with the
-     * specified report. This request is made by the specified user so the
-     * results will be filtered accordingly.
-     *
-     * @param groupId group id
-     * @param statusReport report
-     * @param user user making request
-     * @return the component status
-     */
-    public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport, final NiFiUser user) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
-
-        // on demand status request for a specific user... require authorization per component and filter results as appropriate
-        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user), Integer.MAX_VALUE, 1);
-    }
-
-    /**
-     * Returns the status for components in the specified group. This request is
-     * made by the specified user so the results will be filtered accordingly.
-     *
-     * @param groupId group id
-     * @param user user making request
-     * @return the component status
-     */
-    public ProcessGroupStatus getGroupStatus(final String groupId, final NiFiUser user) {
-        final RepositoryStatusReport repoStatusReport = generateRepositoryStatusReport();
-        return getGroupStatus(groupId, repoStatusReport, user);
-    }
-
-
-
-    /**
-     * Returns the status for the components in the specified group with the
-     * specified report. This request is made by the specified user so the
-     * results will be filtered accordingly.
-     *
-     * @param groupId group id
-     * @param statusReport report
-     * @param user user making request
-     * @param recursiveStatusDepth the number of levels deep we should recurse and still include the the processors' statuses, the groups' statuses, etc. in the returned ProcessGroupStatus
-     * @return the component status
-     */
-    public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport, final NiFiUser user, final int recursiveStatusDepth) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
-
-        // on demand status request for a specific user... require authorization per component and filter results as appropriate
-        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user), recursiveStatusDepth, 1);
-    }
 
     /**
      * Returns the status for the components in the specified group with the
@@ -237,16 +128,14 @@ public class StandardEventAccess implements UserAwareEventAccess {
      * @return the component status
      */
     ProcessGroupStatus getGroupStatus(final ProcessGroup group, final RepositoryStatusReport statusReport, final Predicate<Authorizable> isAuthorized,
-                                              final int recursiveStatusDepth, final int currentDepth) {
+                                      final int recursiveStatusDepth, final int currentDepth) {
         if (group == null) {
             return null;
         }
 
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
-
         final ProcessGroupStatus status = new ProcessGroupStatus();
         status.setId(group.getIdentifier());
-        status.setName(isAuthorized.evaluate(group) ? group.getName() : group.getIdentifier());
+        status.setName(isAuthorized.test(group) ? group.getName() : group.getIdentifier());
         int activeGroupThreads = 0;
         int terminatedGroupThreads = 0;
         long bytesRead = 0L;
@@ -340,9 +229,9 @@ public class StandardEventAccess implements UserAwareEventAccess {
 
         // get the connection and remote port status
         for (final Connection conn : group.getConnections()) {
-            final boolean isConnectionAuthorized = isAuthorized.evaluate(conn);
-            final boolean isSourceAuthorized = isAuthorized.evaluate(conn.getSource());
-            final boolean isDestinationAuthorized = isAuthorized.evaluate(conn.getDestination());
+            final boolean isConnectionAuthorized = isAuthorized.test(conn);
+            final boolean isSourceAuthorized = isAuthorized.test(conn.getSource());
+            final boolean isDestinationAuthorized = isAuthorized.test(conn.getDestination());
 
             final ConnectionStatus connStatus = new ConnectionStatus();
             connStatus.setId(conn.getIdentifier());
@@ -379,7 +268,7 @@ public class StandardEventAccess implements UserAwareEventAccess {
                     predictions.setPredictedPercentBytes(predictionValues.get("nextIntervalPercentageUseBytes").intValue());
                     predictions.setPredictionIntervalMillis(predictionValues.get("intervalTimeMillis"));
                 }
-            }else{
+            } else {
                 connStatus.setPredictions(null);
             }
 
@@ -431,7 +320,7 @@ public class StandardEventAccess implements UserAwareEventAccess {
 
         final Set<Port> inputPorts = group.getInputPorts();
         for (final Port port : inputPorts) {
-            final boolean isInputPortAuthorized = isAuthorized.evaluate(port);
+            final boolean isInputPortAuthorized = isAuthorized.test(port);
 
             final PortStatus portStatus = new PortStatus();
             portStatus.setId(port.getIdentifier());
@@ -494,7 +383,7 @@ public class StandardEventAccess implements UserAwareEventAccess {
 
         final Set<Port> outputPorts = group.getOutputPorts();
         for (final Port port : outputPorts) {
-            final boolean isOutputPortAuthorized = isAuthorized.evaluate(port);
+            final boolean isOutputPortAuthorized = isAuthorized.test(port);
 
             final PortStatus portStatus = new PortStatus();
             portStatus.setId(port.getIdentifier());
@@ -588,11 +477,8 @@ public class StandardEventAccess implements UserAwareEventAccess {
         return status;
     }
 
-
     private RemoteProcessGroupStatus createRemoteGroupStatus(final RemoteProcessGroup remoteGroup, final RepositoryStatusReport statusReport, final Predicate<Authorizable> isAuthorized) {
-        final boolean isRemoteProcessGroupAuthorized = isAuthorized.evaluate(remoteGroup);
-
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
+        final boolean isRemoteProcessGroupAuthorized = isAuthorized.test(remoteGroup);
 
         int receivedCount = 0;
         long receivedContentSize = 0L;
@@ -678,10 +564,8 @@ public class StandardEventAccess implements UserAwareEventAccess {
         return getProcessorStatus(entry, procNode, isAuthorized);
     }
 
-    private ProcessorStatus getProcessorStatus(final FlowFileEvent flowFileEvent, final ProcessorNode procNode, final Predicate<Authorizable> isAuthorized) {
-        final boolean isProcessorAuthorized = isAuthorized.evaluate(procNode);
-
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
+    protected ProcessorStatus getProcessorStatus(final FlowFileEvent flowFileEvent, final ProcessorNode procNode, final Predicate<Authorizable> isAuthorized) {
+        final boolean isProcessorAuthorized = isAuthorized.test(procNode);
 
         final ProcessorStatus status = new ProcessorStatus();
         status.setId(procNode.getIdentifier());
@@ -746,6 +630,22 @@ public class StandardEventAccess implements UserAwareEventAccess {
     }
 
     /**
+     * Returns the status of all components in the controller. This request is
+     * not in the context of a user so the results will be unfiltered.
+     *
+     * @return the component status
+     */
+    @Override
+    public ProcessGroupStatus getControllerStatus() {
+        return getGroupStatus(flowManager.getRootGroupId());
+    }
+
+    @Override
+    public List<ProvenanceEventRecord> getProvenanceEvents(final long firstEventId, final int maxRecords) throws IOException {
+        return new ArrayList<>(getProvenanceRepository().getEvents(firstEventId, maxRecords));
+    }
+
+    /**
      * Returns the total number of bytes read by this instance (at the root process group level, i.e. all events) since the instance started
      *
      * @return the total number of bytes read by this instance
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ClassAnnotationPair.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/Connectables.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/Connectables.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/Connectables.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/Connectables.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
similarity index 88%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
index 5666f9c..5f163e6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ReflectionUtils.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
@@ -16,19 +16,22 @@
  */
 package org.apache.nifi.util;
 
+import org.apache.nifi.logging.ComponentLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import org.apache.nifi.logging.ComponentLog;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.core.annotation.AnnotationUtils;
 
 public class ReflectionUtils {
 
@@ -172,7 +175,24 @@ public class ReflectionUtils {
     }
 
     private static List<Method> discoverMethodsWithAnnotations(final Class<?> clazz, final Class<? extends Annotation>[] annotations) {
-        final List<Method> methods = new ArrayList<>();
+        // Consider two methods equal if they have the same name and same parameter types.
+        final Comparator<Method> comparator = Comparator.comparing(Method::getName).thenComparing(new Comparator<Method>() {
+            @Override
+            public int compare(final Method o1, final Method o2) {
+                return createString(o1.getParameterTypes()).compareTo(createString(o2.getParameterTypes()));
+            }
+
+            private String createString(final Class<?>[] parameters) {
+                final StringBuilder sb = new StringBuilder();
+                for (final Class<?> param : parameters) {
+                    sb.append(param).append(",");
+                }
+                return sb.toString();
+            }
+        });
+
+        // We want to de-dupe methods that are equal to one another based on our definition of equality (name & argument class types).
+        final Set<Method> methods = new TreeSet<>(comparator);
 
         for (Method method : clazz.getMethods()) {
             if (isAnyAnnotationPresent(method, annotations)) {
@@ -180,19 +200,33 @@ public class ReflectionUtils {
             }
         }
 
-        return methods;
+        // Look at all super classes recursively, adding to our list of methods if it has any that are not already present.
+        // This way, if a class overrides a method in the superclass, the superclass's implementation won't be added. But any
+        // method that exists in the superclass that isn't overridden will still be added.
+        final Class<?> superClass = clazz.getSuperclass();
+        if (superClass != Object.class) {
+            final List<Method> superMethods = discoverMethodsWithAnnotations(superClass, annotations);
+            methods.addAll(superMethods);
+        }
+
+        return new ArrayList<>(methods);
     }
 
 
     private static boolean isAnyAnnotationPresent(Method method, Class<? extends Annotation>[] annotations) {
         for (Class<? extends Annotation> annotation : annotations) {
-            if (AnnotationUtils.findAnnotation(method, annotation) != null) {
+            if (isAnnotationPresent(method, annotation)) {
                 return true;
             }
         }
         return false;
     }
 
+    private static boolean isAnnotationPresent(final Method method, final Class<? extends Annotation> annotationClass) {
+        final Annotation annotation = method.getAnnotation(annotationClass);
+        return annotation != null;
+    }
+
     private static Object[] buildUpdatedArgumentsList(boolean quietly, Method method, Class<?>[] annotations, ComponentLog processLogger, Object... args) {
         boolean parametersCompatible = true;
         int argsCount = 0;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/SnippetUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/SnippetUtils.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/SnippetUtils.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/SnippetUtils.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ThreadUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ThreadUtils.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/util/ThreadUtils.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ThreadUtils.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorIT.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorIT.groovy
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorIT.groovy
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorIT.groovy
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/encrypt/StringEncryptorTest.groovy
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/util/SnippetUtilsSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/util/SnippetUtilsSpec.groovy
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/util/SnippetUtilsSpec.groovy
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/groovy/org/apache/nifi/util/SnippetUtilsSpec.groovy
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java
index 778efbe..f771271 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestRingBufferEventRepository.java
@@ -17,8 +17,8 @@
 package org.apache.nifi.controller.repository;
 
 import org.apache.nifi.controller.repository.metrics.RingBufferEventRepository;
+import org.junit.Assert;
 import org.junit.Test;
-import org.testng.Assert;
 
 import java.io.IOException;
 import java.util.Collections;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardFlowFileRecord.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestStandardFlowFileRecord.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardFlowFileRecord.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestStandardFlowFileRecord.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardProvenanceReporter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestStandardProvenanceReporter.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardProvenanceReporter.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/TestStandardProvenanceReporter.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/metrics/TestSecondPrecisionEventContainer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/metrics/TestSecondPrecisionEventContainer.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/metrics/TestSecondPrecisionEventContainer.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/repository/metrics/TestSecondPrecisionEventContainer.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceInvocationHandler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceInvocationHandler.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceInvocationHandler.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceInvocationHandler.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/logging/TestStandardLogRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/logging/TestStandardLogRepository.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/logging/TestStandardLogRepository.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/logging/TestStandardLogRepository.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
similarity index 98%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
index 6710163..2be492e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
@@ -33,7 +33,6 @@ import java.util.Set;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.testng.Assert.assertNull;
 
 public class TestStandardParameterContext {
 
@@ -54,12 +53,12 @@ public class TestStandardParameterContext {
 
         final Parameter abcParam = context.getParameter("abc").get();
         assertEquals(abcDescriptor, abcParam.getDescriptor());
-        assertNull(abcParam.getDescriptor().getDescription());
+        Assert.assertNull(abcParam.getDescriptor().getDescription());
         assertEquals("123", abcParam.getValue());
 
         final Parameter xyzParam = context.getParameter("xyz").get();
         assertEquals(xyzDescriptor, xyzParam.getDescriptor());
-        assertNull(xyzParam.getDescriptor().getDescription());
+        Assert.assertNull(xyzParam.getDescriptor().getDescription());
         assertEquals("242526", xyzParam.getValue());
 
         final Map<String, Parameter> secondParameters = new HashMap<>();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/processor/TestSimpleProcessLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/processor/TestSimpleProcessLogger.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/processor/TestSimpleProcessLogger.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/processor/TestSimpleProcessLogger.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/processor/TestStandardPropertyValue.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/processor/TestStandardPropertyValue.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/processor/TestStandardPropertyValue.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/processor/TestStandardPropertyValue.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/util/ReflectionUtilsTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/ReflectionUtilsTest.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/util/ReflectionUtilsTest.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/ReflectionUtilsTest.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/resources/conf/nifi.properties
new file mode 100644
index 0000000..9cfc3fb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/resources/conf/nifi.properties
@@ -0,0 +1,127 @@
+# 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.
+
+# Core Properties #
+nifi.flow.configuration.file=./target/flow.xml.gz
+nifi.flow.configuration.archive.dir=./target/archive/
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=2 sec
+nifi.administrative.yield.duration=30 sec
+
+nifi.reporting.task.configuration.file=./target/reporting-tasks.xml
+nifi.controller.service.configuration.file=./target/controller-services.xml
+nifi.templates.directory=./target/templates
+nifi.ui.banner.text=UI Banner Text
+nifi.ui.autorefresh.interval=30 sec
+nifi.nar.library.directory=./target/lib
+nifi.nar.working.directory=./target/work/nar/
+
+# H2 Settings
+nifi.database.directory=./database_repository
+nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE
+
+# FlowFile Repository
+nifi.flowfile.repository.directory=./target/test-repo
+nifi.flowfile.repository.partitions=1
+nifi.flowfile.repository.checkpoint.interval=2 mins
+nifi.queue.swap.threshold=20000
+nifi.swap.storage.directory=./target/test-repo/swap
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.claim.max.appendable.size=1 MB
+nifi.content.claim.max.flow.files=100
+nifi.content.repository.directory.default=./target/content_repository
+
+# Provenance Repository Properties
+nifi.provenance.repository.storage.directory=./target/provenance_repository
+nifi.provenance.repository.max.storage.time=24 hours
+nifi.provenance.repository.max.storage.size=1 GB
+nifi.provenance.repository.rollover.time=30 secs
+nifi.provenance.repository.rollover.size=100 MB
+
+# Site to Site properties
+nifi.remote.input.socket.port=9990
+nifi.remote.input.secure=true
+
+# web properties #
+nifi.web.war.directory=./target/lib
+nifi.web.http.host=
+nifi.web.http.port=8080
+nifi.web.https.host=
+nifi.web.https.port=
+nifi.web.jetty.working.directory=./target/work/jetty
+
+# security properties #
+nifi.sensitive.props.key=key
+nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL
+nifi.sensitive.props.provider=BC
+
+nifi.security.keystore=
+nifi.security.keystoreType=
+nifi.security.keystorePasswd=
+nifi.security.keyPasswd=
+nifi.security.truststore=
+nifi.security.truststoreType=
+nifi.security.truststorePasswd=
+nifi.security.user.authorizer=
+
+# cluster common properties (cluster manager and nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=false
+nifi.cluster.protocol.socket.timeout=30 sec
+nifi.cluster.protocol.connection.handshake.timeout=45 sec
+# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties must be configured #
+nifi.cluster.protocol.use.multicast=false
+nifi.cluster.protocol.multicast.address=
+nifi.cluster.protocol.multicast.port=
+nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms
+nifi.cluster.protocol.multicast.service.locator.attempts=3
+nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=2
+# if multicast is not used, nifi.cluster.node.unicast.xxx must have same values as nifi.cluster.manager.xxx #
+nifi.cluster.node.unicast.manager.address=
+nifi.cluster.node.unicast.manager.protocol.port=
+nifi.cluster.node.unicast.manager.authority.provider.port=
+
+# cluster manager properties (only configure for cluster manager) #
+nifi.cluster.is.manager=false
+nifi.cluster.manager.address=
+nifi.cluster.manager.protocol.port=
+nifi.cluster.manager.authority.provider.port=
+nifi.cluster.manager.authority.provider.threads=10
+nifi.cluster.manager.node.firewall.file=
+nifi.cluster.manager.node.event.history.size=10
+nifi.cluster.manager.node.api.connection.timeout=30 sec
+nifi.cluster.manager.node.api.read.timeout=30 sec
+nifi.cluster.manager.node.api.request.threads=10
+nifi.cluster.manager.flow.retrieval.delay=5 sec
+nifi.cluster.manager.protocol.threads=10
+nifi.cluster.manager.safemode.duration=0 sec
+
+# analytics properties #
+nifi.analytics.predict.interval=3 mins
+nifi.analytics.connection.model.implementation=org.apache.nifi.controller.status.analytics.models.OrdinaryLeastSquares
+nifi.analytics.connection.model.score.name=rSquared
+nifi.analytics.connection.model.score.threshold=.9
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
index e2d2755..8435c98 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/pom.xml
@@ -57,8 +57,9 @@ language governing permissions and limitations under the License. -->
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.quartz-scheduler</groupId>
-            <artifactId>quartz</artifactId>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-expression-language</artifactId>
+            <version>1.13.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/AbstractValidationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/AbstractValidationContext.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/AbstractValidationContext.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/AbstractValidationContext.java
index 9a00837..d49c10b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/AbstractValidationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/AbstractValidationContext.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.nifi;
+package org.apache.nifi.components.validation;
 
 import org.apache.nifi.components.PropertyDependency;
 import org.apache.nifi.components.PropertyDescriptor;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
index 024466d..f20ce10 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
@@ -749,13 +749,21 @@ public abstract class AbstractComponentNode implements ComponentNode {
         }
         final BundleCoordinate controllerServiceApiCoordinate = controllerServiceApiBundle.getBundleDetails().getCoordinate();
 
-        final Bundle controllerServiceBundle = extensionManager.getBundle(controllerServiceNode.getBundleCoordinate());
+        Bundle controllerServiceBundle = extensionManager.getBundle(controllerServiceNode.getBundleCoordinate());
+        final boolean matchesApiByBundleCoordinates;
         if (controllerServiceBundle == null) {
-            return createInvalidResult(serviceId, propertyName, "Unable to find bundle for coordinate " + controllerServiceNode.getBundleCoordinate());
+            final List<Bundle> possibleBundles = extensionManager.getBundles(controllerServiceNode.getControllerServiceImplementation().getClass().getName());
+            if (possibleBundles.size() != 1) {
+                return createInvalidResult(serviceId, propertyName, "Unable to find bundle for coordinate " + controllerServiceNode.getBundleCoordinate());
+            }
+
+            controllerServiceBundle = possibleBundles.get(0);
+            matchesApiByBundleCoordinates = false;
+        } else {
+            matchesApiByBundleCoordinates = matchesApiBundleCoordinates(extensionManager, controllerServiceBundle, controllerServiceApiCoordinate);
         }
-        final BundleCoordinate controllerServiceCoordinate = controllerServiceBundle.getBundleDetails().getCoordinate();
 
-        final boolean matchesApiByBundleCoordinates = matchesApiBundleCoordinates(extensionManager, controllerServiceBundle, controllerServiceApiCoordinate);
+        final BundleCoordinate controllerServiceCoordinate = controllerServiceBundle.getBundleDetails().getCoordinate();
         if (!matchesApiByBundleCoordinates) {
             final Class<? extends ControllerService> controllerServiceImplClass = controllerServiceNode.getControllerServiceImplementation().getClass();
             logger.debug("Comparing methods from service api '{}' against service implementation '{}'",
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
index e680dcd..b1e474d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
@@ -29,6 +29,7 @@ import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.ProcessSessionFactory;
+import org.apache.nifi.processor.exception.TerminatedTaskException;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 
 public interface ProcessScheduler {
@@ -234,4 +235,11 @@ public interface ProcessScheduler {
      * @param service to disable
      */
     CompletableFuture<Void> disableControllerService(ControllerServiceNode service);
+
+    /**
+     * Submits the given task to be executed exactly once in a background thread
+     *
+     * @param task the task to perform
+     */
+    Future<?> submitFrameworkTask(Runnable task);
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/StandardFunnel.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/StandardFunnel.java
index 9084a6a..264b75a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/StandardFunnel.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/StandardFunnel.java
@@ -37,7 +37,6 @@ import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.util.FormatUtils;
-import org.apache.nifi.util.NiFiProperties;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -60,14 +59,6 @@ public class StandardFunnel implements Funnel {
 
     public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
 
-    // "_nifi.funnel.max.concurrent.tasks" is an experimental NiFi property allowing users to configure
-    // the number of concurrent tasks to schedule for local ports and funnels.
-    static final String MAX_CONCURRENT_TASKS_PROP_NAME = "_nifi.funnel.max.concurrent.tasks";
-
-    // "_nifi.funnel.max.transferred.flowfiles" is an experimental NiFi property allowing users to configure
-    // the maximum number of FlowFiles transferred each time a funnel or local port runs (rounded up to the nearest 1000).
-    static final String MAX_TRANSFERRED_FLOWFILES_PROP_NAME = "_nifi.funnel.max.transferred.flowfiles";
-
     private final String identifier;
     private final Set<Connection> outgoingConnections;
     private final List<Connection> incomingConnections;
@@ -92,7 +83,7 @@ public class StandardFunnel implements Funnel {
     final int maxIterations;
     private final int maxConcurrentTasks;
 
-    public StandardFunnel(final String identifier, final NiFiProperties nifiProperties) {
+    public StandardFunnel(final String identifier, final int maxConcurrentTasks, final int maxBatchSize) {
         this.identifier = identifier;
         this.processGroupRef = new AtomicReference<>();
 
@@ -113,9 +104,8 @@ public class StandardFunnel implements Funnel {
         schedulingNanos = new AtomicLong(MINIMUM_SCHEDULING_NANOS);
         name = new AtomicReference<>("Funnel");
 
-        maxConcurrentTasks = Integer.parseInt(nifiProperties.getProperty(MAX_CONCURRENT_TASKS_PROP_NAME, "1"));
-        int maxTransferredFlowFiles = Integer.parseInt(nifiProperties.getProperty(MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "10000"));
-        maxIterations = Math.max(1, (int) Math.ceil(maxTransferredFlowFiles / 1000.0));
+        this.maxConcurrentTasks = maxConcurrentTasks;
+        this.maxIterations = Math.max(1, (int) Math.ceil(maxBatchSize / 1000.0));
     }
 
     @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceProvider.java
index 15033b9..f610349 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceProvider.java
@@ -85,6 +85,13 @@ public interface ControllerServiceProvider extends ControllerServiceLookup {
     Future<Void> enableControllerServicesAsync(Collection<ControllerServiceNode> serviceNodes);
 
     /**
+     * Enables the given Controller Service and any dependencies that it has
+     * @param serviceNode the Controller Service to enable
+     * @return a Future that can be used to cancel the task or wait until it is completed
+     */
+    Future<Void> enableControllerServiceAndDependencies(ControllerServiceNode serviceNode);
+
+    /**
      * Disables the given controller service so that it cannot be used by other
      * components. This allows configuration to be updated or allows service to
      * be removed.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/engine/FlowEngine.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/engine/FlowEngine.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/engine/FlowEngine.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/engine/FlowEngine.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
index d724c46..dc37634 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
@@ -47,7 +47,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 import java.util.function.Predicate;
 
 /**
@@ -203,7 +203,7 @@ public interface ProcessGroup extends ComponentAuthorizable, Positionable, Versi
      * @throws IllegalStateException if the processor is not valid, or is
      *             already running
      */
-    CompletableFuture<Void> startProcessor(ProcessorNode processor, boolean failIfStopping);
+    Future<Void> startProcessor(ProcessorNode processor, boolean failIfStopping);
 
     /**
      * Starts the given Input Port
@@ -231,7 +231,7 @@ public interface ProcessGroup extends ComponentAuthorizable, Positionable, Versi
      *
      * @param processor to stop
      */
-    CompletableFuture<Void> stopProcessor(ProcessorNode processor);
+    Future<Void> stopProcessor(ProcessorNode processor);
 
     /**
      * Terminates the given Processor
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/provenance/InternalProvenanceReporter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/provenance/InternalProvenanceReporter.java
new file mode 100644
index 0000000..9bb45bc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/provenance/InternalProvenanceReporter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.provenance;
+
+import org.apache.nifi.flowfile.FlowFile;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * An extension of the ProvenanceReporter that provides methods that are meant to be used only internally by the framework
+ * and not by the extensions that have access to the Provenance Reporter.
+ */
+public interface InternalProvenanceReporter extends ProvenanceReporter {
+    ProvenanceEventRecord generateDropEvent(FlowFile flowFile, String explanation);
+
+    void clone(FlowFile parent, FlowFile child, boolean verifyFlowFile);
+
+    ProvenanceEventRecord generateJoinEvent(Collection<FlowFile> parents, FlowFile child);
+
+    void remove(ProvenanceEventRecord event);
+
+    void clear();
+
+    void migrate(InternalProvenanceReporter newOwner, Collection<String> flowFileIds);
+
+    void receiveMigration(Set<ProvenanceEventRecord> events);
+
+    Set<ProvenanceEventRecord> getEvents();
+
+    ProvenanceEventBuilder build(FlowFile flowFile, ProvenanceEventType eventType);
+
+    ProvenanceEventRecord drop(FlowFile flowFile, String explanation);
+
+    void expire(FlowFile flowFile, String details);
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestStandardFunnel.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestStandardFunnel.java
index 5daa08b..799eb20 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestStandardFunnel.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestStandardFunnel.java
@@ -17,61 +17,42 @@
 
 package org.apache.nifi.controller;
 
-import org.apache.nifi.util.NiFiProperties;
 import org.junit.Test;
 
-import java.util.HashMap;
-
 import static org.junit.Assert.assertEquals;
 
 public class TestStandardFunnel {
-    @Test
-    public void testDefaultValues() {
-        StandardFunnel funnel = getStandardFunnel("", "");
-        assertEquals(1, funnel.getMaxConcurrentTasks());
-        assertEquals(10, funnel.maxIterations);
-    }
-
-    @Test
-    public void testSetConcurrentTasks() {
-        StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_CONCURRENT_TASKS_PROP_NAME, "2");
-        assertEquals(2, funnel.getMaxConcurrentTasks());
-        assertEquals(10, funnel.maxIterations);
-    }
 
     @Test
     public void testSetFlowFileLimit() {
         {
-            StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "100000");
+            StandardFunnel funnel = getStandardFunnel(1, 100000);
             assertEquals(1, funnel.getMaxConcurrentTasks());
             assertEquals(100, funnel.maxIterations);
         }
         {
-            StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "100001");
+            StandardFunnel funnel = getStandardFunnel(1, 100001);
             assertEquals(1, funnel.getMaxConcurrentTasks());
             assertEquals(101, funnel.maxIterations);
         }
         {
-            StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "99999");
+            StandardFunnel funnel = getStandardFunnel(1, 99999);
             assertEquals(1, funnel.getMaxConcurrentTasks());
             assertEquals(100, funnel.maxIterations);
         }
         {
-            StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "0");
+            StandardFunnel funnel = getStandardFunnel(1, 0);
             assertEquals(1, funnel.getMaxConcurrentTasks());
             assertEquals(1, funnel.maxIterations);
         }
         {
-            StandardFunnel funnel = getStandardFunnel(StandardFunnel.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "1");
+            StandardFunnel funnel = getStandardFunnel(1, 1);
             assertEquals(1, funnel.getMaxConcurrentTasks());
             assertEquals(1, funnel.maxIterations);
         }
     }
 
-    private StandardFunnel getStandardFunnel(String name, String value) {
-        HashMap<String, String> additionalProperties = new HashMap<>();
-        additionalProperties.put(name, value);
-        NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties);
-        return new StandardFunnel("1", niFiProperties);
+    private StandardFunnel getStandardFunnel(final int maxConcurrentTasks, final int maxBatchSize) {
+        return new StandardFunnel("1", maxConcurrentTasks, maxBatchSize);
     }
 }
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index 86fcd42..d914af6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -103,8 +103,8 @@
             <artifactId>nifi-framework-authorization</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.quartz-scheduler</groupId>
-            <artifactId>quartz</artifactId>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-administration</artifactId>
         </dependency>
         <dependency>
             <groupId>com.h2database</groupId>
@@ -211,11 +211,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.jasypt</groupId>
-            <artifactId>jasypt</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-mock</artifactId>
             <version>1.13.0-SNAPSHOT</version>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 9debcf2..7cde926 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -525,7 +525,8 @@ public class FlowController implements ReportingTaskProvider, Authorizable, Node
         repositoryContextFactory = new RepositoryContextFactory(contentRepository, flowFileRepository, flowFileEventRepository, counterRepositoryRef.get(), provenanceRepository);
         flowManager = new StandardFlowManager(nifiProperties, sslContext, this, flowFileEventRepository, parameterContextManager);
 
-        controllerServiceProvider = new StandardControllerServiceProvider(this, processScheduler, bulletinRepository);
+        controllerServiceProvider = new StandardControllerServiceProvider(processScheduler, bulletinRepository, flowManager, extensionManager);
+        flowManager.initialize(controllerServiceProvider);
 
         eventDrivenSchedulingAgent = new EventDrivenSchedulingAgent(
                 eventDrivenEngineRef.get(), controllerServiceProvider, stateManagerProvider, eventDrivenWorkerQueue,
@@ -568,7 +569,7 @@ public class FlowController implements ReportingTaskProvider, Authorizable, Node
         this.reloadComponent = new StandardReloadComponent(this);
 
         final ProcessGroup rootGroup = new StandardProcessGroup(ComponentIdGenerator.generateId().toString(), controllerServiceProvider, processScheduler,
-                nifiProperties, encryptor, this, new MutableVariableRegistry(this.variableRegistry));
+                encryptor, extensionManager, stateManagerProvider, flowManager, flowRegistryClient, reloadComponent, new MutableVariableRegistry(this.variableRegistry), this);
         rootGroup.setName(FlowManager.DEFAULT_ROOT_GROUP_NAME);
         setRootGroup(rootGroup);
         instanceId = ComponentIdGenerator.generateId().toString();
@@ -688,7 +689,7 @@ public class FlowController implements ReportingTaskProvider, Authorizable, Node
 
         }
 
-        eventAccess = new StandardEventAccess(this, flowFileEventRepository);
+        eventAccess = new StandardEventAccess(flowManager, flowFileEventRepository, processScheduler, authorizer, provenanceRepository, auditService, analyticsEngine);
 
         timerDrivenEngineRef.get().scheduleWithFixedDelay(new Runnable() {
             @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 5c42de2..85c20ea 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -20,6 +20,7 @@ import org.apache.nifi.annotation.lifecycle.OnAdded;
 import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored;
 import org.apache.nifi.annotation.lifecycle.OnRemoved;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.bundle.BundleCoordinate;
@@ -35,6 +36,7 @@ import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.ExtensionBuilder;
 import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.FlowSnippet;
+import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.ReportingTaskNode;
 import org.apache.nifi.controller.StandardFlowSnippet;
@@ -61,21 +63,15 @@ import org.apache.nifi.logging.ProcessorLogObserver;
 import org.apache.nifi.logging.ReportingTaskLogObserver;
 import org.apache.nifi.nar.ExtensionManager;
 import org.apache.nifi.nar.NarCloseable;
-import org.apache.nifi.parameter.Parameter;
-import org.apache.nifi.parameter.ParameterContext;
 import org.apache.nifi.parameter.ParameterContextManager;
-import org.apache.nifi.parameter.ParameterReferenceManager;
-import org.apache.nifi.parameter.StandardParameterContext;
-import org.apache.nifi.parameter.StandardParameterReferenceManager;
 import org.apache.nifi.registry.VariableRegistry;
-import org.apache.nifi.registry.flow.FlowRegistryClient;
 import org.apache.nifi.registry.variable.MutableVariableRegistry;
 import org.apache.nifi.remote.PublicPort;
-import org.apache.nifi.remote.RemoteGroupPort;
 import org.apache.nifi.remote.StandardPublicPort;
 import org.apache.nifi.remote.StandardRemoteProcessGroup;
 import org.apache.nifi.remote.TransferDirection;
 import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.ReflectionUtils;
 import org.apache.nifi.web.api.dto.FlowSnippetDTO;
@@ -85,21 +81,22 @@ import org.slf4j.LoggerFactory;
 import javax.net.ssl.SSLContext;
 import java.net.URL;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 
 import static java.util.Objects.requireNonNull;
 
-public class StandardFlowManager implements FlowManager {
+public class StandardFlowManager extends AbstractFlowManager implements FlowManager {
+    static final String MAX_CONCURRENT_TASKS_PROP_NAME = "_nifi.funnel.max.concurrent.tasks";
+    static final String MAX_TRANSFERRED_FLOWFILES_PROP_NAME = "_nifi.funnel.max.transferred.flowfiles";
+
     private static final Logger logger = LoggerFactory.getLogger(StandardFlowManager.class);
 
     private final NiFiProperties nifiProperties;
@@ -108,31 +105,20 @@ public class StandardFlowManager implements FlowManager {
     private final Authorizer authorizer;
     private final SSLContext sslContext;
     private final FlowController flowController;
-    private final FlowFileEventRepository flowFileEventRepository;
-    private final ParameterContextManager parameterContextManager;
-
-    private final boolean isSiteToSiteSecure;
 
-    private volatile ProcessGroup rootGroup;
-    private final ConcurrentMap<String, ProcessGroup> allProcessGroups = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, ProcessorNode> allProcessors = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, ReportingTaskNode> allReportingTasks = new ConcurrentHashMap<>();
     private final ConcurrentMap<String, ControllerServiceNode> rootControllerServices = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, Connection> allConnections = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, Port> allInputPorts = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, Port> allOutputPorts = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, Funnel> allFunnels = new ConcurrentHashMap<>();
+
+    private final boolean isSiteToSiteSecure;
 
     public StandardFlowManager(final NiFiProperties nifiProperties, final SSLContext sslContext, final FlowController flowController,
                                final FlowFileEventRepository flowFileEventRepository, final ParameterContextManager parameterContextManager) {
+        super(flowFileEventRepository, parameterContextManager, flowController.getFlowRegistryClient(), flowController::isInitialized);
         this.nifiProperties = nifiProperties;
         this.flowController = flowController;
         this.bulletinRepository = flowController.getBulletinRepository();
         this.processScheduler = flowController.getProcessScheduler();
         this.authorizer = flowController.getAuthorizer();
         this.sslContext = sslContext;
-        this.flowFileEventRepository = flowFileEventRepository;
-        this.parameterContextManager = parameterContextManager;
 
         this.isSiteToSiteSecure = Boolean.TRUE.equals(nifiProperties.isSiteToSiteSecure());
     }
@@ -213,40 +199,12 @@ public class StandardFlowManager implements FlowManager {
     }
 
     public RemoteProcessGroup createRemoteProcessGroup(final String id, final String uris) {
-        return new StandardRemoteProcessGroup(requireNonNull(id), uris, null,
-            processScheduler, bulletinRepository, sslContext, nifiProperties,
-            flowController.getStateManagerProvider().getStateManager(id));
-    }
+        final String expirationPeriod = nifiProperties.getProperty(NiFiProperties.REMOTE_CONTENTS_CACHE_EXPIRATION, "30 secs");
+        final long remoteContentsCacheExpirationMillis = FormatUtils.getTimeDuration(expirationPeriod, TimeUnit.MILLISECONDS);
 
-    public void setRootGroup(final ProcessGroup rootGroup) {
-        if (this.rootGroup != null && this.rootGroup.isEmpty()) {
-            allProcessGroups.remove(this.rootGroup.getIdentifier());
-        }
-
-        this.rootGroup = rootGroup;
-        allProcessGroups.put(ROOT_GROUP_ID_ALIAS, rootGroup);
-        allProcessGroups.put(rootGroup.getIdentifier(), rootGroup);
-    }
-
-    public ProcessGroup getRootGroup() {
-        return rootGroup;
-    }
-
-    @Override
-    public String getRootGroupId() {
-        return rootGroup.getIdentifier();
-    }
-
-    public boolean areGroupsSame(final String id1, final String id2) {
-        if (id1 == null || id2 == null) {
-            return false;
-        } else if (id1.equals(id2)) {
-            return true;
-        } else {
-            final String comparable1 = id1.equals(ROOT_GROUP_ID_ALIAS) ? getRootGroupId() : id1;
-            final String comparable2 = id2.equals(ROOT_GROUP_ID_ALIAS) ? getRootGroupId() : id2;
-            return comparable1.equals(comparable2);
-        }
+        return new StandardRemoteProcessGroup(requireNonNull(id), uris, null,
+            processScheduler, bulletinRepository, sslContext,
+            flowController.getStateManagerProvider().getStateManager(id), remoteContentsCacheExpirationMillis);
     }
 
     private void verifyPortIdDoesNotExist(final String id) {
@@ -266,29 +224,43 @@ public class StandardFlowManager implements FlowManager {
     }
 
     public Funnel createFunnel(final String id) {
-        return new StandardFunnel(id.intern(), nifiProperties);
+        final int maxConcurrentTasks = Integer.parseInt(nifiProperties.getProperty(MAX_CONCURRENT_TASKS_PROP_NAME, "1"));
+        final int maxBatchSize = Integer.parseInt(nifiProperties.getProperty(MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "10000"));
+
+        return new StandardFunnel(id.intern(), maxConcurrentTasks, maxBatchSize);
     }
 
     public Port createLocalInputPort(String id, String name) {
         id = requireNonNull(id).intern();
         name = requireNonNull(name).intern();
         verifyPortIdDoesNotExist(id);
-        return new LocalPort(id, name, ConnectableType.INPUT_PORT, processScheduler, nifiProperties);
+
+        final int maxConcurrentTasks = Integer.parseInt(nifiProperties.getProperty(MAX_CONCURRENT_TASKS_PROP_NAME, "1"));
+        final int maxTransferredFlowFiles = Integer.parseInt(nifiProperties.getProperty(MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "10000"));
+        final String boredYieldDuration = nifiProperties.getBoredYieldDuration();
+
+        return new LocalPort(id, name, ConnectableType.INPUT_PORT, processScheduler, maxConcurrentTasks, maxTransferredFlowFiles, boredYieldDuration);
     }
 
     public Port createLocalOutputPort(String id, String name) {
         id = requireNonNull(id).intern();
         name = requireNonNull(name).intern();
         verifyPortIdDoesNotExist(id);
-        return new LocalPort(id, name, ConnectableType.OUTPUT_PORT, processScheduler, nifiProperties);
+
+        final int maxConcurrentTasks = Integer.parseInt(nifiProperties.getProperty(MAX_CONCURRENT_TASKS_PROP_NAME, "1"));
+        final int maxTransferredFlowFiles = Integer.parseInt(nifiProperties.getProperty(MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "10000"));
+        final String boredYieldDuration = nifiProperties.getBoredYieldDuration();
+
+        return new LocalPort(id, name, ConnectableType.OUTPUT_PORT, processScheduler, maxConcurrentTasks, maxTransferredFlowFiles, boredYieldDuration);
     }
 
     public ProcessGroup createProcessGroup(final String id) {
         final MutableVariableRegistry mutableVariableRegistry = new MutableVariableRegistry(flowController.getVariableRegistry());
 
-        final ProcessGroup group = new StandardProcessGroup(requireNonNull(id), flowController.getControllerServiceProvider(), processScheduler, nifiProperties, flowController.getEncryptor(),
-            flowController, mutableVariableRegistry);
-        allProcessGroups.put(group.getIdentifier(), group);
+        final ProcessGroup group = new StandardProcessGroup(requireNonNull(id), flowController.getControllerServiceProvider(), processScheduler, flowController.getEncryptor(),
+            flowController.getExtensionManager(), flowController.getStateManagerProvider(), this, flowController.getFlowRegistryClient(),
+            flowController.getReloadComponent(), mutableVariableRegistry, flowController);
+        onProcessGroupAdded(group);
 
         return group;
     }
@@ -334,26 +306,6 @@ public class StandardFlowManager implements FlowManager {
         }
     }
 
-    public ProcessGroup getGroup(final String id) {
-        return allProcessGroups.get(requireNonNull(id));
-    }
-
-    public void onProcessGroupAdded(final ProcessGroup group) {
-        allProcessGroups.put(group.getIdentifier(), group);
-    }
-
-    public void onProcessGroupRemoved(final ProcessGroup group) {
-        allProcessGroups.remove(group.getIdentifier());
-    }
-
-    public ProcessorNode createProcessor(final String type, final String id, final BundleCoordinate coordinate) {
-        return createProcessor(type, id, coordinate, true);
-    }
-
-    public ProcessorNode createProcessor(final String type, String id, final BundleCoordinate coordinate, final boolean firstTimeAdded) {
-        return createProcessor(type, id, coordinate, Collections.emptySet(), firstTimeAdded, true);
-    }
-
     public ProcessorNode createProcessor(final String type, String id, final BundleCoordinate coordinate, final Set<URL> additionalUrls,
                                          final boolean firstTimeAdded, final boolean registerLogObserver) {
 
@@ -402,130 +354,10 @@ public class StandardFlowManager implements FlowManager {
         return procNode;
     }
 
-    public void onProcessorAdded(final ProcessorNode procNode) {
-        allProcessors.put(procNode.getIdentifier(), procNode);
-    }
-
-    public void onProcessorRemoved(final ProcessorNode procNode) {
-        String identifier = procNode.getIdentifier();
-        flowFileEventRepository.purgeTransferEvents(identifier);
-        allProcessors.remove(identifier);
-    }
-
-    public Connectable findConnectable(final String id) {
-        final ProcessorNode procNode = getProcessorNode(id);
-        if (procNode != null) {
-            return procNode;
-        }
-
-        final Port inPort = getInputPort(id);
-        if (inPort != null) {
-            return inPort;
-        }
-
-        final Port outPort = getOutputPort(id);
-        if (outPort != null) {
-            return outPort;
-        }
-
-        final Funnel funnel = getFunnel(id);
-        if (funnel != null) {
-            return funnel;
-        }
-
-        final RemoteGroupPort remoteGroupPort = getRootGroup().findRemoteGroupPort(id);
-        if (remoteGroupPort != null) {
-            return remoteGroupPort;
-        }
-
-        return null;
-    }
-
-    public ProcessorNode getProcessorNode(final String id) {
-        return allProcessors.get(id);
-    }
-
-    public void onConnectionAdded(final Connection connection) {
-        allConnections.put(connection.getIdentifier(), connection);
-
-        if (flowController.isInitialized()) {
-            connection.getFlowFileQueue().startLoadBalancing();
-        }
-    }
-
-    public void onConnectionRemoved(final Connection connection) {
-        String identifier = connection.getIdentifier();
-        flowFileEventRepository.purgeTransferEvents(identifier);
-        allConnections.remove(identifier);
-    }
-
-    public Connection getConnection(final String id) {
-        return allConnections.get(id);
-    }
-
     public Connection createConnection(final String id, final String name, final Connectable source, final Connectable destination, final Collection<String> relationshipNames) {
         return flowController.createConnection(id, name, source, destination, relationshipNames);
     }
 
-    public Set<Connection> findAllConnections() {
-        return new HashSet<>(allConnections.values());
-    }
-
-    public void onInputPortAdded(final Port inputPort) {
-        allInputPorts.put(inputPort.getIdentifier(), inputPort);
-    }
-
-    public void onInputPortRemoved(final Port inputPort) {
-        String identifier = inputPort.getIdentifier();
-        flowFileEventRepository.purgeTransferEvents(identifier);
-        allInputPorts.remove(identifier);
-    }
-
-    public Port getInputPort(final String id) {
-        return allInputPorts.get(id);
-    }
-
-    public void onOutputPortAdded(final Port outputPort) {
-        allOutputPorts.put(outputPort.getIdentifier(), outputPort);
-    }
-
-    public void onOutputPortRemoved(final Port outputPort) {
-        String identifier = outputPort.getIdentifier();
-        flowFileEventRepository.purgeTransferEvents(identifier);
-        allOutputPorts.remove(identifier);
-    }
-
-    public Port getOutputPort(final String id) {
-        return allOutputPorts.get(id);
-    }
-
-    public void onFunnelAdded(final Funnel funnel) {
-        allFunnels.put(funnel.getIdentifier(), funnel);
-    }
-
-    public void onFunnelRemoved(final Funnel funnel) {
-        String identifier = funnel.getIdentifier();
-        flowFileEventRepository.purgeTransferEvents(identifier);
-        allFunnels.remove(identifier);
-    }
-
-    public Funnel getFunnel(final String id) {
-        return allFunnels.get(id);
-    }
-
-    public ReportingTaskNode createReportingTask(final String type, final BundleCoordinate bundleCoordinate) {
-        return createReportingTask(type, bundleCoordinate, true);
-    }
-
-    public ReportingTaskNode createReportingTask(final String type, final BundleCoordinate bundleCoordinate, final boolean firstTimeAdded) {
-        return createReportingTask(type, UUID.randomUUID().toString(), bundleCoordinate, firstTimeAdded);
-    }
-
-    @Override
-    public ReportingTaskNode createReportingTask(final String type, final String id, final BundleCoordinate bundleCoordinate, final boolean firstTimeAdded) {
-        return createReportingTask(type, id, bundleCoordinate, Collections.emptySet(), firstTimeAdded, true);
-    }
-
     public ReportingTaskNode createReportingTask(final String type, final String id, final BundleCoordinate bundleCoordinate, final Set<URL> additionalUrls,
                                                  final boolean firstTimeAdded, final boolean register) {
         if (type == null || id == null || bundleCoordinate == null) {
@@ -571,7 +403,7 @@ public class StandardFlowManager implements FlowManager {
         }
 
         if (register) {
-            allReportingTasks.put(id, taskNode);
+            onReportingTaskAdded(taskNode);
 
             // Register log observer to provide bulletins when reporting task logs anything at WARN level or above
             logRepository.addObserver(StandardProcessorNode.BULLETIN_OBSERVER_ID, LogLevel.WARN,
@@ -581,49 +413,6 @@ public class StandardFlowManager implements FlowManager {
         return taskNode;
     }
 
-    public ReportingTaskNode getReportingTaskNode(final String taskId) {
-        return allReportingTasks.get(taskId);
-    }
-
-    @Override
-    public void removeReportingTask(final ReportingTaskNode reportingTaskNode) {
-        final ReportingTaskNode existing = allReportingTasks.get(reportingTaskNode.getIdentifier());
-        if (existing == null || existing != reportingTaskNode) {
-            throw new IllegalStateException("Reporting Task " + reportingTaskNode + " does not exist in this Flow");
-        }
-
-        reportingTaskNode.verifyCanDelete();
-
-        final Class<?> taskClass = reportingTaskNode.getReportingTask().getClass();
-        try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(), taskClass, reportingTaskNode.getReportingTask().getIdentifier())) {
-            ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnRemoved.class, reportingTaskNode.getReportingTask(), reportingTaskNode.getConfigurationContext());
-        }
-
-        for (final Map.Entry<PropertyDescriptor, String> entry : reportingTaskNode.getEffectivePropertyValues().entrySet()) {
-            final PropertyDescriptor descriptor = entry.getKey();
-            if (descriptor.getControllerServiceDefinition() != null) {
-                final String value = entry.getValue() == null ? descriptor.getDefaultValue() : entry.getValue();
-                if (value != null) {
-                    final ControllerServiceNode serviceNode = flowController.getControllerServiceProvider().getControllerServiceNode(value);
-                    if (serviceNode != null) {
-                        serviceNode.removeReference(reportingTaskNode, descriptor);
-                    }
-                }
-            }
-        }
-
-        allReportingTasks.remove(reportingTaskNode.getIdentifier());
-        LogRepositoryFactory.removeRepository(reportingTaskNode.getIdentifier());
-        processScheduler.onReportingTaskRemoved(reportingTaskNode);
-
-        flowController.getExtensionManager().removeInstanceClassLoader(reportingTaskNode.getIdentifier());
-    }
-
-    @Override
-    public Set<ReportingTaskNode> getAllReportingTasks() {
-        return new HashSet<>(allReportingTasks.values());
-    }
-
     public Set<ControllerServiceNode> getRootControllerServices() {
         return new HashSet<>(rootControllerServices.values());
     }
@@ -727,124 +516,20 @@ public class StandardFlowManager implements FlowManager {
         return serviceNode;
     }
 
-    public Set<ControllerServiceNode> getAllControllerServices() {
-        final Set<ControllerServiceNode> allServiceNodes = new HashSet<>();
-        allServiceNodes.addAll(flowController.getControllerServiceProvider().getNonRootControllerServices());
-        allServiceNodes.addAll(rootControllerServices.values());
-        return allServiceNodes;
-    }
-
-    public ControllerServiceNode getControllerServiceNode(final String id) {
-        return flowController.getControllerServiceProvider().getControllerServiceNode(id);
-    }
-
     @Override
-    public ParameterContextManager getParameterContextManager() {
-        return parameterContextManager;
-    }
-
-    @Override
-    public Map<String, Integer> getComponentCounts() {
-        final Map<String, Integer> componentCounts = new LinkedHashMap<>();
-        componentCounts.put("Processors", allProcessors.size());
-        componentCounts.put("Controller Services", getAllControllerServices().size());
-        componentCounts.put("Reporting Tasks", getAllReportingTasks().size());
-        componentCounts.put("Process Groups", allProcessGroups.size() - 2); // -2 to account for the root group because we don't want it in our counts and the 'root group alias' key.
-        componentCounts.put("Remote Process Groups", getRootGroup().findAllRemoteProcessGroups().size());
-
-        int localInputPorts = 0;
-        int publicInputPorts = 0;
-        for (final Port port : allInputPorts.values()) {
-            if (port instanceof PublicPort) {
-                publicInputPorts++;
-            } else {
-                localInputPorts++;
-            }
-        }
-
-        int localOutputPorts = 0;
-        int publicOutputPorts = 0;
-        for (final Port port : allOutputPorts.values()) {
-            if (port instanceof PublicPort) {
-                localOutputPorts++;
-            } else {
-                publicOutputPorts++;
-            }
-        }
-
-        componentCounts.put("Local Input Ports", localInputPorts);
-        componentCounts.put("Local Output Ports", localOutputPorts);
-        componentCounts.put("Public Input Ports", publicInputPorts);
-        componentCounts.put("Public Output Ports", publicOutputPorts);
-
-        return componentCounts;
+    protected ExtensionManager getExtensionManager() {
+        return flowController.getExtensionManager();
     }
 
     @Override
-    public ParameterContext createParameterContext(final String id, final String name, final Map<String, Parameter> parameters) {
-        final boolean namingConflict = parameterContextManager.getParameterContexts().stream()
-            .anyMatch(paramContext -> paramContext.getName().equals(name));
-
-        if (namingConflict) {
-            throw new IllegalStateException("Cannot create Parameter Context with name '" + name + "' because a Parameter Context already exists with that name");
-        }
-
-        final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(this);
-        final ParameterContext parameterContext = new StandardParameterContext(id, name, referenceManager, flowController);
-        parameterContext.setParameters(parameters);
-        parameterContextManager.addParameterContext(parameterContext);
-        return parameterContext;
+    protected ProcessScheduler getProcessScheduler() {
+        return flowController.getProcessScheduler();
     }
 
     @Override
-    public void purge() {
-        verifyCanPurge();
-
-        final ProcessGroup rootGroup = getRootGroup();
-
-        // Delete templates from all levels first. This allows us to avoid having to purge each individual Process Group recursively
-        // and instead just delete all child Process Groups after removing the connections to/from those Process Groups.
-        for (final ProcessGroup group : rootGroup.findAllProcessGroups()) {
-            group.getTemplates().forEach(group::removeTemplate);
-        }
-        rootGroup.getTemplates().forEach(rootGroup::removeTemplate);
-
-        rootGroup.getConnections().forEach(rootGroup::removeConnection);
-        rootGroup.getProcessors().forEach(rootGroup::removeProcessor);
-        rootGroup.getFunnels().forEach(rootGroup::removeFunnel);
-        rootGroup.getInputPorts().forEach(rootGroup::removeInputPort);
-        rootGroup.getOutputPorts().forEach(rootGroup::removeOutputPort);
-        rootGroup.getLabels().forEach(rootGroup::removeLabel);
-        rootGroup.getRemoteProcessGroups().forEach(rootGroup::removeRemoteProcessGroup);
-
-        rootGroup.getProcessGroups().forEach(rootGroup::removeProcessGroup);
-
-        final ControllerServiceProvider serviceProvider = flowController.getControllerServiceProvider();
-        rootGroup.getControllerServices(false).forEach(serviceProvider::removeControllerService);
-
-        getRootControllerServices().forEach(this::removeRootControllerService);
-        getAllReportingTasks().forEach(this::removeReportingTask);
-
-        final FlowRegistryClient registryClient = flowController.getFlowRegistryClient();
-        for (final String registryId : registryClient.getRegistryIdentifiers()) {
-            registryClient.removeFlowRegistry(registryId);
-        }
-
-        for (final ParameterContext parameterContext : parameterContextManager.getParameterContexts()) {
-            parameterContextManager.removeParameterContext(parameterContext.getIdentifier());
-        }
+    protected Authorizable getParameterContextParent() {
+        return flowController;
     }
 
-    private void verifyCanPurge() {
-        for (final ControllerServiceNode serviceNode : getAllControllerServices()) {
-            serviceNode.verifyCanDelete();
-        }
 
-        for (final ReportingTaskNode reportingTask : getAllReportingTasks()) {
-            reportingTask.verifyCanDelete();
-        }
-
-        final ProcessGroup rootGroup = getRootGroup();
-        rootGroup.verifyCanDelete(true, true);
-    }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java
index 6793a4a..ac1d561 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingContext.java
@@ -16,71 +16,33 @@
  */
 package org.apache.nifi.controller.reporting;
 
-import org.apache.nifi.parameter.ParameterLookup;
-import org.apache.nifi.attribute.expression.language.PreparedQuery;
-import org.apache.nifi.attribute.expression.language.Query;
-import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
 import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.components.state.StateManager;
-import org.apache.nifi.connectable.Connectable;
-import org.apache.nifi.controller.ControllerService;
-import org.apache.nifi.controller.ControllerServiceLookup;
 import org.apache.nifi.controller.FlowController;
-import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.events.BulletinFactory;
+import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.parameter.ParameterLookup;
 import org.apache.nifi.registry.VariableRegistry;
-import org.apache.nifi.reporting.Bulletin;
 import org.apache.nifi.reporting.BulletinRepository;
 import org.apache.nifi.reporting.EventAccess;
 import org.apache.nifi.reporting.ReportingContext;
 import org.apache.nifi.reporting.ReportingTask;
-import org.apache.nifi.reporting.Severity;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Set;
 
-public class StandardReportingContext implements ReportingContext, ControllerServiceLookup {
+public class StandardReportingContext extends AbstractReportingContext implements ReportingContext {
 
     private final FlowController flowController;
     private final EventAccess eventAccess;
-    private final ReportingTask reportingTask;
-    private final BulletinRepository bulletinRepository;
-    private final ControllerServiceProvider serviceProvider;
-    private final Map<PropertyDescriptor, String> properties;
-    private final Map<PropertyDescriptor, PreparedQuery> preparedQueries;
-    private final VariableRegistry variableRegistry;
-    private final ParameterLookup parameterLookup;
     private final boolean analyticsEnabled;
 
     public StandardReportingContext(final FlowController flowController, final BulletinRepository bulletinRepository,
                                     final Map<PropertyDescriptor, String> properties, final ReportingTask reportingTask,
                                     final VariableRegistry variableRegistry, final ParameterLookup parameterLookup) {
+        super(reportingTask, bulletinRepository, properties, flowController.getControllerServiceProvider(), parameterLookup, variableRegistry);
         this.flowController = flowController;
         this.eventAccess = flowController.getEventAccess();
-        this.bulletinRepository = bulletinRepository;
-        this.properties = Collections.unmodifiableMap(properties);
-        this.serviceProvider = flowController.getControllerServiceProvider();
-        this.reportingTask = reportingTask;
-        this.variableRegistry = variableRegistry;
-        this.parameterLookup = parameterLookup;
         this.analyticsEnabled = flowController.getStatusAnalyticsEngine() != null;
-        preparedQueries = new HashMap<>();
-
-        for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
-            final PropertyDescriptor desc = entry.getKey();
-            String value = entry.getValue();
-            if (value == null) {
-                value = desc.getDefaultValue();
-            }
-
-            final PreparedQuery pq = Query.prepare(value);
-            preparedQueries.put(desc, pq);
-        }
     }
 
     @Override
@@ -89,87 +51,13 @@ public class StandardReportingContext implements ReportingContext, ControllerSer
     }
 
     @Override
-    public BulletinRepository getBulletinRepository() {
-        return bulletinRepository;
-    }
-
-    @Override
-    public Bulletin createBulletin(final String category, final Severity severity, final String message) {
-        return BulletinFactory.createBulletin(category, severity.name(), message);
-    }
-
-    @Override
-    public Bulletin createBulletin(final String componentId, final String category, final Severity severity, final String message) {
-        final Connectable connectable = flowController.getFlowManager().findConnectable(componentId);
-        if (connectable == null) {
-            throw new IllegalStateException("Cannot create Component-Level Bulletin because no component can be found with ID " + componentId);
-        }
-        return BulletinFactory.createBulletin(connectable, category, severity.name(), message);
-    }
-
-    @Override
-    public Map<PropertyDescriptor, String> getProperties() {
-        return Collections.unmodifiableMap(properties);
-    }
-
-    @Override
-    public Map<String, String> getAllProperties() {
-        final Map<String,String> propValueMap = new LinkedHashMap<>();
-        for (final Map.Entry<PropertyDescriptor, String> entry : getProperties().entrySet()) {
-            propValueMap.put(entry.getKey().getName(), entry.getValue());
-        }
-        return propValueMap;
-    }
-
-    @Override
-    public PropertyValue getProperty(final PropertyDescriptor property) {
-        final PropertyDescriptor descriptor = reportingTask.getPropertyDescriptor(property.getName());
-        if (descriptor == null) {
-            return null;
-        }
-
-        final String configuredValue = properties.get(property);
-        return new StandardPropertyValue(configuredValue == null ? descriptor.getDefaultValue() : configuredValue, this, parameterLookup, preparedQueries.get(property), variableRegistry);
-    }
-
-    @Override
-    public ControllerService getControllerService(final String serviceIdentifier) {
-        return serviceProvider.getControllerService(serviceIdentifier);
-    }
-
-    @Override
-    public Set<String> getControllerServiceIdentifiers(final Class<? extends ControllerService> serviceType) {
-        return serviceProvider.getControllerServiceIdentifiers(serviceType, null);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final ControllerService service) {
-        return serviceProvider.isControllerServiceEnabled(service);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabled(final String serviceIdentifier) {
-        return serviceProvider.isControllerServiceEnabled(serviceIdentifier);
-    }
-
-    @Override
-    public boolean isControllerServiceEnabling(final String serviceIdentifier) {
-        return serviceProvider.isControllerServiceEnabling(serviceIdentifier);
-    }
-
-    @Override
-    public ControllerServiceLookup getControllerServiceLookup() {
-        return this;
-    }
-
-    @Override
-    public String getControllerServiceName(final String serviceIdentifier) {
-        return serviceProvider.getControllerServiceName(serviceIdentifier);
+    public StateManager getStateManager() {
+        return flowController.getStateManagerProvider().getStateManager(getReportingTask().getIdentifier());
     }
 
     @Override
-    public StateManager getStateManager() {
-        return flowController.getStateManagerProvider().getStateManager(reportingTask.getIdentifier());
+    protected FlowManager getFlowManager() {
+        return flowController.getFlowManager();
     }
 
     @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
new file mode 100644
index 0000000..e3c2a79
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/StandardRepositoryContext.java
@@ -0,0 +1,37 @@
+/*
+ * 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.controller.repository;
+
+import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.controller.repository.claim.ContentClaimWriteCache;
+import org.apache.nifi.controller.repository.claim.StandardContentClaimWriteCache;
+import org.apache.nifi.provenance.ProvenanceEventRepository;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class StandardRepositoryContext extends AbstractRepositoryContext implements RepositoryContext {
+
+    public StandardRepositoryContext(final Connectable connectable, final AtomicLong connectionIndex, final ContentRepository contentRepository, final FlowFileRepository flowFileRepository,
+                                     final FlowFileEventRepository flowFileEventRepository, final CounterRepository counterRepository, final ProvenanceEventRepository provenanceRepository) {
+        super(connectable, connectionIndex, contentRepository, flowFileRepository, flowFileEventRepository, counterRepository, provenanceRepository);
+    }
+
+    @Override
+    public ContentClaimWriteCache createContentClaimWriteCache() {
+        return new StandardContentClaimWriteCache(getContentRepository());
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
similarity index 93%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
index bf3a870..8e8e7c6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/ContentClaimWriteCache.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/repository/claim/StandardContentClaimWriteCache.java
@@ -27,21 +27,22 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.nifi.controller.repository.ContentRepository;
 
-public class ContentClaimWriteCache {
+public class StandardContentClaimWriteCache implements ContentClaimWriteCache {
     private final ContentRepository contentRepo;
     private final Map<ResourceClaim, OutputStream> streamMap = new ConcurrentHashMap<>();
     private final Queue<ContentClaim> queue = new LinkedList<>();
     private final int bufferSize;
 
-    public ContentClaimWriteCache(final ContentRepository contentRepo) {
+    public StandardContentClaimWriteCache(final ContentRepository contentRepo) {
         this(contentRepo, 8192);
     }
 
-    public ContentClaimWriteCache(final ContentRepository contentRepo, final int bufferSize) {
+    public StandardContentClaimWriteCache(final ContentRepository contentRepo, final int bufferSize) {
         this.contentRepo = contentRepo;
         this.bufferSize = bufferSize;
     }
 
+    @Override
     public void reset() throws IOException {
         try {
             forEachStream(OutputStream::close);
@@ -51,6 +52,7 @@ public class ContentClaimWriteCache {
         }
     }
 
+    @Override
     public ContentClaim getContentClaim() throws IOException {
         final ContentClaim contentClaim = queue.poll();
         if (contentClaim != null) {
@@ -70,6 +72,7 @@ public class ContentClaimWriteCache {
         return buffered;
     }
 
+    @Override
     public OutputStream write(final ContentClaim claim) throws IOException {
         OutputStream out = streamMap.get(claim.getResourceClaim());
         if (out == null) {
@@ -120,6 +123,7 @@ public class ContentClaimWriteCache {
         };
     }
 
+    @Override
     public void flush(final ContentClaim contentClaim) throws IOException {
         if (contentClaim == null) {
             return;
@@ -128,6 +132,7 @@ public class ContentClaimWriteCache {
         flush(contentClaim.getResourceClaim());
     }
 
+    @Override
     public void flush(final ResourceClaim claim) throws IOException {
         final OutputStream out = streamMap.get(claim);
         if (out != null) {
@@ -135,6 +140,7 @@ public class ContentClaimWriteCache {
         }
     }
 
+    @Override
     public void flush() throws IOException {
         forEachStream(OutputStream::flush);
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/EventDrivenSchedulingAgent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/EventDrivenSchedulingAgent.java
index 87e2762..5e38337 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/EventDrivenSchedulingAgent.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/EventDrivenSchedulingAgent.java
@@ -33,6 +33,7 @@ import org.apache.nifi.controller.repository.StandardProcessSession;
 import org.apache.nifi.controller.repository.StandardProcessSessionFactory;
 import org.apache.nifi.controller.repository.WeakHashMapProcessSessionFactory;
 import org.apache.nifi.controller.repository.metrics.StandardFlowFileEvent;
+import org.apache.nifi.controller.repository.scheduling.ConnectableProcessContext;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.encrypt.StringEncryptor;
 import org.apache.nifi.engine.FlowEngine;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
index ff9df5f..0aca912 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/RepositoryContextFactory.java
@@ -22,6 +22,7 @@ import org.apache.nifi.controller.repository.CounterRepository;
 import org.apache.nifi.controller.repository.FlowFileEventRepository;
 import org.apache.nifi.controller.repository.FlowFileRepository;
 import org.apache.nifi.controller.repository.RepositoryContext;
+import org.apache.nifi.controller.repository.StandardRepositoryContext;
 import org.apache.nifi.provenance.ProvenanceRepository;
 
 import java.util.concurrent.atomic.AtomicLong;
@@ -46,7 +47,7 @@ public class RepositoryContextFactory {
     }
 
     public RepositoryContext newProcessContext(final Connectable connectable, final AtomicLong connectionIndex) {
-        return new RepositoryContext(connectable, connectionIndex, contentRepo, flowFileRepo, flowFileEventRepo, counterRepo, provenanceRepo);
+        return new StandardRepositoryContext(connectable, connectionIndex, contentRepo, flowFileRepo, flowFileEventRepo, counterRepo, provenanceRepo);
     }
 
     public ContentRepository getContentRepository() {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
index 508be54..590683b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
@@ -35,6 +35,7 @@ import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.SchedulingAgentCallback;
 import org.apache.nifi.controller.StandardProcessorNode;
 import org.apache.nifi.controller.exception.ProcessorInstantiationException;
+import org.apache.nifi.controller.repository.scheduling.ConnectableProcessContext;
 import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.encrypt.StringEncryptor;
@@ -231,7 +232,7 @@ public final class StandardProcessScheduler implements ProcessScheduler {
 
                     LOG.error("Failed to invoke the On-Scheduled Lifecycle methods of {} due to {}; administratively yielding this "
                             + "ReportingTask and will attempt to schedule it again after {}",
-                            new Object[]{reportingTask, e.toString(), administrativeYieldDuration}, e);
+                            reportingTask, e.toString(), administrativeYieldDuration, e);
 
 
                     try (final NarCloseable x = NarCloseable.withComponentNarLoader(flowController.getExtensionManager(), reportingTask.getClass(), reportingTask.getIdentifier())) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ConnectableTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ConnectableTask.java
index cd268ea..8fe3a59 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ConnectableTask.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/ConnectableTask.java
@@ -32,7 +32,7 @@ import org.apache.nifi.controller.repository.StandardProcessSession;
 import org.apache.nifi.controller.repository.StandardProcessSessionFactory;
 import org.apache.nifi.controller.repository.WeakHashMapProcessSessionFactory;
 import org.apache.nifi.controller.repository.metrics.StandardFlowFileEvent;
-import org.apache.nifi.controller.scheduling.ConnectableProcessContext;
+import org.apache.nifi.controller.repository.scheduling.ConnectableProcessContext;
 import org.apache.nifi.controller.scheduling.LifecycleState;
 import org.apache.nifi.controller.scheduling.RepositoryContextFactory;
 import org.apache.nifi.controller.scheduling.SchedulingAgent;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java
index 483c776..ddd7943 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/reporting/StandardEventAccess.java
@@ -16,137 +16,62 @@
  */
 package org.apache.nifi.reporting;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.collections4.Predicate;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.action.Action;
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.components.validation.ValidationStatus;
-import org.apache.nifi.connectable.Connectable;
-import org.apache.nifi.connectable.ConnectableType;
-import org.apache.nifi.connectable.Connection;
-import org.apache.nifi.connectable.Funnel;
-import org.apache.nifi.connectable.Port;
-import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ProcessorNode;
-import org.apache.nifi.controller.ScheduledState;
-import org.apache.nifi.controller.queue.QueueSize;
+import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.controller.repository.FlowFileEvent;
 import org.apache.nifi.controller.repository.FlowFileEventRepository;
 import org.apache.nifi.controller.repository.RepositoryStatusReport;
 import org.apache.nifi.controller.repository.metrics.EmptyFlowFileEvent;
-import org.apache.nifi.controller.status.ConnectionStatus;
-import org.apache.nifi.controller.status.PortStatus;
 import org.apache.nifi.controller.status.ProcessGroupStatus;
 import org.apache.nifi.controller.status.ProcessorStatus;
-import org.apache.nifi.controller.status.RemoteProcessGroupStatus;
-import org.apache.nifi.controller.status.RunStatus;
-import org.apache.nifi.controller.status.TransmissionStatus;
-import org.apache.nifi.controller.status.analytics.ConnectionStatusPredictions;
-import org.apache.nifi.controller.status.analytics.StatusAnalytics;
 import org.apache.nifi.controller.status.analytics.StatusAnalyticsEngine;
 import org.apache.nifi.groups.ProcessGroup;
-import org.apache.nifi.groups.RemoteProcessGroup;
 import org.apache.nifi.history.History;
-import org.apache.nifi.processor.Relationship;
-import org.apache.nifi.provenance.ProvenanceEventRecord;
 import org.apache.nifi.provenance.ProvenanceRepository;
-import org.apache.nifi.registry.flow.VersionControlInformation;
-import org.apache.nifi.registry.flow.VersionedFlowState;
-import org.apache.nifi.registry.flow.VersionedFlowStatus;
-import org.apache.nifi.remote.PublicPort;
-import org.apache.nifi.remote.RemoteGroupPort;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-public class StandardEventAccess implements UserAwareEventAccess {
-    private static final Logger logger = LoggerFactory.getLogger(StandardEventAccess.class);
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
 
+public class StandardEventAccess extends AbstractEventAccess implements UserAwareEventAccess {
+    private final FlowManager flowManager;
     private final FlowFileEventRepository flowFileEventRepository;
-    private final FlowController flowController;
-    private final StatusAnalyticsEngine statusAnalyticsEngine;
+    private final Authorizer authorizer;
+    private final ProvenanceRepository provenanceRepository;
+    private final AuditService auditService;
 
-    public StandardEventAccess(final FlowController flowController, final FlowFileEventRepository flowFileEventRepository) {
-        this.flowController = flowController;
+    public StandardEventAccess(final FlowManager flowManager, final FlowFileEventRepository flowFileEventRepository, final ProcessScheduler processScheduler,
+                               final Authorizer authorizer, final ProvenanceRepository provenanceRepository, final AuditService auditService, final StatusAnalyticsEngine statusAnalyticsEngine) {
+        super(processScheduler, statusAnalyticsEngine, flowManager, flowFileEventRepository);
         this.flowFileEventRepository = flowFileEventRepository;
-        this.statusAnalyticsEngine = flowController.getStatusAnalyticsEngine();
+        this.flowManager = flowManager;
+        this.authorizer = authorizer;
+        this.provenanceRepository = provenanceRepository;
+        this.auditService = auditService;
     }
 
-    /**
-     * Returns the status of all components in the controller. This request is
-     * not in the context of a user so the results will be unfiltered.
-     *
-     * @return the component status
-     */
-    @Override
-    public ProcessGroupStatus getControllerStatus() {
-        return getGroupStatus(flowController.getFlowManager().getRootGroupId());
-    }
-
-    /**
-     * Returns the status of all components in the specified group. This request
-     * is not in the context of a user so the results will be unfiltered.
-     *
-     * @param groupId group id
-     * @return the component status
-     */
-    @Override
-    public ProcessGroupStatus getGroupStatus(final String groupId) {
-        final RepositoryStatusReport repoStatusReport = generateRepositoryStatusReport();
-        return getGroupStatus(groupId, repoStatusReport);
-    }
-
-    /**
-     * Returns the status for the components in the specified group with the
-     * specified report. This request is not in the context of a user so the
-     * results will be unfiltered.
-     *
-     * @param groupId group id
-     * @param statusReport report
-     * @return the component status
-     */
-    public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
-
-        // this was invoked with no user context so the results will be unfiltered... necessary for aggregating status history
-        return getGroupStatus(group, statusReport, authorizable -> true, Integer.MAX_VALUE, 1);
-    }
-
-
-    @Override
-    public List<ProvenanceEventRecord> getProvenanceEvents(final long firstEventId, final int maxRecords) throws IOException {
-        return new ArrayList<>(getProvenanceRepository().getEvents(firstEventId, maxRecords));
-    }
 
     @Override
     public List<Action> getFlowChanges(final int firstActionId, final int maxActions) {
-        final History history = flowController.getAuditService().getActions(firstActionId, maxActions);
+        final History history = auditService.getActions(firstActionId, maxActions);
         return new ArrayList<>(history.getActions());
     }
 
     @Override
     public ProvenanceRepository getProvenanceRepository() {
-        return flowController.getProvenanceRepository();
-    }
-
-
-    private RepositoryStatusReport generateRepositoryStatusReport() {
-        return flowFileEventRepository.reportTransferEvents(System.currentTimeMillis());
+        return provenanceRepository;
     }
 
     @Override
     public ProcessorStatus getProcessorStatus(final String processorId, final NiFiUser user) {
-        final ProcessorNode procNode = flowController.getFlowManager().getProcessorNode(processorId);
+        final ProcessorNode procNode = flowManager.getProcessorNode(processorId);
         if (procNode == null) {
             return null;
         }
@@ -156,7 +81,7 @@ public class StandardEventAccess implements UserAwareEventAccess {
             flowFileEvent = EmptyFlowFileEvent.INSTANCE;
         }
 
-        final Predicate<Authorizable> authorizer = authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user);
+        final Predicate<Authorizable> authorizer = authorizable -> authorizable.isAuthorized(this.authorizer, RequestAction.READ, user);
         return getProcessorStatus(flowFileEvent, procNode, authorizer);
     }
 
@@ -185,10 +110,10 @@ public class StandardEventAccess implements UserAwareEventAccess {
      * @return the component status
      */
     public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport, final NiFiUser user) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
+        final ProcessGroup group = flowManager.getGroup(groupId);
 
         // on demand status request for a specific user... require authorization per component and filter results as appropriate
-        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user), Integer.MAX_VALUE, 1);
+        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(this.authorizer, RequestAction.READ, user), Integer.MAX_VALUE, 1);
     }
 
     /**
@@ -204,8 +129,6 @@ public class StandardEventAccess implements UserAwareEventAccess {
         return getGroupStatus(groupId, repoStatusReport, user);
     }
 
-
-
     /**
      * Returns the status for the components in the specified group with the
      * specified report. This request is made by the specified user so the
@@ -218,570 +141,9 @@ public class StandardEventAccess implements UserAwareEventAccess {
      * @return the component status
      */
     public ProcessGroupStatus getGroupStatus(final String groupId, final RepositoryStatusReport statusReport, final NiFiUser user, final int recursiveStatusDepth) {
-        final ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
+        final ProcessGroup group = flowManager.getGroup(groupId);
 
         // on demand status request for a specific user... require authorization per component and filter results as appropriate
-        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(flowController.getAuthorizer(), RequestAction.READ, user), recursiveStatusDepth, 1);
-    }
-
-    /**
-     * Returns the status for the components in the specified group with the
-     * specified report. The results will be filtered by executing the specified
-     * predicate.
-     *
-     * @param group group id
-     * @param statusReport report
-     * @param isAuthorized is authorized check
-     * @param recursiveStatusDepth the number of levels deep we should recurse and still include the the processors' statuses, the groups' statuses, etc. in the returned ProcessGroupStatus
-     * @param currentDepth the current number of levels deep that we have recursed
-     * @return the component status
-     */
-    ProcessGroupStatus getGroupStatus(final ProcessGroup group, final RepositoryStatusReport statusReport, final Predicate<Authorizable> isAuthorized,
-                                              final int recursiveStatusDepth, final int currentDepth) {
-        if (group == null) {
-            return null;
-        }
-
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
-
-        final ProcessGroupStatus status = new ProcessGroupStatus();
-        status.setId(group.getIdentifier());
-        status.setName(isAuthorized.evaluate(group) ? group.getName() : group.getIdentifier());
-        int activeGroupThreads = 0;
-        int terminatedGroupThreads = 0;
-        long bytesRead = 0L;
-        long bytesWritten = 0L;
-        int queuedCount = 0;
-        long queuedContentSize = 0L;
-        int flowFilesIn = 0;
-        long bytesIn = 0L;
-        int flowFilesOut = 0;
-        long bytesOut = 0L;
-        int flowFilesReceived = 0;
-        long bytesReceived = 0L;
-        int flowFilesSent = 0;
-        long bytesSent = 0L;
-        int flowFilesTransferred = 0;
-        long bytesTransferred = 0;
-
-        final boolean populateChildStatuses = currentDepth <= recursiveStatusDepth;
-
-        // set status for processors
-        final Collection<ProcessorStatus> processorStatusCollection = new ArrayList<>();
-        status.setProcessorStatus(processorStatusCollection);
-        for (final ProcessorNode procNode : group.getProcessors()) {
-            final ProcessorStatus procStat = getProcessorStatus(statusReport, procNode, isAuthorized);
-            if (populateChildStatuses) {
-                processorStatusCollection.add(procStat);
-            }
-            activeGroupThreads += procStat.getActiveThreadCount();
-            terminatedGroupThreads += procStat.getTerminatedThreadCount();
-            bytesRead += procStat.getBytesRead();
-            bytesWritten += procStat.getBytesWritten();
-
-            flowFilesReceived += procStat.getFlowFilesReceived();
-            bytesReceived += procStat.getBytesReceived();
-            flowFilesSent += procStat.getFlowFilesSent();
-            bytesSent += procStat.getBytesSent();
-        }
-
-        // set status for local child groups
-        final Collection<ProcessGroupStatus> localChildGroupStatusCollection = new ArrayList<>();
-        status.setProcessGroupStatus(localChildGroupStatusCollection);
-        for (final ProcessGroup childGroup : group.getProcessGroups()) {
-            final ProcessGroupStatus childGroupStatus;
-            if (populateChildStatuses) {
-                childGroupStatus = getGroupStatus(childGroup, statusReport, isAuthorized, recursiveStatusDepth, currentDepth + 1);
-                localChildGroupStatusCollection.add(childGroupStatus);
-            } else {
-                // In this case, we don't want to include any of the recursive components' individual statuses. As a result, we can
-                // avoid performing any sort of authorizations. Because we only care about the numbers that come back, we can just indicate
-                // that the user is not authorized. This allows us to avoid the expense of both performing the authorization and calculating
-                // things that we would otherwise need to calculate if the user were in fact authorized.
-                childGroupStatus = getGroupStatus(childGroup, statusReport, authorizable -> false, recursiveStatusDepth, currentDepth + 1);
-            }
-
-            activeGroupThreads += childGroupStatus.getActiveThreadCount();
-            terminatedGroupThreads += childGroupStatus.getTerminatedThreadCount();
-            bytesRead += childGroupStatus.getBytesRead();
-            bytesWritten += childGroupStatus.getBytesWritten();
-            queuedCount += childGroupStatus.getQueuedCount();
-            queuedContentSize += childGroupStatus.getQueuedContentSize();
-
-            flowFilesReceived += childGroupStatus.getFlowFilesReceived();
-            bytesReceived += childGroupStatus.getBytesReceived();
-            flowFilesSent += childGroupStatus.getFlowFilesSent();
-            bytesSent += childGroupStatus.getBytesSent();
-
-            flowFilesTransferred += childGroupStatus.getFlowFilesTransferred();
-            bytesTransferred += childGroupStatus.getBytesTransferred();
-        }
-
-        // set status for remote child groups
-        final Collection<RemoteProcessGroupStatus> remoteProcessGroupStatusCollection = new ArrayList<>();
-        status.setRemoteProcessGroupStatus(remoteProcessGroupStatusCollection);
-        for (final RemoteProcessGroup remoteGroup : group.getRemoteProcessGroups()) {
-            final RemoteProcessGroupStatus remoteStatus = createRemoteGroupStatus(remoteGroup, statusReport, isAuthorized);
-            if (remoteStatus != null) {
-                if (populateChildStatuses) {
-                    remoteProcessGroupStatusCollection.add(remoteStatus);
-                }
-
-                flowFilesReceived += remoteStatus.getReceivedCount();
-                bytesReceived += remoteStatus.getReceivedContentSize();
-                flowFilesSent += remoteStatus.getSentCount();
-                bytesSent += remoteStatus.getSentContentSize();
-            }
-        }
-
-        // connection status
-        final Collection<ConnectionStatus> connectionStatusCollection = new ArrayList<>();
-        status.setConnectionStatus(connectionStatusCollection);
-
-        // get the connection and remote port status
-        for (final Connection conn : group.getConnections()) {
-            final boolean isConnectionAuthorized = isAuthorized.evaluate(conn);
-            final boolean isSourceAuthorized = isAuthorized.evaluate(conn.getSource());
-            final boolean isDestinationAuthorized = isAuthorized.evaluate(conn.getDestination());
-
-            final ConnectionStatus connStatus = new ConnectionStatus();
-            connStatus.setId(conn.getIdentifier());
-            connStatus.setGroupId(conn.getProcessGroup().getIdentifier());
-            connStatus.setSourceId(conn.getSource().getIdentifier());
-            connStatus.setSourceName(isSourceAuthorized ? conn.getSource().getName() : conn.getSource().getIdentifier());
-            connStatus.setDestinationId(conn.getDestination().getIdentifier());
-            connStatus.setDestinationName(isDestinationAuthorized ? conn.getDestination().getName() : conn.getDestination().getIdentifier());
-            connStatus.setBackPressureDataSizeThreshold(conn.getFlowFileQueue().getBackPressureDataSizeThreshold());
-            connStatus.setBackPressureObjectThreshold(conn.getFlowFileQueue().getBackPressureObjectThreshold());
-
-            final FlowFileEvent connectionStatusReport = statusReport.getReportEntry(conn.getIdentifier());
-            if (connectionStatusReport != null) {
-                connStatus.setInputBytes(connectionStatusReport.getContentSizeIn());
-                connStatus.setInputCount(connectionStatusReport.getFlowFilesIn());
-                connStatus.setOutputBytes(connectionStatusReport.getContentSizeOut());
-                connStatus.setOutputCount(connectionStatusReport.getFlowFilesOut());
-
-                flowFilesTransferred += connectionStatusReport.getFlowFilesIn() + connectionStatusReport.getFlowFilesOut();
-                bytesTransferred += connectionStatusReport.getContentSizeIn() + connectionStatusReport.getContentSizeOut();
-            }
-
-            if (statusAnalyticsEngine != null) {
-                StatusAnalytics statusAnalytics =  statusAnalyticsEngine.getStatusAnalytics(conn.getIdentifier());
-                if (statusAnalytics != null) {
-                    Map<String,Long> predictionValues = statusAnalytics.getPredictions();
-                    ConnectionStatusPredictions predictions = new ConnectionStatusPredictions();
-                    connStatus.setPredictions(predictions);
-                    predictions.setPredictedTimeToBytesBackpressureMillis(predictionValues.get("timeToBytesBackpressureMillis"));
-                    predictions.setPredictedTimeToCountBackpressureMillis(predictionValues.get("timeToCountBackpressureMillis"));
-                    predictions.setNextPredictedQueuedBytes(predictionValues.get("nextIntervalBytes"));
-                    predictions.setNextPredictedQueuedCount(predictionValues.get("nextIntervalCount").intValue());
-                    predictions.setPredictedPercentCount(predictionValues.get("nextIntervalPercentageUseCount").intValue());
-                    predictions.setPredictedPercentBytes(predictionValues.get("nextIntervalPercentageUseBytes").intValue());
-                    predictions.setPredictionIntervalMillis(predictionValues.get("intervalTimeMillis"));
-                }
-            }else{
-                connStatus.setPredictions(null);
-            }
-
-            if (isConnectionAuthorized) {
-                if (StringUtils.isNotBlank(conn.getName())) {
-                    connStatus.setName(conn.getName());
-                } else if (conn.getRelationships() != null && !conn.getRelationships().isEmpty()) {
-                    final Collection<String> relationships = new ArrayList<>(conn.getRelationships().size());
-                    for (final Relationship relationship : conn.getRelationships()) {
-                        relationships.add(relationship.getName());
-                    }
-                    connStatus.setName(StringUtils.join(relationships, ", "));
-                }
-            } else {
-                connStatus.setName(conn.getIdentifier());
-            }
-
-            final QueueSize queueSize = conn.getFlowFileQueue().size();
-            final int connectionQueuedCount = queueSize.getObjectCount();
-            final long connectionQueuedBytes = queueSize.getByteCount();
-            if (connectionQueuedCount > 0) {
-                connStatus.setQueuedBytes(connectionQueuedBytes);
-                connStatus.setQueuedCount(connectionQueuedCount);
-            }
-
-            if (populateChildStatuses) {
-                connectionStatusCollection.add(connStatus);
-            }
-
-            queuedCount += connectionQueuedCount;
-            queuedContentSize += connectionQueuedBytes;
-
-            final Connectable source = conn.getSource();
-            if (ConnectableType.REMOTE_OUTPUT_PORT.equals(source.getConnectableType())) {
-                final RemoteGroupPort remoteOutputPort = (RemoteGroupPort) source;
-                activeGroupThreads += processScheduler.getActiveThreadCount(remoteOutputPort);
-            }
-
-            final Connectable destination = conn.getDestination();
-            if (ConnectableType.REMOTE_INPUT_PORT.equals(destination.getConnectableType())) {
-                final RemoteGroupPort remoteInputPort = (RemoteGroupPort) destination;
-                activeGroupThreads += processScheduler.getActiveThreadCount(remoteInputPort);
-            }
-        }
-
-        // status for input ports
-        final Collection<PortStatus> inputPortStatusCollection = new ArrayList<>();
-        status.setInputPortStatus(inputPortStatusCollection);
-
-        final Set<Port> inputPorts = group.getInputPorts();
-        for (final Port port : inputPorts) {
-            final boolean isInputPortAuthorized = isAuthorized.evaluate(port);
-
-            final PortStatus portStatus = new PortStatus();
-            portStatus.setId(port.getIdentifier());
-            portStatus.setGroupId(port.getProcessGroup().getIdentifier());
-            portStatus.setName(isInputPortAuthorized ? port.getName() : port.getIdentifier());
-            portStatus.setActiveThreadCount(processScheduler.getActiveThreadCount(port));
-
-            // determine the run status
-            if (ScheduledState.RUNNING.equals(port.getScheduledState())) {
-                portStatus.setRunStatus(RunStatus.Running);
-            } else if (ScheduledState.DISABLED.equals(port.getScheduledState())) {
-                portStatus.setRunStatus(RunStatus.Disabled);
-            } else if (!port.isValid()) {
-                portStatus.setRunStatus(RunStatus.Invalid);
-            } else {
-                portStatus.setRunStatus(RunStatus.Stopped);
-            }
-
-            // special handling for public ports
-            if (port instanceof PublicPort) {
-                portStatus.setTransmitting(((PublicPort) port).isTransmitting());
-            }
-
-            final FlowFileEvent entry = statusReport.getReportEntries().get(port.getIdentifier());
-            if (entry == null) {
-                portStatus.setInputBytes(0L);
-                portStatus.setInputCount(0);
-                portStatus.setOutputBytes(0L);
-                portStatus.setOutputCount(0);
-            } else {
-                final int processedCount = entry.getFlowFilesOut();
-                final long numProcessedBytes = entry.getContentSizeOut();
-                portStatus.setOutputBytes(numProcessedBytes);
-                portStatus.setOutputCount(processedCount);
-
-                final int inputCount = entry.getFlowFilesIn();
-                final long inputBytes = entry.getContentSizeIn();
-                portStatus.setInputBytes(inputBytes);
-                portStatus.setInputCount(inputCount);
-
-                flowFilesIn += port instanceof PublicPort ? entry.getFlowFilesReceived() : inputCount;
-                bytesIn += port instanceof PublicPort ? entry.getBytesReceived() : inputBytes;
-
-                bytesWritten += entry.getBytesWritten();
-
-                flowFilesReceived += entry.getFlowFilesReceived();
-                bytesReceived += entry.getBytesReceived();
-            }
-
-            if (populateChildStatuses) {
-                inputPortStatusCollection.add(portStatus);
-            }
-
-            activeGroupThreads += portStatus.getActiveThreadCount();
-        }
-
-        // status for output ports
-        final Collection<PortStatus> outputPortStatusCollection = new ArrayList<>();
-        status.setOutputPortStatus(outputPortStatusCollection);
-
-        final Set<Port> outputPorts = group.getOutputPorts();
-        for (final Port port : outputPorts) {
-            final boolean isOutputPortAuthorized = isAuthorized.evaluate(port);
-
-            final PortStatus portStatus = new PortStatus();
-            portStatus.setId(port.getIdentifier());
-            portStatus.setGroupId(port.getProcessGroup().getIdentifier());
-            portStatus.setName(isOutputPortAuthorized ? port.getName() : port.getIdentifier());
-            portStatus.setActiveThreadCount(processScheduler.getActiveThreadCount(port));
-
-            // determine the run status
-            if (ScheduledState.RUNNING.equals(port.getScheduledState())) {
-                portStatus.setRunStatus(RunStatus.Running);
-            } else if (ScheduledState.DISABLED.equals(port.getScheduledState())) {
-                portStatus.setRunStatus(RunStatus.Disabled);
-            } else if (!port.isValid()) {
-                portStatus.setRunStatus(RunStatus.Invalid);
-            } else {
-                portStatus.setRunStatus(RunStatus.Stopped);
-            }
-
-            // special handling for public ports
-            if (port instanceof PublicPort) {
-                portStatus.setTransmitting(((PublicPort) port).isTransmitting());
-            }
-
-            final FlowFileEvent entry = statusReport.getReportEntries().get(port.getIdentifier());
-            if (entry == null) {
-                portStatus.setInputBytes(0L);
-                portStatus.setInputCount(0);
-                portStatus.setOutputBytes(0L);
-                portStatus.setOutputCount(0);
-            } else {
-                final int processedCount = entry.getFlowFilesOut();
-                final long numProcessedBytes = entry.getContentSizeOut();
-                portStatus.setOutputBytes(numProcessedBytes);
-                portStatus.setOutputCount(processedCount);
-
-                final int inputCount = entry.getFlowFilesIn();
-                final long inputBytes = entry.getContentSizeIn();
-                portStatus.setInputBytes(inputBytes);
-                portStatus.setInputCount(inputCount);
-
-                bytesRead += entry.getBytesRead();
-
-                flowFilesOut += port instanceof PublicPort ? entry.getFlowFilesSent() : entry.getFlowFilesOut();
-                bytesOut += port instanceof PublicPort ? entry.getBytesSent() : entry.getContentSizeOut();
-
-                flowFilesSent = entry.getFlowFilesSent();
-                bytesSent += entry.getBytesSent();
-            }
-
-            if (populateChildStatuses) {
-                outputPortStatusCollection.add(portStatus);
-            }
-
-            activeGroupThreads += portStatus.getActiveThreadCount();
-        }
-
-        for (final Funnel funnel : group.getFunnels()) {
-            activeGroupThreads += processScheduler.getActiveThreadCount(funnel);
-        }
-
-        status.setActiveThreadCount(activeGroupThreads);
-        status.setTerminatedThreadCount(terminatedGroupThreads);
-        status.setBytesRead(bytesRead);
-        status.setBytesWritten(bytesWritten);
-        status.setQueuedCount(queuedCount);
-        status.setQueuedContentSize(queuedContentSize);
-        status.setInputContentSize(bytesIn);
-        status.setInputCount(flowFilesIn);
-        status.setOutputContentSize(bytesOut);
-        status.setOutputCount(flowFilesOut);
-        status.setFlowFilesReceived(flowFilesReceived);
-        status.setBytesReceived(bytesReceived);
-        status.setFlowFilesSent(flowFilesSent);
-        status.setBytesSent(bytesSent);
-        status.setFlowFilesTransferred(flowFilesTransferred);
-        status.setBytesTransferred(bytesTransferred);
-
-        final VersionControlInformation vci = group.getVersionControlInformation();
-        if (vci != null) {
-            try {
-                final VersionedFlowStatus flowStatus = vci.getStatus();
-                if (flowStatus != null && flowStatus.getState() != null) {
-                    status.setVersionedFlowState(flowStatus.getState());
-                }
-            } catch (final Exception e) {
-                logger.warn("Failed to determine Version Control State for {}. Will consider state to be SYNC_FAILURE", group, e);
-                status.setVersionedFlowState(VersionedFlowState.SYNC_FAILURE);
-            }
-        }
-
-        return status;
-    }
-
-
-    private RemoteProcessGroupStatus createRemoteGroupStatus(final RemoteProcessGroup remoteGroup, final RepositoryStatusReport statusReport, final Predicate<Authorizable> isAuthorized) {
-        final boolean isRemoteProcessGroupAuthorized = isAuthorized.evaluate(remoteGroup);
-
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
-
-        int receivedCount = 0;
-        long receivedContentSize = 0L;
-        int sentCount = 0;
-        long sentContentSize = 0L;
-        int activeThreadCount = 0;
-        int activePortCount = 0;
-        int inactivePortCount = 0;
-
-        final RemoteProcessGroupStatus status = new RemoteProcessGroupStatus();
-        status.setGroupId(remoteGroup.getProcessGroup().getIdentifier());
-        status.setName(isRemoteProcessGroupAuthorized ? remoteGroup.getName() : remoteGroup.getIdentifier());
-        status.setTargetUri(isRemoteProcessGroupAuthorized ? remoteGroup.getTargetUri() : null);
-
-        long lineageMillis = 0L;
-        int flowFilesRemoved = 0;
-        int flowFilesTransferred = 0;
-        for (final Port port : remoteGroup.getInputPorts()) {
-            // determine if this input port is connected
-            final boolean isConnected = port.hasIncomingConnection();
-
-            // we only want to consider remote ports that we are connected to
-            if (isConnected) {
-                if (port.isRunning()) {
-                    activePortCount++;
-                } else {
-                    inactivePortCount++;
-                }
-
-                activeThreadCount += processScheduler.getActiveThreadCount(port);
-
-                final FlowFileEvent portEvent = statusReport.getReportEntry(port.getIdentifier());
-                if (portEvent != null) {
-                    lineageMillis += portEvent.getAggregateLineageMillis();
-                    flowFilesRemoved += portEvent.getFlowFilesRemoved();
-                    flowFilesTransferred += portEvent.getFlowFilesOut();
-                    sentCount += portEvent.getFlowFilesSent();
-                    sentContentSize += portEvent.getBytesSent();
-                }
-            }
-        }
-
-        for (final Port port : remoteGroup.getOutputPorts()) {
-            // determine if this output port is connected
-            final boolean isConnected = !port.getConnections().isEmpty();
-
-            // we only want to consider remote ports that we are connected from
-            if (isConnected) {
-                if (port.isRunning()) {
-                    activePortCount++;
-                } else {
-                    inactivePortCount++;
-                }
-
-                activeThreadCount += processScheduler.getActiveThreadCount(port);
-
-                final FlowFileEvent portEvent = statusReport.getReportEntry(port.getIdentifier());
-                if (portEvent != null) {
-                    receivedCount += portEvent.getFlowFilesReceived();
-                    receivedContentSize += portEvent.getBytesReceived();
-                }
-            }
-        }
-
-        status.setId(remoteGroup.getIdentifier());
-        status.setTransmissionStatus(remoteGroup.isTransmitting() ? TransmissionStatus.Transmitting : TransmissionStatus.NotTransmitting);
-        status.setActiveThreadCount(activeThreadCount);
-        status.setReceivedContentSize(receivedContentSize);
-        status.setReceivedCount(receivedCount);
-        status.setSentContentSize(sentContentSize);
-        status.setSentCount(sentCount);
-        status.setActiveRemotePortCount(activePortCount);
-        status.setInactiveRemotePortCount(inactivePortCount);
-
-        final int flowFilesOutOrRemoved = flowFilesTransferred + flowFilesRemoved;
-        status.setAverageLineageDuration(flowFilesOutOrRemoved == 0 ? 0 : lineageMillis / flowFilesOutOrRemoved, TimeUnit.MILLISECONDS);
-
-        return status;
-    }
-
-    private ProcessorStatus getProcessorStatus(final RepositoryStatusReport report, final ProcessorNode procNode, final Predicate<Authorizable> isAuthorized) {
-        final FlowFileEvent entry = report.getReportEntries().get(procNode.getIdentifier());
-        return getProcessorStatus(entry, procNode, isAuthorized);
-    }
-
-    private ProcessorStatus getProcessorStatus(final FlowFileEvent flowFileEvent, final ProcessorNode procNode, final Predicate<Authorizable> isAuthorized) {
-        final boolean isProcessorAuthorized = isAuthorized.evaluate(procNode);
-
-        final ProcessScheduler processScheduler = flowController.getProcessScheduler();
-
-        final ProcessorStatus status = new ProcessorStatus();
-        status.setId(procNode.getIdentifier());
-        status.setGroupId(procNode.getProcessGroup().getIdentifier());
-        status.setName(isProcessorAuthorized ? procNode.getName() : procNode.getIdentifier());
-        status.setType(isProcessorAuthorized ? procNode.getComponentType() : "Processor");
-
-        if (flowFileEvent != null && flowFileEvent != EmptyFlowFileEvent.INSTANCE) {
-            final int processedCount = flowFileEvent.getFlowFilesOut();
-            final long numProcessedBytes = flowFileEvent.getContentSizeOut();
-            status.setOutputBytes(numProcessedBytes);
-            status.setOutputCount(processedCount);
-
-            final int inputCount = flowFileEvent.getFlowFilesIn();
-            final long inputBytes = flowFileEvent.getContentSizeIn();
-            status.setInputBytes(inputBytes);
-            status.setInputCount(inputCount);
-
-            final long readBytes = flowFileEvent.getBytesRead();
-            status.setBytesRead(readBytes);
-
-            final long writtenBytes = flowFileEvent.getBytesWritten();
-            status.setBytesWritten(writtenBytes);
-
-            status.setProcessingNanos(flowFileEvent.getProcessingNanoseconds());
-            status.setInvocations(flowFileEvent.getInvocations());
-
-            status.setAverageLineageDuration(flowFileEvent.getAverageLineageMillis());
-
-            status.setFlowFilesReceived(flowFileEvent.getFlowFilesReceived());
-            status.setBytesReceived(flowFileEvent.getBytesReceived());
-            status.setFlowFilesSent(flowFileEvent.getFlowFilesSent());
-            status.setBytesSent(flowFileEvent.getBytesSent());
-            status.setFlowFilesRemoved(flowFileEvent.getFlowFilesRemoved());
-
-            if (isProcessorAuthorized) {
-                status.setCounters(flowFileEvent.getCounters());
-            }
-        }
-
-        // Determine the run status and get any validation error... only validating while STOPPED
-        // is a trade-off we are willing to make, even though processor validity could change due to
-        // environmental conditions (property configured with a file path and the file being externally
-        // removed). This saves on validation costs that would be unnecessary most of the time.
-        if (ScheduledState.DISABLED.equals(procNode.getScheduledState())) {
-            status.setRunStatus(RunStatus.Disabled);
-        } else if (ScheduledState.RUNNING.equals(procNode.getScheduledState())) {
-            status.setRunStatus(RunStatus.Running);
-        } else if (procNode.getValidationStatus() == ValidationStatus.VALIDATING) {
-            status.setRunStatus(RunStatus.Validating);
-        } else if (procNode.getValidationStatus() == ValidationStatus.INVALID) {
-            status.setRunStatus(RunStatus.Invalid);
-        } else {
-            status.setRunStatus(RunStatus.Stopped);
-        }
-
-        status.setExecutionNode(procNode.getExecutionNode());
-        status.setTerminatedThreadCount(procNode.getTerminatedThreadCount());
-        status.setActiveThreadCount(processScheduler.getActiveThreadCount(procNode));
-
-        return status;
-    }
-
-    /**
-     * Returns the total number of bytes read by this instance (at the root process group level, i.e. all events) since the instance started
-     *
-     * @return the total number of bytes read by this instance
-     */
-    @Override
-    public long getTotalBytesRead() {
-        return flowFileEventRepository.reportAggregateEvent().getBytesRead();
-    }
-
-    /**
-     * Returns the total number of bytes written by this instance (at the root process group level, i.e. all events) since the instance started
-     *
-     * @return the total number of bytes written by this instance
-     */
-    @Override
-    public long getTotalBytesWritten() {
-        return flowFileEventRepository.reportAggregateEvent().getBytesWritten();
-    }
-
-    /**
-     * Returns the total number of bytes sent by this instance (at the root process group level) since the instance started
-     *
-     * @return the total number of bytes sent by this instance
-     */
-    @Override
-    public long getTotalBytesSent() {
-        return flowFileEventRepository.reportAggregateEvent().getBytesSent();
-    }
-
-    /**
-     * Returns the total number of bytes received by this instance (at the root process group level) since the instance started
-     *
-     * @return the total number of bytes received by this instance
-     */
-    @Override
-    public long getTotalBytesReceived() {
-        return flowFileEventRepository.reportAggregateEvent().getBytesReceived();
+        return getGroupStatus(group, statusReport, authorizable -> authorizable.isAuthorized(this.authorizer, RequestAction.READ, user), recursiveStatusDepth, 1);
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/connectable/TestLocalPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/connectable/TestLocalPort.java
deleted file mode 100644
index aa6ffe4..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/connectable/TestLocalPort.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.connectable;
-
-import org.apache.nifi.controller.queue.FlowFileQueueFactory;
-import org.apache.nifi.processor.Relationship;
-import org.apache.nifi.util.NiFiProperties;
-import org.junit.Test;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-public class TestLocalPort {
-
-    @Test
-    public void testDefaultValues() {
-        LocalPort port = getLocalInputPort();
-        assertEquals(1, port.getMaxConcurrentTasks());
-        assertEquals(10, port.getMaxIterations());
-    }
-
-    @Test
-    public void testSetConcurrentTasks() {
-        LocalPort port = getLocalInputPort(LocalPort.MAX_CONCURRENT_TASKS_PROP_NAME, "2");
-        assertEquals(2, port.getMaxConcurrentTasks());
-        assertEquals(10, port.getMaxIterations());
-    }
-
-    @Test
-    public void testSetFlowFileLimit() {
-        {
-            LocalPort port = getLocalInputPort(LocalPort.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "100000");
-            assertEquals(1, port.getMaxConcurrentTasks());
-            assertEquals(100, port.getMaxIterations());
-        }
-        {
-            LocalPort port = getLocalInputPort(LocalPort.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "100001");
-            assertEquals(1, port.getMaxConcurrentTasks());
-            assertEquals(101, port.getMaxIterations());
-        }
-        {
-            LocalPort port = getLocalInputPort(LocalPort.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "99999");
-            assertEquals(1, port.getMaxConcurrentTasks());
-            assertEquals(100, port.getMaxIterations());
-        }
-        {
-            LocalPort port = getLocalInputPort(LocalPort.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "0");
-            assertEquals(1, port.getMaxConcurrentTasks());
-            assertEquals(1, port.getMaxIterations());
-        }
-        {
-            LocalPort port = getLocalInputPort(LocalPort.MAX_TRANSFERRED_FLOWFILES_PROP_NAME, "1");
-            assertEquals(1, port.getMaxConcurrentTasks());
-            assertEquals(1, port.getMaxIterations());
-        }
-    }
-
-    private LocalPort getLocalInputPort() {
-        return getLocalPort(ConnectableType.INPUT_PORT, Collections.emptyMap());
-    }
-
-    private LocalPort getLocalInputPort(String name, String value) {
-        Map<String, String> additionalProperties = new HashMap<>();
-        additionalProperties.put(name, value);
-        return getLocalPort(ConnectableType.INPUT_PORT, additionalProperties);
-    }
-
-    private LocalPort getLocalPort(ConnectableType type, Map<String, String> additionalProperties) {
-        NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties);
-        return new LocalPort("1", "test", type, null, niFiProperties);
-    }
-
-    private LocalPort getLocalOutputPort() {
-        return getLocalPort(ConnectableType.OUTPUT_PORT, Collections.emptyMap());
-    }
-
-    @Test
-    public void testInvalidLocalInputPort() {
-        final LocalPort port = getLocalInputPort();
-        assertFalse(port.isValid());
-    }
-
-    @Test
-    public void testValidLocalInputPort() {
-        final LocalPort port = getLocalInputPort();
-
-        // Add an incoming relationship.
-        port.addConnection(new StandardConnection.Builder(null)
-            .source(mock(Connectable.class))
-            .destination(port)
-            .relationships(Collections.singleton(Relationship.ANONYMOUS))
-            .flowFileQueueFactory(mock(FlowFileQueueFactory.class))
-            .build());
-
-        // Add an outgoing relationship.
-        port.addConnection(new StandardConnection.Builder(null)
-            .source(port)
-            .destination(mock(Connectable.class))
-            .relationships(Collections.singleton(Relationship.ANONYMOUS))
-            .flowFileQueueFactory(mock(FlowFileQueueFactory.class))
-            .build());
-
-        assertTrue(port.isValid());
-    }
-
-    @Test
-    public void testInvalidLocalOutputPort() {
-        final LocalPort port = getLocalOutputPort();
-
-        assertFalse(port.isValid());
-    }
-
-    @Test
-    public void testValidLocalOutputPort() {
-        final LocalPort port = getLocalOutputPort();
-
-        // Add an incoming relationship.
-        port.addConnection(new StandardConnection.Builder(null)
-            .source(mock(Connectable.class))
-            .destination(port)
-            .relationships(Collections.singleton(Relationship.ANONYMOUS))
-            .flowFileQueueFactory(mock(FlowFileQueueFactory.class))
-            .build());
-
-        // Add an outgoing relationship.
-        port.addConnection(new StandardConnection.Builder(null)
-            .source(port)
-            .destination(mock(Connectable.class))
-            .relationships(Collections.singleton(Relationship.ANONYMOUS))
-            .flowFileQueueFactory(mock(FlowFileQueueFactory.class))
-            .build());
-
-        assertTrue(port.isValid());
-    }
-
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
similarity index 95%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
index 0bf741a..1dd0125 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
@@ -97,7 +97,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-public class TestStandardProcessorNode {
+public class StandardProcessorNodeIT {
 
     private MockVariableRegistry variableRegistry;
     private Bundle systemBundle;
@@ -174,7 +174,7 @@ public class TestStandardProcessorNode {
         final MockReloadComponent reloadComponent = new MockReloadComponent();
 
         final PropertyDescriptor classpathProp = new PropertyDescriptor.Builder().name("Classpath Resources")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
         final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp));
         final StandardProcessorNode procNode = createProcessorNode(processor, reloadComponent);
 
@@ -229,13 +229,13 @@ public class TestStandardProcessorNode {
         final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties("src/test/resources/conf/nifi.properties", additionalProperties);
 
         final FlowController flowController = FlowController.createStandaloneInstance(mock(FlowFileEventRepository.class), nifiProperties,
-                mock(Authorizer.class), mock(AuditService.class), null, new VolatileBulletinRepository(),
-                new FileBasedVariableRegistry(nifiProperties.getVariableRegistryPropertiesPaths()),
-                mock(FlowRegistryClient.class), extensionManager);
+            mock(Authorizer.class), mock(AuditService.class), null, new VolatileBulletinRepository(),
+            new FileBasedVariableRegistry(nifiProperties.getVariableRegistryPropertiesPaths()),
+            mock(FlowRegistryClient.class), extensionManager);
 
         // Init processor
         final PropertyDescriptor classpathProp = new PropertyDescriptor.Builder().name("Classpath Resources")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
 
         final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp));
         final String uuid = UUID.randomUUID().toString();
@@ -250,7 +250,7 @@ public class TestStandardProcessorNode {
 
         final LoggableComponent<Processor> loggableComponent = new LoggableComponent<>(processor, narBundle.getBundleDetails().getCoordinate(), componentLog);
         final StandardProcessorNode procNode = new StandardProcessorNode(loggableComponent, uuid, validationContextFactory, processScheduler,
-                null, new StandardComponentVariableRegistry(variableRegistry), reloadComponent, extensionManager, new SynchronousValidationTrigger());
+            null, new StandardComponentVariableRegistry(variableRegistry), reloadComponent, extensionManager, new SynchronousValidationTrigger());
 
         final Map<String, String> properties = new HashMap<>();
         properties.put(classpathProp.getName(), "src/test/resources/native");
@@ -276,10 +276,10 @@ public class TestStandardProcessorNode {
         final MockReloadComponent reloadComponent = new MockReloadComponent();
 
         final PropertyDescriptor classpathProp = new PropertyDescriptor.Builder().name("Classpath Resources")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
 
         final PropertyDescriptor otherProp = new PropertyDescriptor.Builder().name("My Property")
-                .addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
 
         final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp, otherProp));
         final StandardProcessorNode procNode = createProcessorNode(processor, reloadComponent);
@@ -343,9 +343,9 @@ public class TestStandardProcessorNode {
         final MockReloadComponent reloadComponent = new MockReloadComponent();
 
         final PropertyDescriptor classpathProp1 = new PropertyDescriptor.Builder().name("Classpath Resource 1")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
         final PropertyDescriptor classpathProp2 = new PropertyDescriptor.Builder().name("Classpath Resource 2")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
 
         final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp1, classpathProp2));
         final StandardProcessorNode procNode = createProcessorNode(processor, reloadComponent);
@@ -388,9 +388,9 @@ public class TestStandardProcessorNode {
         final MockReloadComponent reloadComponent = new MockReloadComponent();
 
         final PropertyDescriptor classpathProp1 = new PropertyDescriptor.Builder().name("Classpath Resource 1")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
         final PropertyDescriptor classpathProp2 = new PropertyDescriptor.Builder().name("Classpath Resource 2")
-                .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
+            .dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
 
         final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp1, classpathProp2));
         final StandardProcessorNode procNode = createProcessorNode(processor, reloadComponent);
@@ -435,7 +435,7 @@ public class TestStandardProcessorNode {
 
             final Map<String, String> properties = new HashMap<>();
             properties.put(ModifiesClasspathNoAnnotationProcessor.CLASSPATH_RESOURCE.getName(),
-                    "src/test/resources/TestClasspathResources/resource1.txt");
+                "src/test/resources/TestClasspathResources/resource1.txt");
             procNode.setProperties(properties);
 
             final URL[] testResources = getTestResources();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardProcessSession.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
similarity index 99%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardProcessSession.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
index 8309d9d..e4ed83b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestStandardProcessSession.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
@@ -104,12 +104,12 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-public class TestStandardProcessSession {
+public class StandardProcessSessionIT {
 
     private StandardProcessSession session;
     private MockContentRepository contentRepo;
     private FlowFileQueue flowFileQueue;
-    private RepositoryContext context;
+    private StandardRepositoryContext context;
     private Connectable connectable;
 
     private ProvenanceEventRepository provenanceRepo;
@@ -156,7 +156,7 @@ public class TestStandardProcessSession {
     public void setup() throws IOException {
         resourceClaimManager = new StandardResourceClaimManager();
 
-        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestStandardProcessSession.class.getResource("/conf/nifi.properties").getFile());
+        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, StandardProcessSessionIT.class.getResource("/conf/nifi.properties").getFile());
         flowFileEventRepository = new RingBufferEventRepository(1);
         counterRepository = new StandardCounterRepository();
         provenanceRepo = new MockProvenanceRepository();
@@ -198,7 +198,7 @@ public class TestStandardProcessSession {
         contentRepo.initialize(new StandardResourceClaimManager());
         flowFileRepo = new MockFlowFileRepository(contentRepo);
 
-        context = new RepositoryContext(connectable, new AtomicLong(0L), contentRepo, flowFileRepo, flowFileEventRepository, counterRepository, provenanceRepo);
+        context = new StandardRepositoryContext(connectable, new AtomicLong(0L), contentRepo, flowFileRepo, flowFileEventRepository, counterRepository, provenanceRepo);
         session = new StandardProcessSession(context, () -> false);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestContentClaimWriteCache.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestContentClaimWriteCache.java
index 8cc068b..d46c564 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestContentClaimWriteCache.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestContentClaimWriteCache.java
@@ -60,7 +60,7 @@ public class TestContentClaimWriteCache {
 
     @Test
     public void testFlushWriteCorrectData() throws IOException {
-        final ContentClaimWriteCache cache = new ContentClaimWriteCache(repository, 4);
+        final ContentClaimWriteCache cache = new StandardContentClaimWriteCache(repository, 4);
 
         final ContentClaim claim1 = cache.getContentClaim();
         assertNotNull(claim1);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
index 01e368e..619a2dd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
@@ -162,7 +162,7 @@ public class TestStandardProcessScheduler {
         when(controller.getFlowManager()).thenReturn(flowManager);
         Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
-        serviceProvider = new StandardControllerServiceProvider(controller, scheduler, null);
+        serviceProvider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
 
         final ConcurrentMap<String, ProcessorNode> processorMap = new ConcurrentHashMap<>();
         Mockito.doAnswer(new Answer<ProcessorNode>() {
@@ -184,7 +184,7 @@ public class TestStandardProcessScheduler {
 
         when(controller.getControllerServiceProvider()).thenReturn(serviceProvider);
 
-        rootGroup = new MockProcessGroup(controller);
+        rootGroup = new MockProcessGroup(flowManager);
         when(flowManager.getGroup(Mockito.anyString())).thenReturn(rootGroup);
 
         when(controller.getReloadComponent()).thenReturn(Mockito.mock(ReloadComponent.class));
@@ -493,7 +493,7 @@ public class TestStandardProcessScheduler {
     @Ignore
     public void validateEnabledDisableMultiThread() throws Exception {
         final StandardProcessScheduler scheduler = createScheduler();
-        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
         final ExecutorService executor = Executors.newCachedThreadPool();
         for (int i = 0; i < 200; i++) {
             final ControllerServiceNode serviceNode = flowManager.createControllerService(RandomShortDelayEnablingService.class.getName(), "1",
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderIT.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderIT.java
index e73ad23..593be2e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderIT.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderIT.java
@@ -129,16 +129,12 @@ public class StandardControllerServiceProviderIT {
     }
 
     public void testEnableReferencingServicesGraph(final StandardProcessScheduler scheduler) throws InterruptedException, ExecutionException {
-        final FlowController controller = Mockito.mock(FlowController.class);
-
-        final ProcessGroup procGroup = new MockProcessGroup(controller);
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
+        final ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
-        final ControllerServiceProvider serviceProvider = new StandardControllerServiceProvider(controller, scheduler, null);
+        final ControllerServiceProvider serviceProvider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
 
         // build a graph of controller services with dependencies as such:
         //
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
index 96df54c..887570b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
@@ -16,18 +16,18 @@
  */
 package org.apache.nifi.controller.service;
 
-import java.util.Collections;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.state.StateManagerProvider;
 import org.apache.nifi.components.validation.ValidationTrigger;
 import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.ExtensionBuilder;
-import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.NodeTypeProvider;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.ReloadComponent;
+import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.nar.ExtensionDiscoveringManager;
+import org.apache.nifi.nar.ExtensionManager;
 import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
 import org.apache.nifi.nar.SystemBundle;
 import org.apache.nifi.registry.VariableRegistry;
@@ -39,6 +39,8 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import java.util.Collections;
+
 
 
 public class StandardControllerServiceProviderTest {
@@ -46,14 +48,12 @@ public class StandardControllerServiceProviderTest {
     private ControllerService proxied;
     private ControllerService implementation;
     private static VariableRegistry variableRegistry;
-    private static NiFiProperties nifiProperties;
     private static ExtensionDiscoveringManager extensionManager;
     private static Bundle systemBundle;
-    private static FlowController flowController;
 
     @BeforeClass
     public static void setupSuite() {
-        nifiProperties = NiFiProperties.createBasicNiFiProperties(StandardControllerServiceProviderTest.class.getResource("/conf/nifi.properties").getFile());
+        final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(StandardControllerServiceProviderTest.class.getResource("/conf/nifi.properties").getFile());
 
         // load the system bundle
         systemBundle = SystemBundle.create(nifiProperties);
@@ -61,16 +61,13 @@ public class StandardControllerServiceProviderTest {
         extensionManager.discoverExtensions(systemBundle, Collections.emptySet());
 
         variableRegistry = new FileBasedVariableRegistry(nifiProperties.getVariableRegistryPropertiesPaths());
-
-        flowController = Mockito.mock(FlowController.class);
-        Mockito.when(flowController.getExtensionManager()).thenReturn(extensionManager);
     }
 
     @Before
     public void setup() throws Exception {
         String id = "id";
         String clazz = "org.apache.nifi.controller.service.util.TestControllerService";
-        ControllerServiceProvider provider = new StandardControllerServiceProvider(Mockito.mock(FlowController.class), null, null);
+        ControllerServiceProvider provider = new StandardControllerServiceProvider(null, null, Mockito.mock(FlowManager.class), Mockito.mock(ExtensionManager.class));
         ControllerServiceNode node = createControllerService(clazz, id, systemBundle.getBundleDetails().getCoordinate(), provider);
         proxied = node.getProxiedControllerService();
         implementation = node.getControllerServiceImplementation();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
index 500fa27..6ed1ee0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
@@ -17,22 +17,6 @@
  */
 package org.apache.nifi.controller.service;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.PropertyDescriptor;
@@ -61,14 +45,12 @@ import org.apache.nifi.controller.service.mock.ServiceC;
 import org.apache.nifi.controller.state.StandardStateMap;
 import org.apache.nifi.engine.FlowEngine;
 import org.apache.nifi.groups.ProcessGroup;
-import org.apache.nifi.groups.StandardProcessGroup;
 import org.apache.nifi.nar.ExtensionDiscoveringManager;
 import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
 import org.apache.nifi.nar.SystemBundle;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.StandardValidationContextFactory;
 import org.apache.nifi.registry.VariableRegistry;
-import org.apache.nifi.registry.variable.MutableVariableRegistry;
 import org.apache.nifi.registry.variable.StandardComponentVariableRegistry;
 import org.apache.nifi.util.MockProcessContext;
 import org.apache.nifi.util.MockProcessorInitializationContext;
@@ -82,6 +64,22 @@ import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+
 public class TestStandardControllerServiceProvider {
 
     private static StateManagerProvider stateManagerProvider = new StateManagerProvider() {
@@ -119,7 +117,7 @@ public class TestStandardControllerServiceProvider {
     private static NiFiProperties niFiProperties;
     private static ExtensionDiscoveringManager extensionManager;
     private static Bundle systemBundle;
-    private FlowController controller;
+    private FlowManager flowManager;
 
     @BeforeClass
     public static void setNiFiProps() {
@@ -133,11 +131,7 @@ public class TestStandardControllerServiceProvider {
 
     @Before
     public void setup() {
-        controller = Mockito.mock(FlowController.class);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
-
-        final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
+        flowManager = Mockito.mock(FlowManager.class);
 
         final ConcurrentMap<String, ProcessorNode> processorMap = new ConcurrentHashMap<>();
         Mockito.doAnswer(new Answer<ProcessorNode>() {
@@ -193,16 +187,13 @@ public class TestStandardControllerServiceProvider {
 
     @Test
     public void testDisableControllerService() {
-        final ProcessGroup procGroup = new MockProcessGroup(controller);
-        final FlowController controller = Mockito.mock(FlowController.class);
+        final ProcessGroup procGroup = new MockProcessGroup(flowManager);
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         final StandardProcessScheduler scheduler = createScheduler();
-        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
 
         final ControllerServiceNode serviceNode = createControllerService(ServiceB.class.getName(), "B", systemBundle.getBundleDetails().getCoordinate(), provider);
         serviceNode.performValidation();
@@ -213,18 +204,13 @@ public class TestStandardControllerServiceProvider {
 
     @Test(timeout = 10000)
     public void testEnableDisableWithReference() throws InterruptedException {
-        final ProcessGroup group = new MockProcessGroup(controller);
-        final FlowController controller = Mockito.mock(FlowController.class);
+        final ProcessGroup group = new MockProcessGroup(flowManager);
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(group);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         final StandardProcessScheduler scheduler = createScheduler();
-        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
-
-        Mockito.when(controller.getControllerServiceProvider()).thenReturn(provider);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
 
         final ControllerServiceNode serviceNodeB = createControllerService(ServiceB.class.getName(), "B", systemBundle.getBundleDetails().getCoordinate(), provider);
         final ControllerServiceNode serviceNodeA = createControllerService(ServiceA.class.getName(), "A", systemBundle.getBundleDetails().getCoordinate(), provider);
@@ -278,17 +264,12 @@ public class TestStandardControllerServiceProvider {
 
     @Test
     public void testOrderingOfServices() {
-        final ProcessGroup procGroup = new MockProcessGroup(controller);
-        final FlowController controller = Mockito.mock(FlowController.class);
+        final ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
-
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
-        final StandardControllerServiceProvider provider =
-            new StandardControllerServiceProvider(controller, null, null);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(null, null, flowManager, extensionManager);
         final ControllerServiceNode serviceNode1 = createControllerService(ServiceA.class.getName(), "1", systemBundle.getBundleDetails().getCoordinate(), provider);
         final ControllerServiceNode serviceNode2 = createControllerService(ServiceB.class.getName(), "2", systemBundle.getBundleDetails().getCoordinate(), provider);
 
@@ -448,8 +429,7 @@ public class TestStandardControllerServiceProvider {
         Mockito.when(flowController.getFlowManager()).thenReturn(flowManager);
         Mockito.when(flowController.getStateManagerProvider()).thenReturn(stateManagerProvider);
 
-        final ProcessGroup group = new StandardProcessGroup(UUID.randomUUID().toString(), serviceProvider, scheduler, null, null, flowController,
-            new MutableVariableRegistry(variableRegistry));
+        final ProcessGroup group = new MockProcessGroup(null);
         group.addProcessor(procNode);
         procNode.setProcessGroup(group);
 
@@ -458,17 +438,13 @@ public class TestStandardControllerServiceProvider {
 
     @Test
     public void testEnableReferencingComponents() {
-        final ProcessGroup procGroup = new MockProcessGroup(controller);
-        final FlowController controller = Mockito.mock(FlowController.class);
+        final ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
-
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         final StandardProcessScheduler scheduler = createScheduler();
-        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, null, null);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
         final ControllerServiceNode serviceNode = createControllerService(ServiceA.class.getName(), "1", systemBundle.getBundleDetails().getCoordinate(), provider);
 
         final ProcessorNode procNode = createProcessor(scheduler, provider);
@@ -488,14 +464,11 @@ public class TestStandardControllerServiceProvider {
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
 
         StandardProcessScheduler scheduler = createScheduler();
-        FlowController controller = Mockito.mock(FlowController.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
 
-        StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
-        ProcessGroup procGroup = new MockProcessGroup(controller);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
+        ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         ControllerServiceNode A = createControllerService(ServiceA.class.getName(), "A", systemBundle.getBundleDetails().getCoordinate(), provider);
         ControllerServiceNode B = createControllerService(ServiceA.class.getName(), "B", systemBundle.getBundleDetails().getCoordinate(), provider);
@@ -540,14 +513,11 @@ public class TestStandardControllerServiceProvider {
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
 
         StandardProcessScheduler scheduler = createScheduler();
-        FlowController controller = Mockito.mock(FlowController.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
 
-        StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
-        ProcessGroup procGroup = new MockProcessGroup(controller);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
+        ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         ControllerServiceNode A = createControllerService(ServiceC.class.getName(), "A", systemBundle.getBundleDetails().getCoordinate(), provider);
         ControllerServiceNode B = createControllerService(ServiceA.class.getName(), "B", systemBundle.getBundleDetails().getCoordinate(), provider);
@@ -585,14 +555,11 @@ public class TestStandardControllerServiceProvider {
         final FlowManager flowManager = Mockito.mock(FlowManager.class);
 
         StandardProcessScheduler scheduler = createScheduler();
-        FlowController controller = Mockito.mock(FlowController.class);
-        Mockito.when(controller.getFlowManager()).thenReturn(flowManager);
 
-        StandardControllerServiceProvider provider = new StandardControllerServiceProvider(controller, scheduler, null);
-        ProcessGroup procGroup = new MockProcessGroup(controller);
+        final StandardControllerServiceProvider provider = new StandardControllerServiceProvider(scheduler, null, flowManager, extensionManager);
+        ProcessGroup procGroup = new MockProcessGroup(flowManager);
 
         Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(procGroup);
-        Mockito.when(controller.getExtensionManager()).thenReturn(extensionManager);
 
         ControllerServiceNode serviceNode1 = createControllerService(ServiceA.class.getName(), "1", systemBundle.getBundleDetails().getCoordinate(), provider);
         ControllerServiceNode serviceNode2 = createControllerService(ServiceA.class.getName(), "2", systemBundle.getBundleDetails().getCoordinate(), provider);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
index 0e0e7e8..b9ee11c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
@@ -26,10 +26,10 @@ import org.apache.nifi.connectable.Port;
 import org.apache.nifi.connectable.Position;
 import org.apache.nifi.connectable.Positionable;
 import org.apache.nifi.controller.ComponentNode;
-import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.Snippet;
 import org.apache.nifi.controller.Template;
+import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.controller.label.Label;
 import org.apache.nifi.controller.queue.DropFlowFileStatus;
 import org.apache.nifi.controller.service.ControllerServiceNode;
@@ -65,13 +65,13 @@ import java.util.function.Predicate;
 public class MockProcessGroup implements ProcessGroup {
     private final Map<String, ControllerServiceNode> serviceMap = new HashMap<>();
     private final Map<String, ProcessorNode> processorMap = new HashMap<>();
-    private final FlowController flowController;
+    private final FlowManager flowManager;
     private final MutableVariableRegistry variableRegistry = new MutableVariableRegistry(VariableRegistry.ENVIRONMENT_SYSTEM_REGISTRY);
     private VersionControlInformation versionControlInfo;
     private ParameterContext parameterContext;
 
-    public MockProcessGroup(final FlowController flowController) {
-        this.flowController = flowController;
+    public MockProcessGroup(final FlowManager flowManager) {
+        this.flowManager = flowManager;
     }
 
     @Override
@@ -293,16 +293,16 @@ public class MockProcessGroup implements ProcessGroup {
     public void addProcessor(final ProcessorNode processor) {
         processor.setProcessGroup(this);
         processorMap.put(processor.getIdentifier(), processor);
-        if (flowController.getFlowManager() != null) {
-            flowController.getFlowManager().onProcessorAdded(processor);
+        if (flowManager != null) {
+            flowManager.onProcessorAdded(processor);
         }
     }
 
     @Override
     public void removeProcessor(final ProcessorNode processor) {
         processorMap.remove(processor.getIdentifier());
-        if (flowController.getFlowManager() != null) {
-            flowController.getFlowManager().onProcessorRemoved(processor);
+        if (flowManager != null) {
+            flowManager.onProcessorRemoved(processor);
         }
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestConnectableTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestConnectableTask.java
index 7214b80..a611799 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestConnectableTask.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestConnectableTask.java
@@ -37,6 +37,7 @@ import org.apache.nifi.controller.ProcessorNode;
 import org.apache.nifi.controller.queue.FlowFileQueue;
 import org.apache.nifi.controller.repository.FlowFileEventRepository;
 import org.apache.nifi.controller.repository.RepositoryContext;
+import org.apache.nifi.controller.repository.StandardRepositoryContext;
 import org.apache.nifi.controller.scheduling.RepositoryContextFactory;
 import org.apache.nifi.controller.scheduling.LifecycleState;
 import org.apache.nifi.controller.scheduling.SchedulingAgent;
@@ -52,7 +53,7 @@ public class TestConnectableTask {
         final FlowController flowController = Mockito.mock(FlowController.class);
         Mockito.when(flowController.getStateManagerProvider()).thenReturn(Mockito.mock(StateManagerProvider.class));
 
-        final RepositoryContext repoContext = Mockito.mock(RepositoryContext.class);
+        final RepositoryContext repoContext = Mockito.mock(StandardRepositoryContext.class);
         Mockito.when(repoContext.getFlowFileEventRepository()).thenReturn(Mockito.mock(FlowFileEventRepository.class));
 
         final RepositoryContextFactory contextFactory = Mockito.mock(RepositoryContextFactory.class);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
index 89755d1..5b9f2e6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
@@ -126,4 +126,8 @@ public interface ExtensionManager {
      */
     void logClassLoaderMapping();
 
+    /**
+     * Logs details about the files loaded by the class loaders
+     */
+    void logClassLoaderDetails();
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
index f780400..375bf66 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
@@ -549,4 +549,54 @@ public class StandardExtensionDiscoveringManager implements ExtensionDiscovering
 
         logger.info(builder.toString());
     }
+
+    @Override
+    public void logClassLoaderDetails() {
+        if (!logger.isDebugEnabled()) {
+            return;
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ClassLoader Hierarchy:\n");
+
+        for (final Bundle bundle : classLoaderBundleLookup.values()) {
+            buildClassLoaderDetails(bundle, sb, 0);
+            sb.append("\n---------------------------------------------------------------------------------------------------------\n");
+        }
+
+        final String message = sb.toString();
+        logger.debug(message);
+    }
+
+    private void buildClassLoaderDetails(final Bundle bundle, final StringBuilder sb, final int indentLevel) {
+        final StringBuilder indentBuilder = new StringBuilder();
+        indentBuilder.append("\n");
+
+        for (int i=0; i < indentLevel; i++) {
+            indentBuilder.append(" ");
+        }
+
+        final String prefix = indentBuilder.toString();
+
+        sb.append(prefix).append("Bundle: ").append(bundle);
+        sb.append(prefix).append("Working Directory: ").append(bundle.getBundleDetails().getWorkingDirectory().getAbsolutePath());
+        sb.append(prefix).append("Coordinates: ").append(bundle.getBundleDetails().getCoordinate().getCoordinate());
+        sb.append(prefix).append("Files loaded: ").append(bundle.getBundleDetails().getCoordinate().getCoordinate());
+
+        final ClassLoader classLoader = bundle.getClassLoader();
+        if (classLoader instanceof URLClassLoader) {
+            final URL[] urls = ((URLClassLoader) bundle.getClassLoader()).getURLs();
+            for (final URL url : urls) {
+                sb.append(prefix).append("    ").append(url.getFile());
+            }
+        }
+
+        final BundleCoordinate parentCoordinate = bundle.getBundleDetails().getDependencyCoordinate();
+        if (parentCoordinate != null) {
+            final Bundle parent = getBundle(parentCoordinate);
+            if (parent != null) {
+                buildClassLoaderDetails(parent, sb, indentLevel + 4);
+            }
+        }
+    }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoaders.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoaders.java
index d830253..4e3893a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoaders.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoaders.java
@@ -228,7 +228,8 @@ public final class NarClassLoaders {
                     // see if this class loader is eligible for loading
                     ClassLoader narClassLoader = null;
                     if (narDependencyCoordinate == null) {
-                        narClassLoader = createNarClassLoader(narDetail.getWorkingDirectory(), jettyClassLoader);
+                        final ClassLoader parentClassLoader = jettyClassLoader == null ? ClassLoader.getSystemClassLoader() : jettyClassLoader;
+                        narClassLoader = createNarClassLoader(narDetail.getWorkingDirectory(), parentClassLoader);
                     } else {
                         final String dependencyCoordinateStr = narDependencyCoordinate.getCoordinate();
 
@@ -279,7 +280,7 @@ public final class NarClassLoaders {
 
             // Ensure exactly one NiFiServer implementation, otherwise report none or multiples found
             if (niFiServers.size() == 0) {
-                throw new IOException("No implementations of NiFiServer found, there must be exactly one implementation.");
+                serverInstance = null;
             } else if (niFiServers.size() > 1) {
                 String sb = "Expected exactly one implementation of NiFiServer but found " + niFiServers.size() + ": " +
                         niFiServers.entrySet().stream().map((entry) -> entry.getKey().getClass().getName() + " from " + entry.getValue()).collect(Collectors.joining(", "));
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarUnpacker.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarUnpacker.java
index 9fa92bb..fcfe598 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarUnpacker.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarUnpacker.java
@@ -46,6 +46,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -73,6 +74,16 @@ public final class NarUnpacker {
         final File frameworkWorkingDir = props.getFrameworkWorkingDirectory();
         final File extensionsWorkingDir = props.getExtensionsWorkingDirectory();
         final File docsWorkingDir = props.getComponentDocumentationWorkingDirectory();
+
+        return unpackNars(systemBundle, frameworkWorkingDir, extensionsWorkingDir, docsWorkingDir, narLibraryDirs);
+    }
+
+    public static ExtensionMapping unpackNars(final Bundle systemBundle, final File frameworkWorkingDir, final File extensionsWorkingDir, final File docsWorkingDir, final List<Path> narLibraryDirs) {
+        return unpackNars(systemBundle, frameworkWorkingDir, extensionsWorkingDir, docsWorkingDir, narLibraryDirs, true, true, (coordinate) -> true);
+    }
+
+    public static ExtensionMapping unpackNars(final Bundle systemBundle, final File frameworkWorkingDir, final File extensionsWorkingDir, final File docsWorkingDir, final List<Path> narLibraryDirs,
+                                              final boolean requireFrameworkNar, final boolean requireJettyNar, final Predicate<BundleCoordinate> narFilter) {
         final Map<File, BundleCoordinate> unpackedNars = new HashMap<>();
 
         try {
@@ -82,7 +93,10 @@ public final class NarUnpacker {
             final List<File> narFiles = new ArrayList<>();
 
             // make sure the nar directories are there and accessible
-            FileUtils.ensureDirectoryExistAndCanReadAndWrite(frameworkWorkingDir);
+            if (requireFrameworkNar) {
+                FileUtils.ensureDirectoryExistAndCanReadAndWrite(frameworkWorkingDir);
+            }
+
             FileUtils.ensureDirectoryExistAndCanReadAndWrite(extensionsWorkingDir);
             FileUtils.ensureDirectoryExistAndCanReadAndWrite(docsWorkingDir);
 
@@ -109,6 +123,12 @@ public final class NarUnpacker {
                     // get the manifest for this nar
                     try (final JarFile nar = new JarFile(narFile)) {
                         BundleCoordinate bundleCoordinate = createBundleCoordinate(nar.getManifest());
+
+                        if (!narFilter.test(bundleCoordinate)) {
+                            logger.debug("Will not expand NAR {} because it does not match the provided filter", bundleCoordinate);
+                            continue;
+                        }
+
                         // determine if this is the framework
                         if (NarClassLoaders.FRAMEWORK_NAR_ID.equals(bundleCoordinate.getId())) {
                             if (unpackedFramework != null) {
@@ -133,27 +153,33 @@ public final class NarUnpacker {
                     }
                 }
 
-                // ensure we've found the framework nar
-                if (unpackedFramework == null) {
-                    throw new IllegalStateException("No framework NAR found.");
-                } else if (!unpackedFramework.canRead()) {
-                    throw new IllegalStateException("Framework NAR cannot be read.");
+                if (requireFrameworkNar) {
+                    // ensure we've found the framework nar
+                    if (unpackedFramework == null) {
+                        throw new IllegalStateException("No framework NAR found.");
+                    } else if (!unpackedFramework.canRead()) {
+                        throw new IllegalStateException("Framework NAR cannot be read.");
+                    }
                 }
 
-                // ensure we've found the jetty nar
-                if (unpackedJetty == null) {
-                    throw new IllegalStateException("No Jetty NAR found.");
-                } else if (!unpackedJetty.canRead()) {
-                    throw new IllegalStateException("Jetty NAR cannot be read.");
+                if (requireJettyNar) {
+                    // ensure we've found the jetty nar
+                    if (unpackedJetty == null) {
+                        throw new IllegalStateException("No Jetty NAR found.");
+                    } else if (!unpackedJetty.canRead()) {
+                        throw new IllegalStateException("Jetty NAR cannot be read.");
+                    }
                 }
 
                 // Determine if any nars no longer exist and delete their working directories. This happens
                 // if a new version of a nar is dropped into the lib dir. ensure no old framework are present
-                final File[] frameworkWorkingDirContents = frameworkWorkingDir.listFiles();
-                if (frameworkWorkingDirContents != null) {
-                    for (final File unpackedNar : frameworkWorkingDirContents) {
-                        if (!unpackedFramework.equals(unpackedNar)) {
-                            FileUtils.deleteFile(unpackedNar, true);
+                if (unpackedFramework != null && frameworkWorkingDir != null) {
+                    final File[] frameworkWorkingDirContents = frameworkWorkingDir.listFiles();
+                    if (frameworkWorkingDirContents != null) {
+                        for (final File unpackedNar : frameworkWorkingDirContents) {
+                            if (!unpackedFramework.equals(unpackedNar)) {
+                                FileUtils.deleteFile(unpackedNar, true);
+                            }
                         }
                     }
                 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/SystemBundle.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/SystemBundle.java
index 9f6b88c..6ace9f7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/SystemBundle.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/SystemBundle.java
@@ -45,6 +45,10 @@ public final class SystemBundle {
      */
     public static Bundle create(final NiFiProperties niFiProperties, final ClassLoader systemClassLoader) {
         final String narLibraryDirectory = niFiProperties.getProperty(NiFiProperties.NAR_LIBRARY_DIRECTORY);
+        return create(narLibraryDirectory, systemClassLoader);
+    }
+
+    public static Bundle create(final String narLibraryDirectory, final ClassLoader systemClassLoader) {
         if (StringUtils.isBlank(narLibraryDirectory)) {
             throw new IllegalStateException("Unable to create system bundle because " + NiFiProperties.NAR_LIBRARY_DIRECTORY + " was null or empty");
         }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileRecord.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileRecord.java
index fd098eb..c6c0148 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileRecord.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/main/java/org/apache/nifi/controller/repository/StandardFlowFileRecord.java
@@ -16,16 +16,7 @@
  */
 package org.apache.nifi.controller.repository;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
 import org.apache.commons.lang3.builder.CompareToBuilder;
-import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
@@ -33,6 +24,14 @@ import org.apache.nifi.controller.repository.claim.ContentClaim;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.flowfile.attributes.CoreAttributes;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
 /**
  * <p>
  * A flow file is a logical notion of an item in a flow with its associated attributes and identity which can be used as a reference for its actual content.
@@ -149,7 +148,7 @@ public final class StandardFlowFileRecord implements FlowFile, FlowFileRecord {
             return false;
         }
         final FlowFile otherRecord = (FlowFile) other;
-        return new EqualsBuilder().append(id, otherRecord.getId()).isEquals();
+        return id == otherRecord.getId();
     }
 
     @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardResourceClaimManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardResourceClaimManager.java
similarity index 100%
rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardResourceClaimManager.java
rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-repository-models/src/test/java/org/apache/nifi/controller/repository/claim/TestStandardResourceClaimManager.java
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
index e196531..154f072 100755
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/bin/nifi.sh
@@ -320,6 +320,29 @@ run() {
       run_nifi_cmd="exec ${run_nifi_cmd}"
     fi
 
+    if [ "$1" = "stateless" ]; then
+        STATELESS_JAVA_OPTS="${STATELESS_JAVA_OPTS:=-Xms1024m -Xmx1024m}"
+
+        shift # Remove 'stateless' argument so we can pass all the rest as $@
+
+        echo
+        echo "Note: Use of this command is considered experimental. The commands and approach used may change from time to time."
+        echo
+        echo "Java home (JAVA_HOME): ${JAVA_HOME}"
+        echo "NiFi home (NIFI_HOME): ${NIFI_HOME}"
+        echo "Java options (STATELESS_JAVA_OPTS): ${STATELESS_JAVA_OPTS}"
+        echo
+        run_nifi_cmd="'${JAVA}' ${BOOTSTRAP_LOG_PARAMS} '-Dlogback.configurationFile=${NIFI_HOME}/conf/stateless-logback.xml' -cp '${NIFI_HOME}/lib/*:${NIFI_HOME}/conf' ${STATELESS_JAVA_OPTS} 'org.apache.nifi.stateless.bootstrap.RunStatelessFlow'"
+
+        eval "cd ${NIFI_HOME}"
+        # Our arguments may have spaces (especially for passing parameters). The eval command will strip those out. To avoid that, we need to use "$@" to get the quotes in the arguments, and then
+        # surround that by single-ticks in order to prevent eval from stripping the quotes out.
+        eval "${run_nifi_cmd}" '"$@"'
+        EXIT_STATUS=$?
+        echo
+        return;
+    fi
+
     if [ "$1" = "start" ]; then
         ( eval "cd ${NIFI_HOME} && ${run_nifi_cmd}" & )> /dev/null 1>&-
     else
@@ -334,22 +357,6 @@ run() {
     echo
 }
 
-stateless(){
-    STATELESS_JAVA_OPTS="${STATELESS_JAVA_OPTS:=-Xms1024m -Xmx1024m}"
-
-    init
-    shift
-
-    echo
-    echo "Note: Use of this command is considered experimental. The commands and approach used may change from time to time."
-    echo
-    echo "Java home (JAVA_HOME): ${JAVA_HOME}"
-    echo "NiFi home (NIFI_HOME): ${NIFI_HOME}"
-    echo "Java options (STATELESS_JAVA_OPTS): ${STATELESS_JAVA_OPTS}"
-    echo
-    "${JAVA}" -cp "${NIFI_HOME}/lib/bootstrap/*" ${STATELESS_JAVA_OPTS} "org.apache.nifi.bootstrap.RunStatelessNiFi" ExtractNars
-    "${JAVA}" -cp "${NIFI_HOME}/lib/bootstrap/*" ${STATELESS_JAVA_OPTS} "org.apache.nifi.bootstrap.RunStatelessNiFi" "$@"
-}
 main() {
     init "$1"
     run "$@"
@@ -360,14 +367,11 @@ case "$1" in
     install)
         install "$@"
         ;;
-    start|stop|run|status|dump|diagnostics|env)
+
+    start|stop|run|status|dump|diagnostics|env|stateless)
         main "$@"
         ;;
 
-    #Note: Use of this command is considered experimental. The commands and approach used may change from time to time.
-    stateless)
-        stateless "$@"
-        ;;
     restart)
         init
         run "stop"
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless-logback.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless-logback.xml
new file mode 100644
index 0000000..cc7b55c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless-logback.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<configuration scan="true" scanPeriod="30 seconds">
+    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
+        <resetJUL>true</resetJUL>
+    </contextListener>
+
+    <appender name="APP_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${org.apache.nifi.bootstrap.config.log.dir}/nifi-stateless.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!--
+              For daily rollover, use 'app_%d.log'.
+              For hourly rollover, use 'app_%d{yyyy-MM-dd_HH}.log'.
+              To GZIP rolled files, replace '.log' with '.log.gz'.
+              To ZIP rolled files, replace '.log' with '.log.zip'.
+            -->
+            <fileNamePattern>${org.apache.nifi.bootstrap.config.log.dir}/nifi-stateless_%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <!-- keep 30 log files worth of history -->
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <immediateFlush>true</immediateFlush>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%date %level [%thread] %logger{40} %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%date %level [%thread] %logger{40} %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- valid logging levels: TRACE, DEBUG, INFO, WARN, ERROR -->
+
+    <logger name="org.apache.nifi" level="INFO"/>
+    <logger name="org.apache.nifi.processors" level="WARN"/>
+    <logger name="org.apache.nifi.processors.standard.LogAttribute" level="INFO"/>
+    <logger name="org.apache.nifi.processors.standard.LogMessage" level="INFO"/>
+    <logger name="org.apache.nifi.controller.repository.StandardProcessSession" level="WARN" />
+
+    <logger name="org.apache.calcite.runtime.CalciteException" level="OFF" />
+
+    <!-- Suppress non-error messages from SSHJ which was emitting large amounts of INFO logs by default -->
+    <logger name="net.schmizz.sshj" level="WARN" />
+    <logger name="com.hierynomus.sshj" level="WARN" />
+
+    <!-- Suppress non-error messages from SMBJ which was emitting large amounts of INFO logs by default -->
+    <logger name="com.hierynomus.smbj" level="WARN" />
+
+    <logger name="org.apache.nifi.stateless" level="INFO" additivity="false">
+        <appender-ref ref="CONSOLE" />
+        <appender-ref ref="APP_FILE" />
+    </logger>
+
+    <root level="INFO">
+        <appender-ref ref="APP_FILE" />
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless.properties
new file mode 100644
index 0000000..ab7e397
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/stateless.properties
@@ -0,0 +1,40 @@
+# 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.
+
+# NAR and Working Directories #
+nifi.stateless.nar.directory=./lib
+nifi.stateless.working.directory=./work/stateless
+
+# Security Properties #
+nifi.stateless.security.keystore=
+nifi.stateless.security.keystoreType=
+nifi.stateless.security.keystorePasswd=
+nifi.stateless.security.keyPasswd=
+nifi.stateless.security.truststore=
+nifi.stateless.security.truststoreType=
+nifi.stateless.security.truststorePasswd=
+nifi.stateless.sensitive.props.key=
+
+# Extension Client Configuration #
+# Zero or more Nexus repositories may be specified here.
+# The following client communicates with Maven Central. To use it, uncomment the following 4 lines.
+# Additional clients can be added by adding duplicate properties but changing mvn-central to a different key.
+#nifi.stateless.extension.client.mvn-central.type=nexus
+#nifi.stateless.extension.client.mvn-central.timeout=30 sec
+#nifi.stateless.extension.client.mvn-central.baseUrl=https://repo1.maven.org/maven2/
+#nifi.stateless.extension.client.mvn-central.useSslContext=false
+
+# Kerberos Properties #
+nifi.stateless.kerberos.krb5.file=/etc/krb5.conf
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/pom.xml
index b296ccb..24e0ae8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/pom.xml
@@ -24,15 +24,6 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-security-utils</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-administration</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-api</artifactId>
         </dependency>
         <dependency>
@@ -48,33 +39,6 @@
             <artifactId>nifi-framework-core-api</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.glassfish.jersey.core</groupId>
-            <artifactId>jersey-client</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.mail</groupId>
-            <artifactId>mail</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-web-utils</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-utils</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-site-to-site-client</artifactId>
-            <version>1.13.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-mock</artifactId>
             <version>1.13.0-SNAPSHOT</version>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRemoteGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRemoteGroupPort.java
index da050e2..9d39068 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRemoteGroupPort.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRemoteGroupPort.java
@@ -33,6 +33,7 @@ import org.apache.nifi.processor.DataUnit;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
 import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.io.InputStreamCallback;
 import org.apache.nifi.remote.client.SiteToSiteClient;
 import org.apache.nifi.remote.client.SiteToSiteClientConfig;
@@ -47,7 +48,6 @@ import org.apache.nifi.remote.util.StandardDataPacket;
 import org.apache.nifi.reporting.Severity;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 import org.apache.nifi.util.FormatUtils;
-import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.StopWatch;
 import org.apache.nifi.util.StringUtils;
 import org.slf4j.Logger;
@@ -92,8 +92,7 @@ public class StandardRemoteGroupPort extends RemoteGroupPort {
     }
 
     public StandardRemoteGroupPort(final String id, final String targetId, final String name, final RemoteProcessGroup remoteGroup,
-            final TransferDirection direction, final ConnectableType type, final SSLContext sslContext, final ProcessScheduler scheduler,
-        final NiFiProperties nifiProperties) {
+            final TransferDirection direction, final ConnectableType type, final SSLContext sslContext, final ProcessScheduler scheduler) {
         // remote group port id needs to be unique but cannot just be the id of the port
         // in the remote group instance. this supports referencing the same remote
         // instance more than once.
@@ -227,24 +226,21 @@ public class StandardRemoteGroupPort extends RemoteGroupPort {
             this.targetRunning.set(false);
             final String message = String.format("%s failed to communicate with %s because the remote instance indicates that the port is not in a valid state", this, url);
             logger.error(message);
-            session.rollback();
             remoteGroup.getEventReporter().reportEvent(Severity.ERROR, CATEGORY, message);
-            return;
+            throw new ProcessException(e);
         } catch (final UnknownPortException e) {
             context.yield();
             this.targetExists.set(false);
             final String message = String.format("%s failed to communicate with %s because the remote instance indicates that the port no longer exists", this, url);
             logger.error(message);
-            session.rollback();
             remoteGroup.getEventReporter().reportEvent(Severity.ERROR, CATEGORY, message);
-            return;
+            throw new ProcessException(e);
         } catch (final UnreachableClusterException e) {
             context.yield();
             final String message = String.format("%s failed to communicate with %s due to %s", this, url, e.toString());
             logger.error(message);
-            session.rollback();
             remoteGroup.getEventReporter().reportEvent(Severity.ERROR, CATEGORY, message);
-            return;
+            throw new ProcessException(e);
         } catch (final IOException e) {
             // we do not yield here because the 'peer' will be penalized, and we won't communicate with that particular nifi instance
             // for a while due to penalization, but we can continue to talk to other nifi instances
@@ -253,9 +249,8 @@ public class StandardRemoteGroupPort extends RemoteGroupPort {
             if (logger.isDebugEnabled()) {
                 logger.error("", e);
             }
-            session.rollback();
             remoteGroup.getEventReporter().reportEvent(Severity.ERROR, CATEGORY, message);
-            return;
+            throw new ProcessException(e);
         }
 
         if (transaction == null) {
@@ -285,7 +280,8 @@ public class StandardRemoteGroupPort extends RemoteGroupPort {
 
             remoteGroup.getEventReporter().reportEvent(Severity.ERROR, CATEGORY, message);
             transaction.error();
-            session.rollback();
+
+            throw new ProcessException(t);
         }
     }
 
@@ -365,8 +361,8 @@ public class StandardRemoteGroupPort extends RemoteGroupPort {
             session.commit();
 
             final String flowFileDescription = (flowFilesSent.size() < 20) ? flowFilesSent.toString() : flowFilesSent.size() + " FlowFiles";
-            logger.info("{} Successfully sent {} ({}) to {} in {} milliseconds at a rate of {}", new Object[]{
-                this, flowFileDescription, dataSize, transaction.getCommunicant().getUrl(), uploadMillis, uploadDataRate});
+            logger.info("{} Successfully sent {} ({}) to {} in {} milliseconds at a rate of {}",
+                this, flowFileDescription, dataSize, transaction.getCommunicant().getUrl(), uploadMillis, uploadDataRate);
 
             return flowFilesSent.size();
         } catch (final Exception e) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRemoteGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRemoteGroupPort.java
index 8c47cf8..15637da 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRemoteGroupPort.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRemoteGroupPort.java
@@ -16,27 +16,6 @@
  */
 package org.apache.nifi.remote;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.IntStream;
 import org.apache.nifi.connectable.ConnectableType;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.events.EventReporter;
@@ -47,7 +26,6 @@ import org.apache.nifi.groups.ProcessGroup;
 import org.apache.nifi.groups.RemoteProcessGroup;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
-import org.apache.nifi.properties.StandardNiFiProperties;
 import org.apache.nifi.provenance.ProvenanceEventRecord;
 import org.apache.nifi.provenance.ProvenanceEventType;
 import org.apache.nifi.remote.client.SiteToSiteClient;
@@ -66,6 +44,28 @@ import org.apache.nifi.util.SharedSessionState;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 public class TestStandardRemoteGroupPort {
 
     private static final String ID = "remote-group-port-id";
@@ -119,8 +119,7 @@ public class TestStandardRemoteGroupPort {
                 break;
         }
 
-        port = spy(new StandardRemoteGroupPort(ID, ID, NAME,
-                remoteGroup, direction, connectableType, null, scheduler, new StandardNiFiProperties()));
+        port = spy(new StandardRemoteGroupPort(ID, ID, NAME, remoteGroup, direction, connectableType, null, scheduler));
 
         doReturn(true).when(remoteGroup).isTransmitting();
         doReturn(protocol).when(remoteGroup).getTransportProtocol();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/README.md b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/README.md
deleted file mode 100644
index 081f22c..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/README.md
+++ /dev/null
@@ -1,132 +0,0 @@
-<!--
-  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.
--->
-# Stateless NiFi 
-Similar to other stream processing frameworks, receipt of incoming data is not acknowledged until it is written to a destination. In the event of failure, data can be replayed from the source rather than relying on a stateful content repository. This will not work for all cases (e.g. fire-and-forget HTTP/tcp), but a large portion of use cases have a resilient source to retry from.
-
-Note: Provenance, metrics, logs are not extracted at this time. Docker and other container engines can be used for logs and metrics.
-### Build:
-`mvn package -P docker`
-
-Docker image will be tagged apache/nifi-stateless:1.10.0-SNAPSHOT-dockermaven
-
-### Usage:
-After building, the image can be used as follows:
-`docker run <options> apache/nifi-stateless:1.10.0-SNAPSHOT-dockermaven <arguments>`
-
-Stateless NiFi flows can also be run using nifi.sh
-`./bin/nifi.sh stateless <arguments>`
-
-The <arguments> dictate the runtime to use:
-```
-1) RunFromRegistry [Once|Continuous] --json <JSON>
-   RunFromRegistry [Once|Continuous] --file <File Name>   # Filename of JSON file that matches the examples below.
-
-2) RunYARNServiceFromRegistry <YARN RM URL> <Docker Image Name> <Service Name> <# of Containers> --json <JSON>
-   RunYARNServiceFromRegistry <YARN RM URL> <Docker Image Name> <Service Name> <# of Containers> --file <File Name>
-
-3) RunOpenwhiskActionServer   <Port>
-```
-
-### Examples:
-```
-1) ${NIFI_HOME}/bin/nifi.sh stateless RunFromRegistry Once --file /Users/nifi/nifi-stateless-configs/flow-abc.json
-2) docker run --rm -it apache/nifi-stateless:1.10.0-SNAPSHOT-dockermaven \
-    RunFromRegistry Once --json "`cat /Users/nifi/nifi-stateless-configs/flow-abc.json`"
-3) docker run --rm -it -v /Users/nifi/nifi-stateless-configs/kafka-to-solr.json:/home/nifi/flow.json apache/nifi-stateless:1.10.0-SNAPSHOT-dockermaven \
-    RunYARNServiceFromRegistry http://127.0.0.1:8088 apache/nifi-stateless:latest kafka-to-solr 3 --file /home/nifi/flow.json
-4) docker run -d apache/nifi-stateless:1.10.0-SNAPSHOT-dockermaven \
-    RunOpenwhiskActionServer 8080
-```
-
-### Notes:
-```
-1) The configuration file must be in JSON format.
-2) When providing configurations via JSON, the following attributes must be provided: nifi_registry, nifi_bucket, nifi_flow.
-3) When running in docker, the configuration can either be provided as a string or by localizing the file into the docker container such as through the "-v" option.
-```
-
-### JSON Format
-The JSON that is provided, either via the `--json` command-line argument or the `--file` command-line argument has the following elements:
-
-- `registryUrl` : The URL of the NiFi Registry that should be used for pulling the Flow
-- `bucketId` : The UUID of the Bucket containing the flow
-- `flowId` : The UUID of the flow to run
-- `flowVersion` : _Optional_ - The Version of the flow to run. If not present or equal to -1, then the latest version of the flow will be used.
-- `materializeContent` : _Optional_ - Whether or not the contents of the FlowFile should be stored in Java Heap so that they can be read multiple times. If this value is `false`, the contents of any
-input FlowFile will be read as a stream of data and not buffered into heap. However, this means that the contents can be read only one time. This can be useful if transferring large files from HDFS to
- another HDFS instance or directory, for example, and contains a simple flow such as `ListHDFS -> FetchHDFS -> PutHDFS`. In this flow, the contents of the files will be buffered into Java Heap if the
- value of this argument is `true` but will not be if the value of this argument is `false`.
-- `failurePortIds`: _Optional_ - An array of Port UUID's, such that if any data is sent to one of the ports with these ID's, the flow is considered "failed" and will stop immediately.
-- `ssl`: _Optional_ - If present, provides SSL keystore and truststore information that can be used for interacting with the NiFi Registry and for Site-to-Site communications for Remote Process 
-Groups.
-- `flowFiles`: _Optional_ - An array of FlowFiles that should be provided to the flow's Input Port. Each element in the array is a JSON object. That JSON object can have multiple keys. If any of those
-keys is `nifi_content` then the String value of that element will be the FlowFile's content. Otherwise, the key/value pair is considered an attribute of the FlowFile.
-- `parameters`: _Optional_ - Key-value pairs (or objects if sensitive) that will be passed to the NiFi Flow as parameters.
-
-
-### Minimal JSON Sample:
-    {
-      "registryUrl": "http://localhost:18080",
-      "bucketId": "3aa885db-30c8-4c87-989c-d32b8ea1d3d8",
-      "flowId": "0d219eb8-419b-42ba-a5ee-ce07445c6fc5"
-    }
-
-
-### Full JSON Sample:
-    {
-      "registryUrl": "https://localhost:9443",
-      "bucketId": "3aa885db-30c8-4c87-989c-d32b8ea1d3d8",
-      "flowId": "0d219eb8-419b-42ba-a5ee-ce07445c6fc5",
-      "flowVersion": 8,
-      "materializeContent":true,
-      "failurePortIds": ["f25c9204-6c95-3aa9-b0a8-c556f5f61849"],
-      "ssl": {
-        "keystoreFile": "/etc/security/keystore.jks",
-        "keystorePass": "apachenifi",
-        "keyPass": "nifiapache",
-        "keystoreType": "JKS",
-        "truststoreFile": "/etc/security/truststore.jks",
-        "truststorePass": "apachenifi",
-        "truststoreType": "JKS"
-      },
-      "flowFiles":[{
-        "absolute.path": "/tmp/nifistateless/input/",
-        "filename": "test.txt",
-
-        "nifi_content": "hello"
-      },
-      {
-        "absolute.path": "/tmp/nifistateless/input/",
-        "filename": "test2.txt",
-
-        "nifi_content": "hi"
-      }],
-      "parameters": {
-        "DestinationDirectory" : "/tmp/nifistateless/output2/",
-        "Username" : "jdoe",
-        "Password": { "sensitive": "true", "value": "password" }
-      }
-    }
-
-
-
-### TODO:
-* Provenance is always recorded instead of waiting for commit. Rollback could result in duplicates:
-    -StatelessProvenanceReporter.send force option is not appreciated
-    -StatelessProcessSession.adjustCounter immediate is not appreciated
-* Send logs, metrics, and provenance to kafka/solr (configure a flow ID for each?)
-* Counters
-* Tests
-* Processor and port IDs from the UI do not match IDs in templates or the registry
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/AbstractStatelessComponent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/AbstractStatelessComponent.java
deleted file mode 100644
index 3028e9f..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/AbstractStatelessComponent.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.stateless.core;
-
-import org.apache.nifi.logging.ComponentLog;
-import org.apache.nifi.processor.Relationship;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public abstract class AbstractStatelessComponent implements StatelessComponent {
-    private List<StatelessComponent> parents = new ArrayList<>();
-    private List<String> incomingConnections = new ArrayList<>();
-    private final Map<Relationship, List<StatelessComponent>> children = new HashMap<>();
-    private final Set<Relationship> autoTermination = new HashSet<>();
-    private final Set<Relationship> successOutputPorts = new HashSet<>();
-    private final Set<Relationship> failureOutputPorts = new HashSet<>();
-
-
-    public AbstractStatelessComponent() {
-
-    }
-
-    public List<StatelessComponent> getParents() {
-        return Collections.unmodifiableList(parents);
-    }
-
-    public void addParent(final StatelessComponent parent) {
-        if (parent != null) {
-            parents.add(parent);
-        }
-    }
-
-    public void addIncomingConnection(final String connectionId) {
-        this.incomingConnections.add(connectionId);
-    }
-
-    public void addOutputPort(Relationship relationship, boolean isFailurePort) {
-        if (isFailurePort) {
-            this.failureOutputPorts.add(relationship);
-        } else {
-            this.successOutputPorts.add(relationship);
-        }
-    }
-
-    public void addChild(StatelessComponent child, Relationship relationship) {
-        List<StatelessComponent> list = children.computeIfAbsent(relationship, r -> new ArrayList<>());
-        list.add(child);
-
-        getContext().addConnection(relationship);
-    }
-
-    public void addAutoTermination(Relationship relationship) {
-        this.autoTermination.add(relationship);
-        getContext().addConnection(relationship);
-    }
-
-
-    public boolean validate() {
-        if (!getContext().isValid()) {
-            return false;
-        }
-
-        for (final Relationship relationship : getRelationships()) {
-            boolean hasChildren = this.children.containsKey(relationship);
-            boolean hasAutoterminate = this.autoTermination.contains(relationship);
-            boolean hasFailureOutputPort = this.failureOutputPorts.contains(relationship);
-            boolean hasSuccessOutputPort = this.successOutputPorts.contains(relationship);
-
-            if (!(hasChildren || hasAutoterminate || hasFailureOutputPort || hasSuccessOutputPort)) {
-                getLogger().error("Component: {}, Relationship: {}, either needs to be auto-terminated or connected to another component", new Object[] {toString(), relationship.getName()});
-                return false;
-            }
-        }
-
-        for (final Map.Entry<Relationship, List<StatelessComponent>> entry : this.children.entrySet()) {
-            for (final StatelessComponent component : entry.getValue()) {
-                if (!component.validate()) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    protected Map<Relationship, List<StatelessComponent>> getChildren() {
-        return children;
-    }
-
-    protected Set<Relationship> getSuccessOutputPorts() {
-        return successOutputPorts;
-    }
-
-    protected Set<Relationship> getFailureOutputPorts() {
-        return failureOutputPorts;
-    }
-
-    protected boolean isAutoTerminated(final Relationship relationship) {
-        return autoTermination.contains(relationship);
-    }
-
-
-
-    public abstract Set<Relationship> getRelationships();
-
-    protected abstract StatelessConnectionContext getContext();
-
-    protected abstract ComponentLog getLogger();
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ComponentFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ComponentFactory.java
deleted file mode 100644
index 0f0ba25..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ComponentFactory.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.stateless.core;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.parameter.ParameterLookup;
-import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
-import org.apache.nifi.bundle.Bundle;
-import org.apache.nifi.bundle.BundleCoordinate;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.state.StateManager;
-import org.apache.nifi.controller.ControllerService;
-import org.apache.nifi.controller.ControllerServiceInitializationContext;
-import org.apache.nifi.controller.ControllerServiceLookup;
-import org.apache.nifi.controller.exception.ControllerServiceInstantiationException;
-import org.apache.nifi.controller.exception.ProcessorInstantiationException;
-import org.apache.nifi.logging.ComponentLog;
-import org.apache.nifi.nar.ExtensionManager;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.processor.Processor;
-import org.apache.nifi.processor.ProcessorInitializationContext;
-import org.apache.nifi.processor.Relationship;
-import org.apache.nifi.registry.VariableRegistry;
-import org.apache.nifi.registry.flow.VersionedControllerService;
-import org.apache.nifi.registry.flow.VersionedProcessor;
-import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class ComponentFactory {
-    private static final Logger logger = LoggerFactory.getLogger(ComponentFactory.class);
-    private final ExtensionManager extensionManager;
-
-    public ComponentFactory(final ExtensionManager extensionManager) {
-        this.extensionManager = extensionManager;
-    }
-
-
-    public StatelessProcessorWrapper createProcessor(final VersionedProcessor versionedProcessor, final boolean materializeContent, final StatelessControllerServiceLookup controllerServiceLookup,
-                                                     final VariableRegistry variableRegistry, final Set<URL> classpathUrls, final ParameterContext parameterContext)
-        throws ProcessorInstantiationException {
-
-        final String type = versionedProcessor.getType();
-        final String identifier = versionedProcessor.getIdentifier();
-
-        final Bundle bundle = getAvailableBundle(versionedProcessor.getBundle(), type);
-        if (bundle == null) {
-            throw new IllegalStateException("Unable to find bundle for coordinate "
-                + versionedProcessor.getBundle().getGroup() + ":"
-                + versionedProcessor.getBundle().getArtifact() + ":"
-                + versionedProcessor.getBundle().getVersion());
-        }
-
-        final ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            final ClassLoader detectedClassLoader = extensionManager.createInstanceClassLoader(type, identifier, bundle,
-                classpathUrls == null ? Collections.emptySet() : classpathUrls);
-
-            logger.debug("Setting context class loader to {} (parent = {}) to create {}", detectedClassLoader, detectedClassLoader.getParent(), type);
-            final Class<?> rawClass = Class.forName(type, true, detectedClassLoader);
-            Thread.currentThread().setContextClassLoader(detectedClassLoader);
-
-            final Object extensionInstance = rawClass.newInstance();
-            final ComponentLog componentLog = new SLF4JComponentLog(extensionInstance);
-
-            final Processor processor = (Processor) extensionInstance;
-            final ProcessorInitializationContext initializationContext = new StatelessProcessorInitializationContext(versionedProcessor.getIdentifier(), processor, controllerServiceLookup);
-            processor.initialize(initializationContext);
-
-            // If no classpath urls were provided, check if we need to add additional classpath URL's based on configured properties.
-            if (classpathUrls == null) {
-                final Set<URL> additionalClasspathUrls = getAdditionalClasspathResources(processor.getPropertyDescriptors(), processor.getIdentifier(), versionedProcessor.getProperties(),
-                    parameterContext, variableRegistry,componentLog);
-
-                if (!additionalClasspathUrls.isEmpty()) {
-                    return createProcessor(versionedProcessor, materializeContent, controllerServiceLookup, variableRegistry, additionalClasspathUrls, parameterContext);
-                }
-            }
-
-            final StatelessProcessorWrapper processorWrapper = new StatelessProcessorWrapper(versionedProcessor.getIdentifier(), processor, null,
-                controllerServiceLookup, variableRegistry, materializeContent, detectedClassLoader, parameterContext);
-
-            // Configure the Processor
-            processorWrapper.setAnnotationData(versionedProcessor.getAnnotationData());
-            versionedProcessor.getProperties().forEach(processorWrapper::setProperty);
-            for (String relationship : versionedProcessor.getAutoTerminatedRelationships()) {
-                processorWrapper.addAutoTermination(new Relationship.Builder().name(relationship).build());
-            }
-
-            return processorWrapper;
-        } catch (final Exception e) {
-            throw new ProcessorInstantiationException(type, e);
-        } finally {
-            if (ctxClassLoader != null) {
-                Thread.currentThread().setContextClassLoader(ctxClassLoader);
-            }
-        }
-    }
-
-
-    private Set<URL> getAdditionalClasspathResources(final List<PropertyDescriptor> propertyDescriptors, final String componentId, final Map<String, String> properties,
-                                                     final ParameterLookup parameterLookup, final VariableRegistry variableRegistry, final ComponentLog logger) {
-        final Set<String> modulePaths = new LinkedHashSet<>();
-        for (final PropertyDescriptor descriptor : propertyDescriptors) {
-            if (descriptor.isDynamicClasspathModifier()) {
-                final String value = properties.get(descriptor.getName());
-                if (!StringUtils.isEmpty(value)) {
-                    final StandardPropertyValue propertyValue = new StandardPropertyValue(value, null, parameterLookup, variableRegistry);
-                    modulePaths.add(propertyValue.evaluateAttributeExpressions().getValue());
-                }
-            }
-        }
-
-        final Set<URL> additionalUrls = new LinkedHashSet<>();
-        try {
-            final URL[] urls = ClassLoaderUtils.getURLsForClasspath(modulePaths, null, true);
-            if (urls != null) {
-                additionalUrls.addAll(Arrays.asList(urls));
-            }
-        } catch (MalformedURLException mfe) {
-            logger.error("Error processing classpath resources for " + componentId + ": " + mfe.getMessage(), mfe);
-        }
-
-        return additionalUrls;
-    }
-
-
-    public ControllerService createControllerService(final VersionedControllerService versionedControllerService, final VariableRegistry variableRegistry,
-                                                     final ControllerServiceLookup serviceLookup, final StateManager stateManager, final ParameterLookup parameterLookup) {
-        return createControllerService(versionedControllerService, variableRegistry, null, serviceLookup, stateManager, parameterLookup);
-    }
-
-
-    private ControllerService createControllerService(final VersionedControllerService versionedControllerService, final VariableRegistry variableRegistry, final Set<URL> classpathUrls,
-                                                      final ControllerServiceLookup serviceLookup, final StateManager stateManager, final ParameterLookup parameterLookup) {
-
-        final String type = versionedControllerService.getType();
-        final String identifier = versionedControllerService.getIdentifier();
-
-        final Bundle bundle = getAvailableBundle(versionedControllerService.getBundle(), type);
-        if (bundle == null) {
-            throw new IllegalStateException("Unable to find bundle for coordinate "
-                + versionedControllerService.getBundle().getGroup() + ":"
-                + versionedControllerService.getBundle().getArtifact() + ":"
-                + versionedControllerService.getBundle().getVersion());
-        }
-
-        final ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            final ClassLoader detectedClassLoader = extensionManager.createInstanceClassLoader(type, identifier, bundle,
-                classpathUrls == null ? Collections.emptySet() : classpathUrls);
-
-            logger.debug("Setting context class loader to {} (parent = {}) to create {}", detectedClassLoader, detectedClassLoader.getParent(), type);
-            final Class<?> rawClass = Class.forName(type, true, detectedClassLoader);
-            Thread.currentThread().setContextClassLoader(detectedClassLoader);
-
-            final Object extensionInstance = rawClass.newInstance();
-            final ComponentLog componentLog = new SLF4JComponentLog(extensionInstance);
-
-            final ControllerService service = (ControllerService) extensionInstance;
-            final ControllerServiceInitializationContext initializationContext = new StatelessControllerServiceInitializationContext(identifier, service, serviceLookup, stateManager);
-            service.initialize(initializationContext);
-
-            // If no classpath urls were provided, check if we need to add additional classpath URL's based on configured properties.
-            if (classpathUrls == null) {
-                final Set<URL> additionalClasspathUrls = getAdditionalClasspathResources(service.getPropertyDescriptors(), service.getIdentifier(), versionedControllerService.getProperties(),
-                    parameterLookup, variableRegistry, componentLog);
-
-                if (!additionalClasspathUrls.isEmpty()) {
-                    return createControllerService(versionedControllerService, variableRegistry, additionalClasspathUrls, serviceLookup, stateManager, parameterLookup);
-                }
-            }
-
-            return service;
-        } catch (final Exception e) {
-            throw new ControllerServiceInstantiationException(type, e);
-        } finally {
-            if (ctxClassLoader != null) {
-                Thread.currentThread().setContextClassLoader(ctxClassLoader);
-            }
-        }
-    }
-
-    private Bundle getAvailableBundle(final org.apache.nifi.registry.flow.Bundle bundle, final String componentType) {
-        final BundleCoordinate bundleCoordinate = new BundleCoordinate(bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
-        final Bundle availableBundle = extensionManager.getBundle(bundleCoordinate);
-        if (availableBundle != null) {
-            return availableBundle;
-        }
-
-        final List<Bundle> possibleBundles = extensionManager.getBundles(componentType);
-        if (possibleBundles.isEmpty()) {
-            throw new IllegalStateException("Could not find any NiFi Bundles that contain the Extension [" + componentType + "]");
-        }
-
-        if (possibleBundles.size() > 1) {
-            throw new IllegalStateException("Found " + possibleBundles.size() + " different NiFi Bundles that contain the Extension [" + componentType + "] but none of them had a version of " +
-                bundle.getVersion());
-        }
-
-        return possibleBundles.get(0);
-    }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ProvenanceCollector.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ProvenanceCollector.java
deleted file mode 100644
index ac8ee5d..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/ProvenanceCollector.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * 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.stateless.core;
-
-import org.apache.nifi.flowfile.FlowFile;
-import org.apache.nifi.processor.Relationship;
-import org.apache.nifi.processor.exception.FlowFileHandlingException;
-import org.apache.nifi.provenance.ProvenanceEventBuilder;
-import org.apache.nifi.provenance.ProvenanceEventRecord;
-import org.apache.nifi.provenance.ProvenanceEventType;
-import org.apache.nifi.provenance.ProvenanceReporter;
-import org.apache.nifi.provenance.StandardProvenanceEventRecord;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class ProvenanceCollector implements ProvenanceReporter {
-    private static final Logger logger = LoggerFactory.getLogger(ProvenanceCollector.class);
-    private final StatelessProcessSession session;
-    private final String processorId;
-    private final String processorType;
-    private final Collection<ProvenanceEventRecord> events;
-    private long bytesSent = 0L;
-    private long bytesReceived = 0L;
-    private int flowFilesSent = 0;
-    private int flowFilesReceived = 0;
-    private int flowFilesFetched = 0;
-    private long bytesFetched = 0L;
-
-
-    public ProvenanceCollector(final StatelessProcessSession session, final Collection<ProvenanceEventRecord> events, final String processorId, final String processorType) {
-        this.session = session;
-        this.events = events;
-        this.processorId = processorId;
-        this.processorType = processorType;
-    }
-
-    private void verifyFlowFileKnown(final FlowFile flowFile) {
-        if (session != null && !session.isFlowFileKnown(flowFile)) {
-            throw new FlowFileHandlingException(flowFile + " is not known to " + session);
-        }
-    }
-
-    /**
-     * Removes the given event from the reporter
-     *
-     * @param event event
-     */
-    void remove(final ProvenanceEventRecord event) {
-        events.remove(event);
-    }
-
-    void clear() {
-        events.clear();
-    }
-
-    void migrate(final ProvenanceCollector newOwner, final Set<String> flowFileIds) {
-        final Set<ProvenanceEventRecord> toMove = new LinkedHashSet<>();
-        for (final ProvenanceEventRecord event : events) {
-            if (flowFileIds.contains(event.getFlowFileUuid())) {
-                toMove.add(event);
-            }
-        }
-
-        events.removeAll(toMove);
-        newOwner.events.addAll(toMove);
-    }
-
-    /**
-     * Generates a Fork event for the given child and parents but does not
-     * register the event. This is useful so that a ProcessSession has the
-     * ability to de-dupe events, since one or more events may be created by the
-     * session itself, as well as by the Processor
-     *
-     * @param parents parents
-     * @param child child
-     *
-     * @return record
-     */
-    ProvenanceEventRecord generateJoinEvent(final Collection<FlowFile> parents, final FlowFile child) {
-        final ProvenanceEventBuilder eventBuilder = build(child, ProvenanceEventType.JOIN);
-        eventBuilder.addChildFlowFile(child);
-
-        for (final FlowFile parent : parents) {
-            eventBuilder.addParentFlowFile(parent);
-        }
-
-        return eventBuilder.build();
-    }
-
-    ProvenanceEventRecord generateDropEvent(final FlowFile flowFile, final String details) {
-        return build(flowFile, ProvenanceEventType.DROP).setDetails(details).build();
-    }
-
-    @Override
-    public void receive(final FlowFile flowFile, final String transitUri) {
-        receive(flowFile, transitUri, -1L);
-    }
-
-    @Override
-    public void receive(FlowFile flowFile, String transitUri, String sourceSystemFlowFileIdentifier) {
-        receive(flowFile, transitUri, sourceSystemFlowFileIdentifier, -1L);
-    }
-
-    @Override
-    public void receive(final FlowFile flowFile, final String transitUri, final long transmissionMillis) {
-        receive(flowFile, transitUri, null, transmissionMillis);
-    }
-
-    @Override
-    public void receive(final FlowFile flowFile, final String transitUri, final String sourceSystemFlowFileIdentifier, final long transmissionMillis) {
-        receive(flowFile, transitUri, sourceSystemFlowFileIdentifier, null, transmissionMillis);
-    }
-
-    @Override
-    public void receive(final FlowFile flowFile, final String transitUri, final String sourceSystemFlowFileIdentifier, final String details, final long transmissionMillis) {
-        verifyFlowFileKnown(flowFile);
-
-        try {
-            final ProvenanceEventRecord record = build(flowFile, ProvenanceEventType.RECEIVE)
-                .setTransitUri(transitUri)
-                .setSourceSystemFlowFileIdentifier(sourceSystemFlowFileIdentifier)
-                .setEventDuration(transmissionMillis)
-                .setDetails(details)
-                .build();
-            events.add(record);
-
-            flowFilesReceived++;
-            bytesReceived += flowFile.getSize();
-        } catch (final Exception e) {
-            logger.error("Failed to generate Provenance Event due to " + e);
-            if (logger.isDebugEnabled()) {
-                logger.error("", e);
-            }
-        }
-    }
-
-    @Override
-    public void fetch(final FlowFile flowFile, final String transitUri) {
-        fetch(flowFile, transitUri, -1L);
-    }
-
-    @Override
-    public void fetch(final FlowFile flowFile, final String transitUri, final long transmissionMillis) {
-        fetch(flowFile, transitUri, null, transmissionMillis);
-    }
-
-    @Override
-    public void fetch(final FlowFile flowFile, final String transitUri, final String details, final long transmissionMillis) {
-        verifyFlowFileKnown(flowFile);
-
-        try {
-            final ProvenanceEventRecord record = build(flowFile, ProvenanceEventType.FETCH)
-                .setTransitUri(transitUri)
-                .setEventDuration(transmissionMillis)
-                .setDetails(details)
-                .build();
-            events.add(record);
-
-            flowFilesFetched++;
-            bytesFetched += flowFile.getSize();
-        } catch (final Exception e) {
-            logger.error("Failed to generate Provenance Event due to " + e);
-            if (logger.isDebugEnabled()) {
-                logger.error("", e);
-            }
-        }
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final long transmissionMillis) {
-        send(flowFile, transitUri, transmissionMillis, true);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri) {
-        send(flowFile, transitUri, null, -1L, true);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final String details) {
-        send(flowFile, transitUri, details, -1L, true);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final long transmissionMillis, final boolean force) {
-        send(flowFile, transitUri, null, transmissionMillis, force);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final String details, final boolean force) {
-        send(flowFile, transitUri, details, -1L, force);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final String details, final long transmissionMillis) {
-        send(flowFile, transitUri, details, transmissionMillis, true);
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final String details, final long transmissionMillis, final boolean force) {
-        try {
-            final ProvenanceEventRecord record = build(flowFile, ProvenanceEventType.SEND).setTransitUri(transitUri).setEventDuration(transmissionMillis).setDetails(details).build();
-            events.add(record);
-            flowFilesSent++;
-            bytesSent += flowFile.getSize();
-        } catch (final Exception e) {
-            logger.error("Failed to generate Provenance Event due to " + e);
-            if (logger.isDebugEnabled()) {
-                logger.error("", e);
-            }
-        }
-    }
-
-    @Override
-    public void send(final FlowFile flowFile, final String transitUri, final boolean force) {
-        send(flowFile, transitUri, -1L, true);
-    }
-
-    @Override
-    public void invokeRemoteProcess(final FlowFile flowFile, final String transitUri) {
-        invokeRemoteProcess(flowFile, transitUri, null);
-    }
-
-    @Override
-    public void invokeRemoteProcess(FlowFile flowFile, String transitUri, String details) {
-        try {
-            final ProvenanceEventRecord record = build(flowFile, ProvenanceEventType.REMOTE_INVOCATION)
-                .setTransitUri(transitUri).setDetails(details).build();
-            events.add(record);
-        } catch (final Exception e) {
-            logger.error("Failed to generate Provenance Event due to " + e);
-            if (logger.isDebugEnabled()) {
-                logger.error("", e);
-            }
-        }
-    }
-
-    @Override
-    public void associate(final FlowFile flowFile, final String alternateIdentifierNamespace, final String alternateIdentifier) {
-        try {
... 19160 lines suppressed ...