You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2024/09/16 18:29:51 UTC

(nifi-api) 01/01: NIFI-13732 Initial commit of nifi-api 2.0.0-SNAPSHOT

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

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

commit 42a35005c5d65d3989b2e452e20199eed75fec0e
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Mon Sep 16 13:24:36 2024 -0500

    NIFI-13732 Initial commit of nifi-api 2.0.0-SNAPSHOT
---
 .asf.yaml                                          |  26 +
 .github/PULL_REQUEST_TEMPLATE.md                   |  23 +
 .github/workflows/build.yml                        |  28 +
 .gitignore                                         |  35 +
 .mvn/wrapper/maven-wrapper.properties              |  19 +
 LICENSE                                            | 201 +++++
 NOTICE                                             |   5 +
 README.md                                          |  59 ++
 checkstyle.xml                                     |  67 ++
 mvnw                                               | 259 ++++++
 mvnw.cmd                                           | 149 ++++
 pom.xml                                            | 281 ++++++
 src/main/java/org/apache/nifi/action/Action.java   |  47 +
 .../java/org/apache/nifi/action/Component.java     |  42 +
 .../java/org/apache/nifi/action/Operation.java     |  55 ++
 .../action/component/details/ComponentDetails.java |  26 +
 .../apache/nifi/action/details/ActionDetails.java  |  26 +
 .../annotation/behavior/DefaultRunDuration.java    |  40 +
 .../annotation/behavior/DynamicProperties.java     |  37 +
 .../nifi/annotation/behavior/DynamicProperty.java  |  47 +
 .../annotation/behavior/DynamicRelationship.java   |  44 +
 .../nifi/annotation/behavior/InputRequirement.java |  67 ++
 .../nifi/annotation/behavior/PrimaryNodeOnly.java  |  36 +
 .../nifi/annotation/behavior/ReadsAttribute.java   |  41 +
 .../nifi/annotation/behavior/ReadsAttributes.java  |  39 +
 .../behavior/RequiresInstanceClassLoading.java     |  54 ++
 .../nifi/annotation/behavior/Restricted.java       |  63 ++
 .../nifi/annotation/behavior/Restriction.java      |  50 ++
 .../nifi/annotation/behavior/SideEffectFree.java   |  45 +
 .../apache/nifi/annotation/behavior/Stateful.java  |  57 ++
 .../nifi/annotation/behavior/SupportsBatching.java |  53 ++
 .../SupportsSensitiveDynamicProperties.java        |  36 +
 .../nifi/annotation/behavior/SystemResource.java   |  26 +
 .../behavior/SystemResourceConsideration.java      |  55 ++
 .../behavior/SystemResourceConsiderations.java     |  37 +
 .../nifi/annotation/behavior/TriggerSerially.java  |  38 +
 .../TriggerWhenAnyDestinationAvailable.java        |  40 +
 .../nifi/annotation/behavior/TriggerWhenEmpty.java |  57 ++
 .../nifi/annotation/behavior/WritesAttribute.java  |  41 +
 .../nifi/annotation/behavior/WritesAttributes.java |  39 +
 .../annotation/configuration/DefaultSchedule.java  |  45 +
 .../annotation/configuration/DefaultSettings.java  |  42 +
 .../documentation/CapabilityDescription.java       |  43 +
 .../documentation/DeprecationNotice.java           |  46 +
 .../documentation/MultiProcessorUseCase.java       |  77 ++
 .../documentation/MultiProcessorUseCases.java      |  36 +
 .../documentation/ProcessorConfiguration.java      |  57 ++
 .../nifi/annotation/documentation/SeeAlso.java     |  45 +
 .../apache/nifi/annotation/documentation/Tags.java |  44 +
 .../nifi/annotation/documentation/UseCase.java     |  89 ++
 .../nifi/annotation/documentation/UseCases.java    |  36 +
 .../apache/nifi/annotation/lifecycle/OnAdded.java  |  49 ++
 .../lifecycle/OnConfigurationRestored.java         |  59 ++
 .../nifi/annotation/lifecycle/OnDisabled.java      |  61 ++
 .../nifi/annotation/lifecycle/OnEnabled.java       |  69 ++
 .../nifi/annotation/lifecycle/OnRemoved.java       |  57 ++
 .../nifi/annotation/lifecycle/OnScheduled.java     |  78 ++
 .../nifi/annotation/lifecycle/OnShutdown.java      |  53 ++
 .../nifi/annotation/lifecycle/OnStopped.java       |  75 ++
 .../nifi/annotation/lifecycle/OnUnscheduled.java   |  69 ++
 .../notification/OnPrimaryNodeStateChange.java     |  44 +
 .../annotation/notification/PrimaryNodeState.java  |  33 +
 src/main/java/org/apache/nifi/asset/Asset.java     |  64 ++
 .../components/AbstractConfigurableComponent.java  | 261 ++++++
 .../org/apache/nifi/components/AllowableValue.java | 136 +++
 .../ClassloaderIsolationKeyProvider.java           |  60 ++
 .../nifi/components/ConfigVerificationResult.java  |  83 ++
 .../nifi/components/ConfigurableComponent.java     |  83 ++
 .../org/apache/nifi/components/DescribedValue.java |  38 +
 .../org/apache/nifi/components/PortFunction.java   |  31 +
 .../apache/nifi/components/PropertyDependency.java |  98 +++
 .../apache/nifi/components/PropertyDescriptor.java | 911 +++++++++++++++++++
 .../org/apache/nifi/components/PropertyValue.java  | 371 ++++++++
 .../apache/nifi/components/RequiredPermission.java |  57 ++
 .../apache/nifi/components/ValidationContext.java  | 203 +++++
 .../apache/nifi/components/ValidationResult.java   | 170 ++++
 .../java/org/apache/nifi/components/Validator.java |  51 ++
 .../apache/nifi/components/VersionedComponent.java |  40 +
 .../components/resource/FileResourceReference.java | 100 +++
 .../components/resource/ResourceCardinality.java   |  34 +
 .../nifi/components/resource/ResourceContext.java  |  35 +
 .../components/resource/ResourceDefinition.java    |  37 +
 .../components/resource/ResourceReference.java     |  66 ++
 .../resource/ResourceReferenceFactory.java         |  24 +
 .../components/resource/ResourceReferences.java    |  73 ++
 .../nifi/components/resource/ResourceType.java     |  51 ++
 .../resource/StandardResourceContext.java          |  40 +
 .../resource/StandardResourceDefinition.java       |  40 +
 .../resource/StandardResourceReferenceFactory.java | 131 +++
 .../resource/StandardResourceReferences.java       | 154 ++++
 .../components/resource/URLResourceReference.java  |  67 ++
 .../nifi/components/resource/Utf8TextResource.java |  68 ++
 .../org/apache/nifi/components/state/Scope.java    |  41 +
 .../apache/nifi/components/state/StateManager.java | 101 +++
 .../org/apache/nifi/components/state/StateMap.java |  51 ++
 .../state/exception/StateTooLargeException.java    |  33 +
 .../org/apache/nifi/context/ClusterContext.java    |  30 +
 .../org/apache/nifi/context/PropertyContext.java   |  40 +
 .../nifi/controller/AbstractControllerService.java | 130 +++
 .../nifi/controller/ConfigurationContext.java      |  66 ++
 .../apache/nifi/controller/ControllerService.java  | 206 +++++
 .../ControllerServiceInitializationContext.java    |  53 ++
 .../nifi/controller/ControllerServiceLookup.java   |  72 ++
 .../apache/nifi/controller/NodeTypeProvider.java   |  78 ++
 .../org/apache/nifi/controller/ScheduledState.java |  43 +
 .../controller/VerifiableControllerService.java    |  66 ++
 .../VersionedControllerServiceLookup.java          |  29 +
 .../apache/nifi/controller/queue/QueueSize.java    |  94 ++
 .../nifi/controller/status/ConnectionStatus.java   | 313 +++++++
 .../controller/status/FlowFileAvailability.java    |  26 +
 .../nifi/controller/status/LoadBalanceStatus.java  |  26 +
 .../apache/nifi/controller/status/NodeStatus.java  | 207 +++++
 .../apache/nifi/controller/status/PortStatus.java  | 201 +++++
 .../nifi/controller/status/ProcessGroupStatus.java | 694 +++++++++++++++
 .../status/ProcessingPerformanceStatus.java        | 107 +++
 .../nifi/controller/status/ProcessorStatus.java    | 323 +++++++
 .../status/RemoteProcessGroupStatus.java           | 236 +++++
 .../apache/nifi/controller/status/RunStatus.java   |  26 +
 .../nifi/controller/status/StorageStatus.java      |  69 ++
 .../nifi/controller/status/TransmissionStatus.java |  23 +
 .../analytics/ConnectionStatusPredictions.java     | 115 +++
 .../controller/status/analytics/QueryWindow.java   |  62 ++
 .../status/analytics/StatusAnalytics.java          |  46 +
 .../status/analytics/StatusAnalyticsEngine.java    |  28 +
 .../status/analytics/StatusAnalyticsModel.java     |  64 ++
 .../org/apache/nifi/diagnostics/StorageUsage.java  |  71 ++
 .../documentation/AbstractDocumentationWriter.java | 366 ++++++++
 .../ExtensionDocumentationWriter.java              |  63 ++
 .../apache/nifi/documentation/ExtensionType.java   |  29 +
 .../org/apache/nifi/documentation/ServiceAPI.java  |  51 ++
 .../nifi/documentation/StandardServiceAPI.java     |  51 ++
 ...tionControllerServiceInitializationContext.java |  71 ++
 ...ationFlowAnalysisRuleInitializationContext.java |  60 ++
 ...tionParameterProviderInitializationContext.java |  66 ++
 ...ocumentationProcessorInitializationContext.java |  64 ++
 ...ocumentationReportingInitializationContext.java |  88 ++
 .../init/EmptyControllerServiceLookup.java         |  53 ++
 .../nifi/documentation/init/NopComponentLog.java   | 174 ++++
 .../nifi/documentation/init/NopStateManager.java   |  42 +
 .../init/StandaloneNodeTypeProvider.java           |  31 +
 .../documentation/xml/XmlDocumentationWriter.java  | 670 ++++++++++++++
 .../nifi/expression/AttributeExpression.java       |  81 ++
 .../nifi/expression/AttributeValueDecorator.java   |  28 +
 .../expression/ExpressionLanguageCompiler.java     |  69 ++
 .../nifi/expression/ExpressionLanguageScope.java   |  63 ++
 src/main/java/org/apache/nifi/flow/BatchSize.java  |  76 ++
 src/main/java/org/apache/nifi/flow/Bundle.java     |  92 ++
 .../java/org/apache/nifi/flow/ComponentType.java   |  54 ++
 .../org/apache/nifi/flow/ConnectableComponent.java | 116 +++
 .../apache/nifi/flow/ConnectableComponentType.java |  27 +
 .../org/apache/nifi/flow/ControllerServiceAPI.java |  65 ++
 .../java/org/apache/nifi/flow/ExecutionEngine.java |  35 +
 .../flow/ExternalControllerServiceReference.java   |  44 +
 .../nifi/flow/ParameterProviderReference.java      |  64 ++
 src/main/java/org/apache/nifi/flow/PortType.java   |  23 +
 src/main/java/org/apache/nifi/flow/Position.java   |  76 ++
 .../java/org/apache/nifi/flow/ScheduledState.java  |  23 +
 .../java/org/apache/nifi/flow/VersionedAsset.java  |  44 +
 .../org/apache/nifi/flow/VersionedComponent.java   | 113 +++
 .../nifi/flow/VersionedConfigurableComponent.java  |  34 +
 .../nifi/flow/VersionedConfigurableExtension.java  |  77 ++
 .../org/apache/nifi/flow/VersionedConnection.java  | 174 ++++
 .../nifi/flow/VersionedControllerService.java      |  72 ++
 .../nifi/flow/VersionedExtensionComponent.java     |  32 +
 .../apache/nifi/flow/VersionedExternalFlow.java    |  68 ++
 .../nifi/flow/VersionedExternalFlowMetadata.java   |  84 ++
 .../nifi/flow/VersionedFlowAnalysisRule.java       |  50 ++
 .../apache/nifi/flow/VersionedFlowCoordinates.java | 124 +++
 .../nifi/flow/VersionedFlowRegistryClient.java     |  48 +
 .../java/org/apache/nifi/flow/VersionedFunnel.java |  25 +
 .../java/org/apache/nifi/flow/VersionedLabel.java  |  85 ++
 .../org/apache/nifi/flow/VersionedParameter.java   | 103 +++
 .../nifi/flow/VersionedParameterContext.java       |  90 ++
 .../nifi/flow/VersionedParameterProvider.java      |  89 ++
 .../java/org/apache/nifi/flow/VersionedPort.java   |  87 ++
 .../apache/nifi/flow/VersionedProcessGroup.java    | 246 ++++++
 .../org/apache/nifi/flow/VersionedProcessor.java   | 200 +++++
 .../nifi/flow/VersionedPropertyDescriptor.java     |  83 ++
 .../apache/nifi/flow/VersionedRemoteGroupPort.java | 119 +++
 .../nifi/flow/VersionedRemoteProcessGroup.java     | 146 ++++
 .../apache/nifi/flow/VersionedReportingTask.java   |  69 ++
 .../nifi/flow/VersionedReportingTaskSnapshot.java  |  47 +
 .../nifi/flow/VersionedResourceCardinality.java    |  32 +
 .../nifi/flow/VersionedResourceDefinition.java     |  45 +
 .../apache/nifi/flow/VersionedResourceType.java    |  42 +
 ...ExternalResourceConflictResolutionStrategy.java |  32 +
 .../flow/resource/ExternalResourceDescriptor.java  |  33 +
 .../flow/resource/ExternalResourceProvider.java    |  53 ++
 ...ernalResourceProviderInitializationContext.java |  45 +
 .../ImmutableExternalResourceDescriptor.java       |  43 +
 .../nifi/flowanalysis/AbstractAnalysisResult.java  |  73 ++
 .../flowanalysis/AbstractFlowAnalysisRule.java     |  53 ++
 .../nifi/flowanalysis/ComponentAnalysisResult.java |  45 +
 .../nifi/flowanalysis/EnforcementPolicy.java       |  29 +
 .../nifi/flowanalysis/FlowAnalysisContext.java     |  50 ++
 .../apache/nifi/flowanalysis/FlowAnalysisRule.java |  69 ++
 .../nifi/flowanalysis/FlowAnalysisRuleContext.java |  53 ++
 .../FlowAnalysisRuleInitializationContext.java     |  46 +
 .../nifi/flowanalysis/FlowAnalysisRuleState.java   |  22 +
 .../nifi/flowanalysis/GroupAnalysisResult.java     | 112 +++
 .../flowanalysis/VerifiableFlowAnalysisRule.java   |  66 ++
 .../java/org/apache/nifi/flowfile/FlowFile.java    | 133 +++
 .../nifi/flowfile/attributes/CoreAttributes.java   |  65 ++
 .../flowfile/attributes/FlowFileAttributeKey.java  |  22 +
 .../org/apache/nifi/kerberos/KerberosContext.java  |  51 ++
 .../java/org/apache/nifi/logging/ComponentLog.java | 220 +++++
 .../java/org/apache/nifi/logging/LogLevel.java     |  28 +
 .../java/org/apache/nifi/logging/LogMessage.java   | 123 +++
 .../nifi/migration/PropertyConfiguration.java      | 210 +++++
 .../nifi/migration/RelationshipConfiguration.java  | 105 +++
 .../nifi/parameter/AbstractParameterProvider.java  |  79 ++
 .../java/org/apache/nifi/parameter/Parameter.java  | 179 ++++
 .../apache/nifi/parameter/ParameterDescriptor.java | 115 +++
 .../org/apache/nifi/parameter/ParameterGroup.java  |  54 ++
 .../parameter/ParameterGroupConfiguration.java     | 100 +++
 .../apache/nifi/parameter/ParameterProvider.java   |  87 ++
 .../ParameterProviderInitializationContext.java    |  47 +
 .../nifi/parameter/ParameterSensitivity.java       |  35 +
 .../parameter/VerifiableParameterProvider.java     |  63 ++
 .../apache/nifi/processor/AbstractProcessor.java   |  36 +
 .../processor/AbstractSessionFactoryProcessor.java | 145 +++
 .../java/org/apache/nifi/processor/DataUnit.java   | 245 ++++++
 .../org/apache/nifi/processor/FlowFileFilter.java  |  93 ++
 .../org/apache/nifi/processor/ProcessContext.java  | 167 ++++
 .../org/apache/nifi/processor/ProcessSession.java  | 901 +++++++++++++++++++
 .../nifi/processor/ProcessSessionFactory.java      |  30 +
 .../java/org/apache/nifi/processor/Processor.java  | 130 +++
 .../processor/ProcessorInitializationContext.java  |  55 ++
 .../org/apache/nifi/processor/Relationship.java    | 144 +++
 .../apache/nifi/processor/VerifiableProcessor.java |  65 ++
 .../exception/FlowFileAccessException.java         |  36 +
 .../exception/FlowFileHandlingException.java       |  38 +
 .../exception/MissingFlowFileException.java        |  36 +
 .../nifi/processor/exception/ProcessException.java |  43 +
 .../exception/TerminatedTaskException.java         |  37 +
 .../nifi/processor/io/InputStreamCallback.java     |  34 +
 .../nifi/processor/io/OutputStreamCallback.java    |  35 +
 .../apache/nifi/processor/io/StreamCallback.java   |  37 +
 .../nifi/processor/util/StandardValidators.java    | 969 +++++++++++++++++++++
 .../apache/nifi/processor/util/URLValidator.java   | 100 +++
 .../nifi/provenance/ProvenanceEventBuilder.java    | 326 +++++++
 .../nifi/provenance/ProvenanceEventRecord.java     | 248 ++++++
 .../nifi/provenance/ProvenanceEventRepository.java |  97 +++
 .../nifi/provenance/ProvenanceEventType.java       | 139 +++
 .../apache/nifi/provenance/ProvenanceReporter.java | 749 ++++++++++++++++
 .../apache/nifi/registry/EnvironmentVariables.java | 122 +++
 .../apache/nifi/registry/VariableDescriptor.java   | 153 ++++
 .../registry/flow/AbstractFlowRegistryClient.java  |  50 ++
 .../registry/flow/AuthenticationException.java     |  28 +
 .../nifi/registry/flow/AuthorizationException.java |  28 +
 .../apache/nifi/registry/flow/BucketLocation.java  |  54 ++
 .../registry/flow/FlowAlreadyExistsException.java  |  29 +
 .../apache/nifi/registry/flow/FlowLocation.java    |  45 +
 .../nifi/registry/flow/FlowRegistryBranch.java     |  52 ++
 .../nifi/registry/flow/FlowRegistryBucket.java     |  66 ++
 .../nifi/registry/flow/FlowRegistryClient.java     | 266 ++++++
 .../FlowRegistryClientConfigurationContext.java    |  25 +
 .../FlowRegistryClientInitializationContext.java   |  31 +
 .../nifi/registry/flow/FlowRegistryException.java  |  31 +
 .../flow/FlowRegistryInvalidException.java         |  25 +
 .../registry/flow/FlowRegistryPermissions.java     |  47 +
 .../flow/FlowRegistryPreCommitException.java       |  23 +
 .../nifi/registry/flow/FlowVersionLocation.java    |  67 ++
 .../nifi/registry/flow/NoSuchBucketException.java  |  28 +
 .../nifi/registry/flow/NoSuchFlowException.java    |  28 +
 .../registry/flow/NoSuchFlowVersionException.java  |  28 +
 .../apache/nifi/registry/flow/RegisterAction.java  |  25 +
 .../apache/nifi/registry/flow/RegisteredFlow.java  | 120 +++
 .../nifi/registry/flow/RegisteredFlowSnapshot.java | 108 +++
 .../flow/RegisteredFlowSnapshotMetadata.java       |  83 ++
 .../registry/flow/RegisteredFlowVersionInfo.java   |  31 +
 .../nifi/registry/flow/VersionedFlowState.java     |  63 ++
 .../nifi/reporting/AbstractReportingTask.java      | 117 +++
 .../java/org/apache/nifi/reporting/Bulletin.java   | 156 ++++
 .../org/apache/nifi/reporting/BulletinQuery.java   | 118 +++
 .../apache/nifi/reporting/BulletinRepository.java  | 109 +++
 .../org/apache/nifi/reporting/ComponentType.java   |  78 ++
 .../org/apache/nifi/reporting/EventAccess.java     | 116 +++
 .../nifi/reporting/InitializationException.java    |  32 +
 .../apache/nifi/reporting/ReportingContext.java    | 107 +++
 .../reporting/ReportingInitializationContext.java  |  86 ++
 .../org/apache/nifi/reporting/ReportingTask.java   | 104 +++
 .../java/org/apache/nifi/reporting/Severity.java   |  24 +
 .../nifi/reporting/VerifiableReportingTask.java    |  66 ++
 .../org/apache/nifi/scheduling/ExecutionNode.java  |  31 +
 .../apache/nifi/scheduling/SchedulingStrategy.java |  52 ++
 .../java/org/apache/nifi/search/SearchContext.java |  46 +
 .../java/org/apache/nifi/search/SearchResult.java  |  75 ++
 .../java/org/apache/nifi/search/Searchable.java    |  24 +
 .../java/org/apache/nifi/time/DurationFormat.java  | 236 +++++
 .../apache/nifi/components/EnumDescribedValue.java |  61 ++
 .../nifi/components/EnumNotDescribedValue.java     |  21 +
 .../nifi/components/TestPropertyDescriptor.java    | 345 ++++++++
 .../TestStandardResourceReferenceFactory.java      |  94 ++
 .../resource/TestStandardResourceReferences.java   | 163 ++++
 .../org/apache/nifi/parameter/TestParameter.java   | 218 +++++
 .../org/apache/nifi/processor/TestDataUnit.java    |  41 +
 .../org/apache/nifi/time/TestDurationFormat.java   | 278 ++++++
 .../org/apache/nifi/util/TestURLValidator.java     |  41 +
 299 files changed, 28157 insertions(+)

diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..6022a38
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: Apache-2.0
+
+github:
+  description: "Apache NiFi API"
+  homepage: https://nifi.apache.org
+  labels:
+    - apache
+    - nifi
+  features:
+    wiki: false
+    issues: false
+    projects: false
+  enabled_merge_buttons:
+    squash: true
+  autolink_jira:
+    - NIFI
+    - NIP
+  protected_branches:
+    main:
+      required_signatures: true
+      required_linear_history: true
+notifications:
+  commits: commits@nifi.apache.org
+  issues: issues@nifi.apache.org
+  pullrequests: issues@nifi.apache.org
+  jira_options: link worklog
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..2a1ef8e
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,23 @@
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+
+# Summary
+
+[NIFI-00000](https://issues.apache.org/jira/browse/NIFI-00000)
+
+# Tracking
+
+Please complete the following tracking steps prior to pull request creation.
+
+### Issue Tracking
+
+- [ ] [Apache NiFi Jira](https://issues.apache.org/jira/browse/NIFI) issue created
+
+### Pull Request Tracking
+
+- [ ] Pull Request title starts with Apache NiFi Jira issue number, such as `NIFI-00000`
+- [ ] Pull Request commit message starts with Apache NiFi Jira issue number, as such `NIFI-00000`
+
+### Pull Request Formatting
+
+- [ ] Pull Request based on current revision of the `main` branch
+- [ ] Pull Request refers to a feature branch with one commit containing changes
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..b9f7518
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: Apache-2.0
+
+name: build
+
+on:
+  push:
+  pull_request:
+    branches:
+      - main
+
+permissions:
+  contents: read
+
+jobs:
+  build-java-21:
+    name: Java 21
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Sources
+        uses: actions/checkout@v4
+      - name: Setup Java JDK
+        uses: actions/setup-java@v4
+        with:
+          java-version: '21'
+          distribution: 'liberica'
+          cache: 'maven'
+      - name: Build
+        run: ./mvnw --batch-mode verify
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c1606a4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: Apache-2.0
+
+# Editors
+.idea/
+.vscode/
+.java-version
+.project
+.settings
+.classpath
+nbactions.xml
+nb-configuration.xml
+.DS_Store
+.metadata
+.recommenders
+*.iml
+*.iws
+*.ipr
+*~
+
+# Binaries
+*.class
+target/
+
+# Packages
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Logs
+*.log
+hs_err_pid*
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d58dfb7
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# 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.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..bc8e448
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,5 @@
+Apache NiFi API
+Copyright 2024 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7a4ae23
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+
+# Apache NiFi API
+
+[![license](https://img.shields.io/github/license/apache/nifi-api)](https://github.com/apache/nifi-api/blob/main/LICENSE)
+[![build](https://github.com/apache/nifi-api/actions/workflows/build.yml/badge.svg)](https://github.com/apache/nifi-api/actions/workflows/build.yml)
+
+The [Apache NiFi](https://nifi.apache.org) API repository contains public interfaces and classes for building extension components.
+
+## Requirements
+
+- Java 21
+- Maven 3.9
+
+## Versioning
+
+The project follows [Semantic Versioning 2.0.0](https://semver.org).
+
+## Building
+
+This project uses [Maven](https://maven.apache.org) to build distribution packages.
+
+This project includes the [Maven Wrapper](https://maven.apache.org/wrapper/) to build with required Maven versions.
+
+```
+./mvnw install
+```
+
+## Verifying
+
+Code and documentation changes must pass build verification to be eligible for review.
+
+```
+./mvnw verify
+```
+
+The project uses several build plugins for static code analysis and license evaluation.
+
+- [Apache Maven Checkstyle Plugin](https://maven.apache.org/plugins/maven-checkstyle-plugin/)
+- [Apache Maven PMD Plugin](https://maven.apache.org/plugins/maven-pmd-plugin/)
+- [Apache Release Audit Tool Plugin](https://creadur.apache.org/rat/apache-rat-plugin/)
+- [JaCoCo Maven Plugin](https://www.jacoco.org/jacoco/trunk/doc/maven.html)
+
+## Documentation
+
+The [Apache NiFi Documentation](https://nifi.apache.org/documentation/) includes reference information for project capabilities.
+
+## Contributing
+
+The [Apache NiFi Contributor Guide](https://cwiki.apache.org/confluence/display/NIFI/Contributor+Guide)
+describes the process for getting involved in the development of this project.
+
+## Issues
+
+This project uses [Jira](https://issues.apache.org/jira/browse/NIFI) for tracking bugs and features.
+
+## Licensing
+
+This project is released under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..7c87426
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
+<module name="Checker">
+    <module name="NewlineAtEndOfFile"/>
+    <module name="FileLength"/>
+    <module name="LineLength">
+        <property name="max" value="200"/>
+        <property name="fileExtensions" value="java"/>
+    </module>
+    <module name="FileTabCharacter"/>
+    <module name="RegexpSingleline">
+        <property name="format" value="\s+$"/>
+        <property name="minimum" value="0"/>
+        <property name="maximum" value="0"/>
+        <property name="message" value="Line has trailing spaces."/>
+    </module>
+    <module name="TreeWalker">
+        <module name="MethodName"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InvalidJavadocPosition"/>
+        <module name="JavadocMethod"/>
+        <module name="JavadocType"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/>
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+        <module name="MethodLength">
+            <property name="max" value="250"/>
+        </module>
+        <module name="EmptyForIteratorPad"/>
+        <module name="GenericWhitespace"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround"/>
+        <module name="ModifierOrder"/>
+        <module name="AvoidNestedBlocks"/>
+        <module name="EmptyBlock"/>
+        <module name="LeftCurly"/>
+        <module name="NeedBraces"/>
+        <module name="RightCurly"/>
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+        <module name="MissingSwitchDefault"/>
+        <module name="MultipleVariableDeclarations"/>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+        <module name="InterfaceIsType"/>
+        <module name="ArrayTypeStyle"/>
+        <module name="TodoComment"/>
+        <module name="UpperEll"/>
+        <module name="UnusedLocalVariable"/>
+    </module>
+</module>
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..19529dd
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+#   JAVA_HOME - location of a JDK home dir, required when download maven via java source
+#   MVNW_REPOURL - repo url base for downloading maven distribution
+#   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+#   MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+  [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+  native_path() { cygpath --path --windows "$1"; }
+  ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+  # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+  if [ -n "${JAVA_HOME-}" ]; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+      JAVACCMD="$JAVA_HOME/jre/sh/javac"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+      JAVACCMD="$JAVA_HOME/bin/javac"
+
+      if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+        echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+        echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+        return 1
+      fi
+    fi
+  else
+    JAVACMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v java
+    )" || :
+    JAVACCMD="$(
+      'set' +e
+      'unset' -f command 2>/dev/null
+      'command' -v javac
+    )" || :
+
+    if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+      echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+      return 1
+    fi
+  fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+  str="${1:-}" h=0
+  while [ -n "$str" ]; do
+    char="${str%"${str#?}"}"
+    h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+    str="${str#?}"
+  done
+  printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+  printf %s\\n "$1" >&2
+  exit 1
+}
+
+trim() {
+  # MWRAPPER-139:
+  #   Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+  #   Needed for removing poorly interpreted newline sequences when running in more
+  #   exotic environments such as mingw bash on Windows.
+  printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+  case "${key-}" in
+  distributionUrl) distributionUrl=$(trim "${value-}") ;;
+  distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+  esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+  MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+  case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+  *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+  :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+  :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+  :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+  *)
+    echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+    distributionPlatform=linux-amd64
+    ;;
+  esac
+  distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+  ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+  unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+  exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+  verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+  clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+  trap clean HUP INT TERM EXIT
+else
+  die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+  distributionUrl="${distributionUrl%.zip}.tar.gz"
+  distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+  verbose "Found wget ... using wget"
+  wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+  verbose "Found curl ... using curl"
+  curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+  verbose "Falling back to use Java to download"
+  javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+  targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+  cat >"$javaSource" <<-END
+	public class Downloader extends java.net.Authenticator
+	{
+	  protected java.net.PasswordAuthentication getPasswordAuthentication()
+	  {
+	    return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+	  }
+	  public static void main( String[] args ) throws Exception
+	  {
+	    setDefault( new Downloader() );
+	    java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+	  }
+	}
+	END
+  # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+  verbose " - Compiling Downloader.java ..."
+  "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+  verbose " - Running Downloader.java ..."
+  "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+  distributionSha256Result=false
+  if [ "$MVN_CMD" = mvnd.sh ]; then
+    echo "Checksum validation is not supported for maven-mvnd." >&2
+    echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  elif command -v sha256sum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  elif command -v shasum >/dev/null; then
+    if echo "$distributionSha256Sum  $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+      distributionSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+    echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+    exit 1
+  fi
+  if [ $distributionSha256Result = false ]; then
+    echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+    echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+  unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+  tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..249bdf3
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM   MVNW_REPOURL - repo url base for downloading maven distribution
+@REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+  IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+  $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+  Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+  "maven-mvnd-*" {
+    $USE_MVND = $true
+    $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+    $MVN_CMD = "mvnd.cmd"
+    break
+  }
+  default {
+    $USE_MVND = $false
+    $MVN_CMD = $script -replace '^mvnw','mvn'
+    break
+  }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+  $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+  $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+  $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+  Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+  Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+  exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+  Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+  if ($TMP_DOWNLOAD_DIR.Exists) {
+    try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+    catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+  }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+  $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+  if ($USE_MVND) {
+    Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+  }
+  Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+  if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+    Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+  }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+  Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+  if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+    Write-Error "fail to move MAVEN_HOME"
+  }
+} finally {
+  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+  catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c6eeb46
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache</groupId>
+        <artifactId>apache</artifactId>
+        <version>33</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.apache.nifi</groupId>
+    <artifactId>nifi-api</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>nifi-api</name>
+    <description>Apache NiFi API</description>
+    <url>https://nifi.apache.org</url>
+    <inceptionYear>2014</inceptionYear>
+    <organization>
+        <name>Apache NiFi Project</name>
+        <url>https://nifi.apache.org</url>
+    </organization>
+    <licenses>
+        <license>
+            <name>Apache License 2.0</name>
+            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+    <developers>
+        <developer>
+            <name>Apache NiFi Developers</name>
+            <email>dev@nifi.apache.org</email>
+            <organization>Apache NiFi Project</organization>
+            <organizationUrl>https://nifi.apache.org</organizationUrl>
+        </developer>
+    </developers>
+    <mailingLists>
+        <mailingList>
+            <name>Developers</name>
+            <subscribe>dev-subscribe@nifi.apache.org</subscribe>
+            <unsubscribe>dev-unsubscribe@nifi.apache.org</unsubscribe>
+            <post>dev@nifi.apache.org</post>
+            <archive>https://lists.apache.org/list.html?dev@nifi.apache.org</archive>
+        </mailingList>
+        <mailingList>
+            <name>Users</name>
+            <subscribe>users-subscribe@nifi.apache.org</subscribe>
+            <unsubscribe>users-unsubscribe@nifi.apache.org</unsubscribe>
+            <post>users@nifi.apache.org</post>
+            <archive>https://lists.apache.org/list.html?users@nifi.apache.org</archive>
+        </mailingList>
+        <mailingList>
+            <name>Commits</name>
+            <subscribe>commits-subscribe@nifi.apache.org</subscribe>
+            <unsubscribe>commits-unsubscribe@nifi.apache.org</unsubscribe>
+            <post>commits@nifi.apache.org</post>
+            <archive>https://lists.apache.org/list.html?commits@nifi.apache.org</archive>
+        </mailingList>
+    </mailingLists>
+    <scm>
+        <connection>scm:git:https://github.com/apache/nifi-api</connection>
+        <developerConnection>scm:git:https://github.com/apache/nifi-api</developerConnection>
+        <url>https://github.com/apache/nifi-api</url>
+    </scm>
+    <issueManagement>
+        <system>jira</system>
+        <url>https://issues.apache.org/jira/browse/NIFI</url>
+    </issueManagement>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.release>21</maven.compiler.release>
+        <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
+        <minimalJavaBuildVersion>21</minimalJavaBuildVersion>
+        <minimalMavenBuildVersion>3.9.9</minimalMavenBuildVersion>
+
+        <checkstyle.version>10.18.1</checkstyle.version>
+        <junit-bom.version>5.11.0</junit-bom.version>
+        <jacoco.version>0.8.12</jacoco.version>
+        <maven-pmd-plugin.version>3.25.0</maven-pmd-plugin.version>
+        <mockito.version>5.13.0</mockito.version>
+        <pmd.version>7.5.0</pmd.version>
+        <swagger-annotations.version>2.2.23</swagger-annotations.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.junit</groupId>
+                <artifactId>junit-bom</artifactId>
+                <version>${junit-bom.version}</version>
+                <scope>import</scope>
+                <type>pom</type>
+            </dependency>
+            <dependency>
+                <groupId>org.mockito</groupId>
+                <artifactId>mockito-bom</artifactId>
+                <version>${mockito.version}</version>
+                <scope>import</scope>
+                <type>pom</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Optional dependency for generating OpenAPI Specification -->
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>${swagger-annotations.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.jacoco</groupId>
+                    <artifactId>jacoco-maven-plugin</artifactId>
+                    <version>${jacoco.version}</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-checkstyle-plugin</artifactId>
+                    <version>${version.maven-checkstyle-plugin}</version>
+                    <dependencies>
+                        <dependency>
+                            <groupId>com.puppycrawl.tools</groupId>
+                            <artifactId>checkstyle</artifactId>
+                            <version>${checkstyle.version}</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-pmd-plugin</artifactId>
+                    <version>${maven-pmd-plugin.version}</version>
+                    <dependencies>
+                        <dependency>
+                            <groupId>net.sourceforge.pmd</groupId>
+                            <artifactId>pmd-core</artifactId>
+                            <version>${pmd.version}</version>
+                        </dependency>
+                        <dependency>
+                            <groupId>net.sourceforge.pmd</groupId>
+                            <artifactId>pmd-java</artifactId>
+                            <version>${pmd.version}</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <useIdeaDefaultExcludes>true</useIdeaDefaultExcludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <configLocation>checkstyle.xml</configLocation>
+                    <includeTestSourceDirectory>true</includeTestSourceDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>check</id>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <skipIfEmpty>true</skipIfEmpty>
+                    <archive>
+                        <manifest>
+                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                        </manifest>
+                        <manifestEntries>
+                            <Automatic-Module-Name>${project.groupId}</Automatic-Module-Name>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <quiet>true</quiet>
+                    <failOnError>true</failOnError>
+                    <failOnWarnings>true</failOnWarnings>
+                    <doclint>all,-missing</doclint>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <configuration>
+                    <printFailingErrors>true</printFailingErrors>
+                    <linkXRef>false</linkXRef>
+                    <targetJdk>${maven.compiler.release}</targetJdk>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>check</id>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>prepare-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>report</id>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/main/java/org/apache/nifi/action/Action.java b/src/main/java/org/apache/nifi/action/Action.java
new file mode 100644
index 0000000..44c28a6
--- /dev/null
+++ b/src/main/java/org/apache/nifi/action/Action.java
@@ -0,0 +1,47 @@
+/*
+ * 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.action;
+
+import java.io.Serializable;
+import java.util.Date;
+import org.apache.nifi.action.component.details.ComponentDetails;
+import org.apache.nifi.action.details.ActionDetails;
+
+/**
+ * An action taken on the flow by a user.
+ */
+public interface Action extends Serializable {
+
+    Integer getId();
+
+    Date getTimestamp();
+
+    String getUserIdentity();
+
+    String getSourceId();
+
+    String getSourceName();
+
+    Component getSourceType();
+
+    ComponentDetails getComponentDetails();
+
+    Operation getOperation();
+
+    ActionDetails getActionDetails();
+
+}
diff --git a/src/main/java/org/apache/nifi/action/Component.java b/src/main/java/org/apache/nifi/action/Component.java
new file mode 100644
index 0000000..9a65185
--- /dev/null
+++ b/src/main/java/org/apache/nifi/action/Component.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.action;
+
+/**
+ * Defines possible components for a given action.
+ */
+public enum Component {
+
+    Controller,
+    Processor,
+    InputPort,
+    OutputPort,
+    ProcessGroup,
+    RemoteProcessGroup,
+    Funnel,
+    Connection,
+    ControllerService,
+    ReportingTask,
+    FlowAnalysisRule,
+    FlowRegistryClient,
+    ParameterContext,
+    ParameterProvider,
+    AccessPolicy,
+    User,
+    UserGroup,
+    Label;
+}
diff --git a/src/main/java/org/apache/nifi/action/Operation.java b/src/main/java/org/apache/nifi/action/Operation.java
new file mode 100644
index 0000000..1a199c5
--- /dev/null
+++ b/src/main/java/org/apache/nifi/action/Operation.java
@@ -0,0 +1,55 @@
+/*
+ * 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.action;
+
+/**
+ * Defines possible operations for a given action.
+ */
+public enum Operation {
+
+    Add("Add"),
+    Remove("Remove"),
+    Paste("Paste"),
+    Configure("Configure"),
+    Move("Move"),
+    Disconnect("Disconnect"),
+    Connect("Connect"),
+    Start("Start"),
+    Stop("Stop"),
+    Enable("Enable"),
+    Disable("Disable"),
+    Batch("Batch"),
+    Purge("Purge"),
+    ClearState("Clear State"),
+    StartVersionControl("Start Version Control"),
+    StopVersionControl("Stop Version Control"),
+    CommitLocalChanges("Commit Local Changes"),
+    RevertLocalChanges("Revert Local Changes"),
+    ChangeVersion("Change Version"),
+    RunOnce("RunOnce");
+
+    private final String label;
+
+    Operation(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java b/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java
new file mode 100644
index 0000000..1eb35c1
--- /dev/null
+++ b/src/main/java/org/apache/nifi/action/component/details/ComponentDetails.java
@@ -0,0 +1,26 @@
+/*
+ * 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.action.component.details;
+
+import java.io.Serializable;
+
+/**
+ * Base interface for providing component details to an Action.
+ */
+public interface ComponentDetails extends Serializable {
+
+}
diff --git a/src/main/java/org/apache/nifi/action/details/ActionDetails.java b/src/main/java/org/apache/nifi/action/details/ActionDetails.java
new file mode 100644
index 0000000..75ebd9d
--- /dev/null
+++ b/src/main/java/org/apache/nifi/action/details/ActionDetails.java
@@ -0,0 +1,26 @@
+/*
+ * 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.action.details;
+
+import java.io.Serializable;
+
+/**
+ * Provides additional details about a given action.
+ */
+public interface ActionDetails extends Serializable {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java b/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java
new file mode 100644
index 0000000..2ef6aa8
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/DefaultRunDuration.java
@@ -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.
+ */
+package org.apache.nifi.annotation.behavior;
+
+import java.time.Duration;
+
+public enum DefaultRunDuration {
+    NO_BATCHING(Duration.ZERO),
+    TWENTY_FIVE_MILLIS(Duration.ofMillis(25)),
+    FIFTY_MILLIS(Duration.ofMillis(50)),
+    ONE_HUNDRED_MILLIS(Duration.ofMillis(100)),
+    TWO_HUNDRED_FIFTY_MILLIS(Duration.ofMillis(250)),
+    FIVE_HUNDRED_MILLIS(Duration.ofMillis(500)),
+    ONE_SECOND(Duration.ofSeconds(1)),
+    TWO_SECONDS(Duration.ofSeconds(2));
+
+    private final Duration duration;
+
+    DefaultRunDuration(final Duration duration) {
+        this.duration = duration;
+    }
+
+    public Duration getDuration() {
+        return duration;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.java
new file mode 100644
index 0000000..9670535
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperties.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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a component has more than one dynamic property
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DynamicProperties {
+
+    DynamicProperty[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java
new file mode 100644
index 0000000..5bccd48
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicProperty.java
@@ -0,0 +1,47 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+
+/**
+ * An annotation that may be placed on a {@link ConfigurableComponent} to
+ * indicate that it supports a dynamic property.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DynamicProperty {
+
+    String name();
+
+    String value();
+
+    String description();
+
+    ExpressionLanguageScope expressionLanguageScope() default ExpressionLanguageScope.NONE;
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java b/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java
new file mode 100644
index 0000000..006f6ad
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/DynamicRelationship.java
@@ -0,0 +1,44 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.processor.Relationship;
+
+/**
+ * Annotation to indicate that a {@link Processor} supports dynamic
+ * relationship. A dynamic {@link Relationship} is one where the relationship is
+ * generated based on a user defined {@link PropertyDescriptor}
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DynamicRelationship {
+
+    String name();
+
+    String description();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java b/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java
new file mode 100644
index 0000000..372d80c
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/InputRequirement.java
@@ -0,0 +1,67 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker interface that a Processor can use to indicate whether it accepts, requires, or forbids
+ * input from other Processors. This information is used by the framework in order to ensure that
+ * a Processor is marked as invalid if it is missing necessary input or has input that will be ignored.
+ * This information also is used by the NiFi UI in order to prevent users from making connections
+ * to Processors that don't make sense.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface InputRequirement {
+    Requirement value();
+
+    enum Requirement {
+        /**
+         * This value is used to indicate that the Processor requires input from other Processors
+         * in order to run. As a result, the Processor will not be valid if it does not have any
+         * incoming connections.
+         */
+        INPUT_REQUIRED,
+
+        /**
+         * This value is used to indicate that the Processor will consume data from an incoming
+         * connection but does not require an incoming connection in order to perform its task.
+         * If the {@link InputRequirement} annotation is not present, this is the default value
+         * that is used.
+         */
+        INPUT_ALLOWED,
+
+        /**
+         * This value is used to indicate that the Processor is a "Source Processor" and does
+         * not accept incoming connections. Because the Processor does not pull FlowFiles from
+         * an incoming connection, it can be very confusing for users who create incoming connections
+         * to the Processor. As a result, this value can be used in order to clarify that incoming
+         * connections will not be used. This prevents the user from even creating such a connection.
+         */
+        INPUT_FORBIDDEN;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java b/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java
new file mode 100644
index 0000000..6443383
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/PrimaryNodeOnly.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation a {@link org.apache.nifi.processor.Processor} implementation
+ * can use to indicate that the {@link org.apache.nifi.scheduling.ExecutionNode}
+ * of the processor is to be set to PRIMARY
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface PrimaryNodeOnly {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java
new file mode 100644
index 0000000..7094b92
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttribute.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.processor.Processor Processor} indicating that this
+ * processor reads a specific FlowFile attribute.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ReadsAttribute {
+
+    String attribute();
+
+    String description() default "";
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java
new file mode 100644
index 0000000..fe1f8bd
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/ReadsAttributes.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.processor.Processor Processor} indicating that this
+ * processor reads specific FlowFile attributes.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ReadsAttributes {
+
+    ReadsAttribute[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java b/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java
new file mode 100644
index 0000000..0796199
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/RequiresInstanceClassLoading.java
@@ -0,0 +1,54 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *  Marker annotation a component can use to indicate that the framework should create a new ClassLoader
+ *  for each instance of the component, copying all resources from the component's NARClassLoader to a
+ *  new ClassLoader which will only be used by a given instance of the component.
+ *
+ *  If cloneAncestorResources is set to true, the instance ClassLoader will include ancestor resources up to the
+ *  first ClassLoader containing a controller service API referenced by the component, or up to the Jetty NAR.
+ *
+ *  Example #1 - PutHDFS has this flag set to true and does not reference any controller services, so it will include
+ *  resources from nifi-hadoop-nar, nifi-hadoop-libraries-nar, and nifi-standard-services-api-nar, stopping at nifi-jetty-nar.
+ *
+ *  Example #2 - If PutHDFS referenced an SSLContext and has this flag set to true, then it would include
+ *  resources from nifi-hadoop-nar, nifi-hadoop-libraries-nar, and stop before nifi-standard-services-api-nar.
+ *
+ *  Example #3 - HBaseClientService_1_1_2 does not have this flag set so it defaults to false, and therefore includes
+ *  only resources from the nifi-hbase-client-service-1_1_2-nar.
+ *
+ *  NOTE: When this annotation is used it is important to note that each added instance of the component will increase
+ *  the overall memory footprint more than that of a component without this annotation.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface RequiresInstanceClassLoading {
+
+    boolean cloneAncestorResources() default false;
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java b/src/main/java/org/apache/nifi/annotation/behavior/Restricted.java
new file mode 100644
index 0000000..8411c6c
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/Restricted.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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marks the usage of a component as restricted to users with elevated privileges.
+ * </p>
+ * <p>
+ * A {@code Restricted} component is one that can be used to execute arbitrary unsanitized
+ * code provided by the operator through the NiFi REST API/UI or can be used to obtain
+ * or alter data on the NiFi host system using the NiFi OS credentials. These components
+ * could be used by an otherwise authorized NiFi user to go beyond the intended use of
+ * the application, escalate privilege, or could expose data about the internals of the
+ * NiFi process or the host system. All of these capabilities should be considered
+ * privileged, and admins should be aware of these capabilities and explicitly enable
+ * them for a subset of trusted users.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface Restricted {
+    /**
+     * Provides a description of why the component usage is restricted. If using granular
+     * restrictions, specific explanations should be set in the Restriction.
+     *
+     * @return Description of restriction
+     */
+    String value() default "";
+
+    /**
+     * Provides a listing of specific Restrictions. If unspecified, this component will
+     * require access to restricted components regardless of restrictions.
+     *
+     * @return Array of restrictions
+     */
+    Restriction[] restrictions() default {};
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java b/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java
new file mode 100644
index 0000000..63efc98
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/Restriction.java
@@ -0,0 +1,50 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.RequiredPermission;
+
+/**
+ * Specific restriction for a component. Indicates what the required permission is and why the restriction exists.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface Restriction {
+
+    /**
+     * Provides a listing of RequiredPermissions.
+     *
+     * @return Permission required for this restriction
+     */
+    RequiredPermission requiredPermission();
+
+    /**
+     * Provides an explanation of why the component usage is restricted
+     *
+     * @return Explanation for this restriction
+     */
+    String explanation();
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java b/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java
new file mode 100644
index 0000000..9a64f4b
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SideEffectFree.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}
+ * implementation can use to indicate that its operations on FlowFiles can be
+ * safely repeated across process sessions. If a processor has this annotation
+ * and it allows the framework to manage session commit and rollback then the
+ * framework may elect to cascade a
+ * {@link org.apache.nifi.processor.ProcessSession ProcessSession} given to this
+ * processor's onTrigger method to the onTrigger method of another processor. It
+ * can do this knowing that if something fails along a series of processors
+ * using this same session that it can all be safely rolled back without any ill
+ * effects on external services which could not be rolled back and thus all the
+ * processes could be safely repeated (implied idempotent behavior).
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SideEffectFree {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java b/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java
new file mode 100644
index 0000000..cfcc820
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/Stateful.java
@@ -0,0 +1,57 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+
+/**
+ * <p>
+ * Annotation that a Processor, ReportingTask, FlowAnalysisRule, ParameterProvider, or Controller Service can use to indicate
+ * that the component makes use of the {@link StateManager}. This annotation provides the
+ * user with a description of what information is being stored so that the user is able to
+ * understand what is shown to them and know what they are clearing should they choose to
+ * clear the state. Additionally, the UI will not show any state information to users if
+ * this annotation is not present.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface Stateful {
+    /**
+     * Provides a description of what information is being stored in the {@link StateManager}
+     *
+     * @return Description of information stored
+     */
+    String description();
+
+    /**
+     * Indicates the Scope(s) associated with the State that is stored and retrieved.
+     *
+     * @return Array of scopes for information stored
+     */
+    Scope[] scopes();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java b/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java
new file mode 100644
index 0000000..49136ad
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SupportsBatching.java
@@ -0,0 +1,53 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation a Processor implementation can use to indicate that users
+ * should be able to supply a Batch Duration for the Processor. If a Processor
+ * uses this annotation, it is allowing the Framework to batch
+ * {@link org.apache.nifi.processor.ProcessSession ProcessSession}s' commits, as well as
+ * allowing the Framework to return the same ProcessSession multiple times from
+ * subsequent calls to
+ * {@link org.apache.nifi.processor.ProcessSessionFactory ProcessSessionFactory}.
+ * {@link org.apache.nifi.processor.ProcessSessionFactory#createSession() createSession()}.
+ *
+ * When this Annotation is used, it is important to note that calls to
+ * {@link org.apache.nifi.processor.ProcessSession#commit() ProcessSession.commit()} may
+ * not provide a guarantee that the data has been safely stored in NiFi's
+ * Content Repository or FlowFile Repository. Therefore, it is not appropriate,
+ * for instance, to use this annotation if the Processor will call
+ * ProcessSession.commit() to ensure data is persisted before deleting the data
+ * from a remote source.
+ *
+ * When the defaultDuration parameter is set, the processor is created with the supplied duration time, which can be adjusted afterwards.
+ * The supplied values can be selected from {@link org.apache.nifi.annotation.behavior.DefaultRunDuration}.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SupportsBatching {
+    DefaultRunDuration defaultDuration() default DefaultRunDuration.NO_BATCHING;
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java b/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java
new file mode 100644
index 0000000..9927641
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SupportsSensitiveDynamicProperties.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Component annotation indicating support for dynamic properties that can be designated as sensitive for the purpose of
+ * persistence and framework processing
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SupportsSensitiveDynamicProperties {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java
new file mode 100644
index 0000000..cea3c70
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResource.java
@@ -0,0 +1,26 @@
+/*
+ * 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.annotation.behavior;
+
+/**
+ * Represents a system resource.
+ */
+public enum SystemResource {
+
+    CPU, DISK, MEMORY, NETWORK
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java
new file mode 100644
index 0000000..66c8abb
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsideration.java
@@ -0,0 +1,55 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.components.ConfigurableComponent Component} describes how this component may impact a
+ * system resource based on its configuration.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Repeatable(SystemResourceConsiderations.class)
+public @interface SystemResourceConsideration {
+
+    String DEFAULT_DESCRIPTION = "An instance of this component can cause high usage of this system resource.  " +
+            "Multiple instances or high concurrency settings may result a degradation of performance.";
+
+    /**
+     * The {@link SystemResource SystemResource} which may be affected by this component.
+     *
+     * @return System Resource to be considered
+     */
+    SystemResource resource();
+
+    /**
+     * A description of how this component and its configuration may affect system resource usage.
+     *
+     * @return Description of resource consideration
+     */
+    String description() default DEFAULT_DESCRIPTION;
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.java b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.java
new file mode 100644
index 0000000..cd6c8c2
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/SystemResourceConsiderations.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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.components.ConfigurableComponent Component} describes how this component may impact
+ * system resources based on its configuration.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SystemResourceConsiderations {
+    SystemResourceConsideration[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java
new file mode 100644
index 0000000..974e358
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerSerially.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}
+ * implementation can use to indicate that the Processor is not safe for
+ * concurrent execution of its onTrigger() method. By default, Processors are
+ * assumed to be safe for concurrent execution.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface TriggerSerially {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java
new file mode 100644
index 0000000..db115cb
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenAnyDestinationAvailable.java
@@ -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.
+ */
+package org.apache.nifi.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}
+ * implementation can use to indicate that the Processor is to be triggered if
+ * any of its destinations has available space for incoming FlowFiles. By
+ * default, Processors are triggered only when all destinations report that they
+ * have available space (i.e., none of the outgoing Connections is full).
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface TriggerWhenAnyDestinationAvailable {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java
new file mode 100644
index 0000000..6fac03b
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/TriggerWhenEmpty.java
@@ -0,0 +1,57 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor}
+ * implementation can use to indicate that the Processor should still be
+ * triggered even when it has no data in its work queue.
+ * </p>
+ *
+ * <p>
+ * A Processor is scheduled to be triggered based on its configured Scheduling Period
+ * and Scheduling Strategy. However, when the scheduling period elapses, the Processor
+ * will not be scheduled if it has no work to do. Normally, a Processor is said to have
+ * work to do if one of the following circumstances is true:
+ * </p>
+ *
+ * <ul>
+ * <li>An incoming Connection has data in its queue</li>
+ * <li>The Processor has no incoming Connections.</li>
+ * <li>All incoming Connections are self-loops (both the source and destination of the Connection are the same Processor).
+ * </ul>
+ *
+ * <p>
+ * If the Processor needs to be triggered to run even when the above conditions are all
+ * <code>false</code>, the Processor's class can be annotated with this annotation, which
+ * will cause the Processor to be triggered, even if its incoming queues are empty.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface TriggerWhenEmpty {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java
new file mode 100644
index 0000000..d06af8f
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttribute.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.processor.Processor Processor} indicating that this
+ * processor writes/updates a specific FlowFile attribute.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface WritesAttribute {
+
+    String attribute();
+
+    String description() default "";
+}
diff --git a/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java
new file mode 100644
index 0000000..603eb86
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/behavior/WritesAttributes.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a
+ * {@link org.apache.nifi.processor.Processor Processor} indicating that this
+ * processor writes/updates specific FlowFile attributes.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface WritesAttributes {
+
+    WritesAttribute[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java
new file mode 100644
index 0000000..f1a145a
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSchedule.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotation.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+
+/**
+ * <p>
+ * Marker interface that a Processor can use to configure default settings for the schedule strategy, the period and the number of concurrent tasks.
+ * Marker interface that a ReportingTask can use to configure default settings the  schedule strategy and the period.
+ * Note that the number of Concurrent tasks will be ignored if the annotation @TriggerSerialy is used
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DefaultSchedule {
+
+    SchedulingStrategy strategy() default  SchedulingStrategy.TIMER_DRIVEN;
+    String period() default "0 sec";
+    int concurrentTasks() default 1;
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java
new file mode 100644
index 0000000..09402c7
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/configuration/DefaultSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.annotation.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.logging.LogLevel;
+
+/**
+ * <p>
+ * Marker interface that a Processor can use to configure the yield duration, the  penalty duration and the bulletin log level.
+ * Note that the number of Concurrent tasks will be ignored if the annotation @TriggerSerialy is used
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DefaultSettings {
+    String yieldDuration() default "1 sec";
+    String penaltyDuration() default "30 sec";
+    LogLevel bulletinLevel() default LogLevel.WARN;
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java b/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java
new file mode 100644
index 0000000..5ac0e97
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/CapabilityDescription.java
@@ -0,0 +1,43 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that may be placed on a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider},
+ * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} allowing for a
+ * description to be provided. This description can be provided to a user in
+ * logs, UI, etc.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CapabilityDescription {
+
+    String value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java b/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java
new file mode 100644
index 0000000..e385843
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java
@@ -0,0 +1,46 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.ConfigurableComponent;
+
+/**
+ * Annotation that can be applied to a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} in order to
+ * warn about the deprecation of the component. The deprecation warning is informational only
+ * and doesn't affect the processor run time behavior in any way
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+
+public @interface DeprecationNotice {
+    Class<? extends ConfigurableComponent>[] alternatives() default {};
+
+    String[] classNames() default {};
+
+    String reason() default "";
+}
+
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java
new file mode 100644
index 0000000..562647e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCase.java
@@ -0,0 +1,77 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ *     An annotation that can be used for Processors in order to explain a specific use case that can be
+ *     accomplished using this Processor in conjunction with at least one other Processor.
+ *     For Processors that are able to be used for multiple use cases, the component
+ *     may be annotated with multiple MultiProcessorUseCase annotations.
+ * </p>
+ * <p>
+ *     Note that this annotation differs from {@link UseCase} in that UseCase should describe a use case that is
+ *     accomplished using only the extension that is annotated. In contrast, MultiProcessorUseCase documents a use case
+ *     that is accomplished by using both the Processor that is annotated as well as other Processors.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Repeatable(MultiProcessorUseCases.class)
+public @interface MultiProcessorUseCase {
+
+    /**
+     * A simple 1 (at most 2) sentence description of the use case. This should not include any extraneous details, such
+     * as caveats, examples, etc. Those can be provided using the {@link #notes()} method.
+     *
+     * @return a simple description of the use case
+     */
+    String description();
+
+    /**
+     * Most of the time, 1-2 sentences is sufficient to describe a use case. Those 1-2 sentence should then be returned
+     * by the {@link #description()}. In the event that the description is not sufficient, details may be provided to
+     * further explain, by providing examples, caveats, etc.
+     *
+     * @return any important notes that pertain to the use case
+     */
+    String notes() default "";
+
+    /**
+     * An optional array of keywords that can be associated with the use case.
+     * @return keywords associated with the use case
+     */
+    String[] keywords() default {};
+
+    /**
+     * An array of {@link ProcessorConfiguration}s that are necessary in order to accomplish the task described in this use case.
+     * @return an array of processor configurations
+     */
+    ProcessorConfiguration[] configurations();
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java
new file mode 100644
index 0000000..a7ae09b
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/MultiProcessorUseCases.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An enclosing annotation that can be used in order to use the {@link MultiProcessorUseCase} annotation in a repeated manner.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface MultiProcessorUseCases {
+    MultiProcessorUseCase[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java b/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java
new file mode 100644
index 0000000..ec2ab1f
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/ProcessorConfiguration.java
@@ -0,0 +1,57 @@
+/*
+ * 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.annotation.documentation;
+
+
+import org.apache.nifi.processor.Processor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that can be used in conjunction with {@link MultiProcessorUseCase} in order to specify the different
+ * components that are involved in a given use case.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ProcessorConfiguration {
+
+    /**
+     * Returns the class of the Processor that is to be used in the use case, if it is provided. Either the
+     * Processor Class or the Processor Class Name must be provided.
+     *
+     * @return the Processor's class, or <code>Processor</code> if the processor's classname is specified instead
+     */
+    Class<? extends Processor> processorClass() default Processor.class;
+
+    /**
+     * @return the fully qualified classname of the component
+     */
+    String processorClassName() default "";
+
+    /**
+     * @return an explanation of how the Processor should be configured.
+     */
+    String configuration();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java b/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java
new file mode 100644
index 0000000..5eae159
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/SeeAlso.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.components.ConfigurableComponent;
+
+/**
+ * Annotation that may be placed on a null {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider},
+ * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} that indicates
+ * this component is related to the components listed.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SeeAlso {
+
+    Class<? extends ConfigurableComponent>[] value() default {};
+
+    String[] classNames() default {};
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/Tags.java b/src/main/java/org/apache/nifi/annotation/documentation/Tags.java
new file mode 100644
index 0000000..a22cb28
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/Tags.java
@@ -0,0 +1,44 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that can be applied to a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider},
+ * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} in order to
+ * associate tags (keywords) with the component. These tags do not affect the
+ * component in any way but serve as additional documentation and can be used to
+ * sort/filter Processors.
+ *
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface Tags {
+
+    String[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java b/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java
new file mode 100644
index 0000000..478ba1e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/UseCase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.annotation.documentation;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ *     An annotation that can be used for extension points in order to explain a specific use case that can be
+ *     accomplished using this extension. For components that are able to be used for multiple use cases, the component
+ *     may be annotated with multiple UseCase annotations.
+ * </p>
+ * <p>
+ *     Note that this annotation differs from {@link CapabilityDescription} in that CapabilityDescription should describe the
+ *     general purpose of the extension point. UseCase, on the other hand, documents one very specific use case that
+ *     can be accomplished. Some extension points may use only a single UseCase annotation while others may accomplish
+ *     many use cases.
+ * </p>
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Repeatable(UseCases.class)
+public @interface UseCase {
+
+    /**
+     * A simple 1 (at most 2) sentence description of the use case. This should not include any extraneous details, such
+     * as caveats, examples, etc. Those can be provided using the {@link #notes()} method.
+     *
+     * @return a simple description of the use case
+     */
+    String description();
+
+    /**
+     * Most of the time, 1-2 sentences is sufficient to describe a use case. Those 1-2 sentence should then be returned
+     * by the {@link #description()}. In the event that the description is not sufficient, details may be provided to
+     * further explain, by providing examples, caveats, etc.
+     *
+     * @return any important notes that pertain to the use case
+     */
+    String notes() default "";
+
+    /**
+     * Most Processors specify an InputRequirement of either {@link InputRequirement.Requirement#INPUT_REQUIRED INPUT_REQUIRED}
+     * or {@link InputRequirement.Requirement#INPUT_FORBIDDEN}. However, some Processors use {@link InputRequirement.Requirement#INPUT_ALLOWED}
+     * because some use cases require input while others do not. The inputRequirement here is only relevant for Processors that use
+     * an InputRequirement of {@link InputRequirement.Requirement#INPUT_ALLOWED} and can indicate whether or not the Processor should have
+     * input (aka incoming Connections) for this particular use case.
+     *
+     * @return the {@link InputRequirement} that corresponds to this use case.
+     */
+    InputRequirement.Requirement inputRequirement() default InputRequirement.Requirement.INPUT_ALLOWED;
+
+    /**
+     * An optional array of keywords that can be associated with the use case.
+     * @return keywords associated with the use case
+     */
+    String[] keywords() default {};
+
+    /**
+     * A description of how to configure the extension for this particular use case.
+     * @return a description of how to configure the extension for this particular use case.
+     */
+    String configuration() default "";
+}
diff --git a/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java b/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java
new file mode 100644
index 0000000..f64c8e5
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/documentation/UseCases.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation.documentation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An enclosing annotation that can be used in order to use the {@link UseCase} annotation in a repeated manner.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface UseCases {
+    UseCase[] value();
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java
new file mode 100644
index 0000000..4933a38
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnAdded.java
@@ -0,0 +1,49 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.registry.flow.FlowRegistryClient FlowRegistryClient},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider},
+ * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation
+ * can use to indicate a method should be called whenever the component is added
+ * to the flow. This method will be called once for the entire life of a
+ * component instance.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation are called without any arguments, as all
+ * settings and properties can be assumed to be the defaults.
+ * </p>
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnAdded {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java
new file mode 100644
index 0000000..72c5bd3
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnConfigurationRestored.java
@@ -0,0 +1,59 @@
+/*
+ * 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.annotation.lifecycle;
+
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.processor.ProcessContext;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker Annotation that a Processor, Reporting Task, or Controller Service can use to indicate
+ * that the method with this Annotation should be invoked whenever the component's configuration
+ * is restored after a restart of NiFi.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation are permitted to take no arguments or to take a
+ * single argument. If using a single argument, that argument must be of type
+ * {@link ConfigurationContext} if the component is a ReportingTask or a
+ * ControllerService. If the component is a Processor, then the argument must be
+ * of type {@link ProcessContext}.
+ * </p>
+ *
+ * <p>
+ * Whenever a new component is added to the flow, this method will be called immediately, since
+ * there is no configuration to restore (in this case all configuration has already been restored,
+ * since there is no configuration to restore).
+ * </p>
+ *
+ * @since 0.5.0
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnConfigurationRestored {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java
new file mode 100644
index 0000000..c8d77e3
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnDisabled.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.controller.ConfigurationContext;
+
+/**
+ * <p>
+ * Marker annotation a
+ * {@link org.apache.nifi.controller.ControllerService ControllerService} can
+ * use to indicate a method should be called whenever the service is disabled.
+ * </p>
+ *
+ * <p>
+ * Methods using this annotation are permitted to take zero arguments or to take
+ * a single argument of type {@link ConfigurationContext}. If a method with this
+ * annotation throws a Throwable, a log message and bulletin will be issued for
+ * the service, but the service will still be marked as Disabled. The failing
+ * method will not be called again until the service is enabled and disabled again.
+ * This is done in order to prevent a ControllerService from continually failing
+ * in such a way that the service could not be disabled and updated without
+ * restarting the instance of NiFi.
+ * </p>
+ *
+ * <p>
+ * Note that this annotation will be ignored if applied to a ReportingTask, ParameterProvider or
+ * Processor. For a Controller Service, enabling and disabling are considered
+ * lifecycle events, as the action makes them usable or unusable by other
+ * components. However, for a Processor and a Reporting Task, these are not
+ * lifecycle events but rather a mechanism to allow a component to be excluded
+ * when starting or stopping a group of components.
+ * </p>
+ *
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnDisabled {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java
new file mode 100644
index 0000000..28132c8
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnEnabled.java
@@ -0,0 +1,69 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation a
+ * {@link org.apache.nifi.controller.ControllerService ControllerService} can
+ * use to indicate a method should be called whenever the service is enabled.
+ * Any method that has this annotation will be called every time a user enables
+ * the service. Additionally, each time that NiFi is restarted, if NiFi is
+ * configured to "auto-resume state" and the service is enabled, the method will
+ * be invoked.
+ * </p>
+ *
+ * <p>
+ * Methods using this annotation must take either 0 arguments or a single
+ * argument of type
+ * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}.
+ * </p>
+ *
+ * <p>
+ * If a method with this annotation throws a Throwable, a log message and
+ * bulletin will be issued for the component. In this event, the service will
+ * remain in an 'ENABLING' state and will not be usable. All methods with this
+ * annotation will then be called again after a delay. The service will not be
+ * made available for use until all methods with this annotation have returned
+ * without throwing anything.
+ * </p>
+ *
+ * <p>
+ * Note that this annotation will be ignored if applied to a ReportingTask, ParameterProvider or
+ * Processor. For a Controller Service, enabling and disabling are considered
+ * lifecycle events, as the action makes them usable or unusable by other
+ * components. However, for a Processor and a Reporting Task, these are not
+ * lifecycle events but rather a mechanism to allow a component to be excluded
+ * when starting or stopping a group of components.
+ * </p>
+ *
+ *
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnEnabled {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java
new file mode 100644
index 0000000..172979e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnRemoved.java
@@ -0,0 +1,57 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.processor.ProcessContext;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.registry.flow.FlowRegistryClient FlowRegistryClient},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider},
+ * {@link org.apache.nifi.flowanalysis.FlowAnalysisRule FlowAnalysisRule}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation
+ * can use to indicate a method should be called whenever the component is
+ * removed from the flow. This method will be called once for the entire life of
+ * a component instance. If the method throw any Throwable, that Throwable will
+ * be caught and logged but will not prevent subsequent methods with this
+ * annotation or removal of the component from the flow.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation are permitted to take no arguments or to take a
+ * single argument. If using a single argument, that argument must be of type
+ * {@link ConfigurationContext} if the component is a ReportingTask, a ParameterProvider, or a
+ * ControllerService. If the component is a Processor, then the argument must be
+ * of type {@link ProcessContext}.
+ * </p>
+ *
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnRemoved {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java
new file mode 100644
index 0000000..b320858
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnScheduled.java
@@ -0,0 +1,78 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation
+ * can use to indicate a method should be called whenever the component is
+ * scheduled to run. This will be called before any call to 'onTrigger' and will
+ * be called once each time a Processor or Reporting Task is scheduled to run.
+ * This occurs in one of two ways: either a user clicks to schedule the
+ * component to run, or NiFi is restarted with the "auto-resume state"
+ * configuration set to true (the default value) and the component is already
+ * running.
+ * </p>
+ *
+ * <p>
+ * Methods using this annotation must take either 0 arguments or a single
+ * argument.
+ * </p>
+ *
+ * <p>
+ * If using 1 argument and the component using the annotation is a Processor,
+ * that argument must be of type
+ * {@link org.apache.nifi.processor.ProcessContext ProcessContext}.
+ * </p>
+ *
+ * <p>
+ * If using 1 argument and the component using the annotation is a Reporting
+ * Task, that argument must be of type
+ * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}.
+ * </p>
+ *
+ * <p>
+ * If any method annotated with this annotation throws any Throwable, the
+ * framework will wait a while and then attempt to invoke the method again. This
+ * will continue until the method succeeds, and the component will then be
+ * scheduled to run after this method return successfully.
+ * </p>
+ *
+ * <p><b>Implementation Guidelines:</b>
+ * <ul>
+ *   <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is
+ *       expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li>
+ *   <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period
+ *       of time (the duration is configurable in the properties file), the Thread may be interrupted.</li>
+ *   <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow
+ *       {@link InterruptedException}.</li>
+ * </ul>
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnScheduled {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java
new file mode 100644
index 0000000..88a3ec3
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnShutdown.java
@@ -0,0 +1,53 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.processor.ProcessContext;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor},
+ * {@link org.apache.nifi.controller.ControllerService ControllerService},
+ * {@link org.apache.nifi.parameter.ParameterProvider ParameterProvider}, or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation
+ * can use to indicate a method should be called whenever the flow is being
+ * shutdown. This will be called at most once for each component in a JVM
+ * lifetime. It is not, however, guaranteed that this method will be called on
+ * shutdown, as the service may be killed suddenly.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation are permitted to take either 0 or 1 argument. If
+ * an argument is used, it must be of type {@link ConfigurationContext} if the
+ * component is a ReportingTask or Controller Service, or of type
+ * {@link ProcessContext} if the component is a Processor.
+ * </p>
+ *
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnShutdown {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java
new file mode 100644
index 0000000..647c2ec
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnStopped.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.processor.ProcessContext;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} implementation
+ * can use to indicate that a method should be called whenever the component is
+ * no longer scheduled to run. Methods marked with this annotation will be
+ * invoked each time the component is stopped and will be invoked only after the
+ * last thread has returned from the <code>onTrigger</code> method.
+ * </p>
+ *
+ * <p>
+ * This means that the thread executing in this method will be the only thread
+ * executing in any part of the Processor. However, since other threads may
+ * later execute other parts of the code, member variables must still be
+ * protected appropriately. However, access to multiple variables need not be
+ * atomic.
+ * </p>
+ *
+ * <p>
+ * To indicate that a method should be called immediately when a component is no
+ * longer scheduled to run (as opposed to after all threads have returned from
+ * the <code>onTrigger</code> method), see the {@link OnUnscheduled} annotation.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation are permitted to take either 0 or 1 argument. If
+ * an argument is used, it must be of type {@link ConfigurationContext} if the
+ * component is a ReportingTask or of type {@link ProcessContext} if the
+ * component is a Processor.
+ * </p>
+ *
+ * <p><b>Implementation Guidelines:</b>
+ * <ul>
+ *   <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is
+ *       expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li>
+ *   <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period
+ *       of time (the duration is configurable in the properties file), the Thread may be interrupted.</li>
+ *   <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow
+ *       {@link InterruptedException}.</li>
+ * </ul>
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnStopped {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java b/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java
new file mode 100644
index 0000000..ea041ae
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/lifecycle/OnUnscheduled.java
@@ -0,0 +1,69 @@
+/*
+ * 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.annotation.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation a {@link org.apache.nifi.processor.Processor Processor} or
+ * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} should be
+ * called whenever the component is no longer scheduled to run. Methods marked
+ * with this annotation will be invoked each time the framework is notified to
+ * stop scheduling the component. This method is invoked as other threads are
+ * potentially running. To invoke a method after all threads have finished
+ * processing, see the {@link OnStopped} annotation.
+ * </p>
+ *
+ * <p>
+ * Methods using this annotation must take either 0 arguments or a single
+ * argument.
+ * </p>
+ *
+ * <p>
+ * If using 1 argument and the component using the annotation is a Processor,
+ * that argument must be of type
+ * {@link org.apache.nifi.processor.ProcessContext ProcessContext}.
+ * </p>
+ *
+ * <p>
+ * If using 1 argument and the component using the annotation is a Reporting
+ * Task, that argument must be of type
+ * {@link org.apache.nifi.controller.ConfigurationContext ConfigurationContext}.
+ * </p>
+ *
+ * <p><b>Implementation Guidelines:</b>
+ * <ul>
+ *   <li>Methods with this annotation are expected to perform very quick, short-lived tasks. If the function is
+ *       expensive or long-lived, the logic should be performed in the {@code onTrigger} method instead.</li>
+ *   <li>If a method with this annotation does not return (exceptionally or otherwise) within a short period
+ *       of time (the duration is configurable in the properties file), the Thread may be interrupted.</li>
+ *   <li>Methods that make use of this interface should honor Java's Thread interruption mechanisms and not swallow
+ *       {@link InterruptedException}.</li>
+ * </ul>
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnUnscheduled {
+}
diff --git a/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java b/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java
new file mode 100644
index 0000000..e3ff401
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/notification/OnPrimaryNodeStateChange.java
@@ -0,0 +1,44 @@
+/*
+ * 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.annotation.notification;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Marker annotation that a component can use to indicate that a method should be
+ * called whenever the state of the Primary Node in a cluster has changed.
+ * </p>
+ *
+ * <p>
+ * Methods with this annotation should take either no arguments or one argument of type
+ * {@link PrimaryNodeState}. The {@link PrimaryNodeState} provides context about what changed
+ * so that the component can take appropriate action.
+ * </p>
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface OnPrimaryNodeStateChange {
+
+}
diff --git a/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java b/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java
new file mode 100644
index 0000000..539e5f0
--- /dev/null
+++ b/src/main/java/org/apache/nifi/annotation/notification/PrimaryNodeState.java
@@ -0,0 +1,33 @@
+/*
+ * 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.annotation.notification;
+
+/**
+ * Represents a state change that occurred for the Primary Node of a NiFi cluster.
+ */
+public enum PrimaryNodeState {
+    /**
+     * The node receiving this state has been elected the Primary Node of the NiFi cluster.
+     */
+    ELECTED_PRIMARY_NODE,
+
+    /**
+     * The node receiving this state was the Primary Node but has now had its Primary Node
+     * role revoked.
+     */
+    PRIMARY_NODE_REVOKED;
+}
diff --git a/src/main/java/org/apache/nifi/asset/Asset.java b/src/main/java/org/apache/nifi/asset/Asset.java
new file mode 100644
index 0000000..3e8c026
--- /dev/null
+++ b/src/main/java/org/apache/nifi/asset/Asset.java
@@ -0,0 +1,64 @@
+/*
+ * 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.asset;
+
+import java.io.File;
+import java.util.Optional;
+
+/**
+ * An Asset is a representation of some resource that is necessary in order to run a dataflow.
+ * An Asset is always accessed as a local file.
+ */
+public interface Asset {
+
+    /**
+     * Returns a unique identifier for the Asset
+     *
+     * @return Asset Identifier
+     */
+    String getIdentifier();
+
+    /**
+     * Returns the identifier of the parameter context the Asset belongs to
+     *
+     * @return Parameter Context Identifier
+     */
+    String getParameterContextIdentifier();
+
+    /**
+     * Returns the name of the Asset
+     *
+     * @return Asset Name
+     */
+    String getName();
+
+    /**
+     * Returns the local file that the Asset is associated with
+     *
+     * @return Asset File
+     */
+    File getFile();
+
+    /**
+     * Returns the digest of the contents of the local file that the Asset is associated with.
+     * The digest will not be present when the asset is considered missing and the local file does not exist.
+     *
+     * @return Asset Digest or empty when not found
+     */
+    Optional<String> getDigest();
+}
diff --git a/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java b/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java
new file mode 100644
index 0000000..2b0a061
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/AbstractConfigurableComponent.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public abstract class AbstractConfigurableComponent implements ConfigurableComponent {
+
+    /**
+     * Allows subclasses to perform their own validation on the already set
+     * properties. Since each property is validated as it is set this allows
+     * validation of groups of properties together. Default return is an empty
+     * set.
+     *
+     * This method will be called only when it has been determined that all
+     * property values are valid according to their corresponding
+     * PropertyDescriptor's validators.
+     *
+     * @param validationContext provides a mechanism for obtaining externally
+     * managed values, such as property values and supplies convenience methods
+     * for operating on those values
+     *
+     * @return Collection of ValidationResult objects that will be added to any
+     * other validation findings - may be null
+     */
+    protected Collection<ValidationResult> customValidate(final ValidationContext validationContext) {
+        return Collections.emptySet();
+    }
+
+    /**
+     * @param descriptorName to lookup the descriptor
+     * @return a PropertyDescriptor for the name specified that is fully
+     * populated
+     */
+    @Override
+    public final PropertyDescriptor getPropertyDescriptor(final String descriptorName) {
+        final PropertyDescriptor specDescriptor = new PropertyDescriptor.Builder().name(descriptorName).build();
+        return getPropertyDescriptor(specDescriptor);
+    }
+
+    private PropertyDescriptor getPropertyDescriptor(final PropertyDescriptor specDescriptor) {
+        //check if property supported
+        PropertyDescriptor descriptor = getSupportedPropertyDescriptor(specDescriptor);
+        if (descriptor != null) {
+            return descriptor;
+        }
+
+        descriptor = getSupportedDynamicPropertyDescriptor(specDescriptor.getName());
+        if (descriptor != null && !descriptor.isDynamic()) {
+            descriptor = new PropertyDescriptor.Builder().fromPropertyDescriptor(descriptor).dynamic(true).build();
+        }
+
+        if (descriptor == null) {
+            descriptor = new PropertyDescriptor.Builder().fromPropertyDescriptor(specDescriptor).addValidator(Validator.INVALID).dynamic(true).build();
+        }
+        return descriptor;
+    }
+
+    private PropertyDescriptor getSupportedPropertyDescriptor(final PropertyDescriptor specDescriptor) {
+        final List<PropertyDescriptor> supportedDescriptors = getSupportedPropertyDescriptors();
+        if (supportedDescriptors != null) {
+            for (final PropertyDescriptor desc : supportedDescriptors) { //find actual descriptor
+                if (specDescriptor.equals(desc)) {
+                    return desc;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public final Collection<ValidationResult> validate(final ValidationContext context) {
+        // goes through context properties, should match supported properties + supported dynamic properties
+        final Collection<ValidationResult> results = new ArrayList<>();
+        final Set<PropertyDescriptor> contextDescriptors = context.getProperties().keySet();
+
+        for (final PropertyDescriptor descriptor : contextDescriptors) {
+            // If the property descriptor's dependency is not satisfied, the property does not need to be considered, as it's not relevant to the
+            // component's functionality.
+            final boolean dependencySatisfied = context.isDependencySatisfied(descriptor, this::getPropertyDescriptor);
+            if (!dependencySatisfied) {
+                continue;
+            }
+
+            validateDependencies(descriptor, context, results);
+
+            String value = context.getProperty(descriptor).getValue();
+            if (value == null) {
+                value = descriptor.getDefaultValue();
+            }
+
+            if (value == null && descriptor.isRequired()) {
+                String displayName = descriptor.getDisplayName();
+                ValidationResult.Builder builder = new ValidationResult.Builder().valid(false).input(null).subject(displayName != null ? displayName : descriptor.getName());
+                builder = (displayName != null) ? builder.explanation(displayName + " is required") : builder.explanation(descriptor.getName() + " is required");
+                results.add(builder.build());
+                continue;
+            } else if (value == null) {
+                continue;
+            }
+
+            final ValidationResult result = descriptor.validate(value, context);
+            if (!result.isValid()) {
+                results.add(result);
+            }
+        }
+
+        // only run customValidate if regular validation is successful. This allows Processor developers to not have to check
+        // if values are null or invalid so that they can focus only on the interaction between the properties, etc.
+        if (results.isEmpty()) {
+            final Collection<ValidationResult> customResults = customValidate(context);
+            if (null != customResults) {
+                for (final ValidationResult result : customResults) {
+                    if (!result.isValid()) {
+                        results.add(result);
+                    }
+                }
+            }
+        }
+
+        return results;
+    }
+
+    private void validateDependencies(final PropertyDescriptor descriptor, final ValidationContext context, final Collection<ValidationResult> results) {
+        // Ensure that we don't have any dependencies on non-existent properties.
+        final Set<PropertyDependency> dependencies = descriptor.getDependencies();
+        for (final PropertyDependency dependency : dependencies) {
+            final String dependentPropertyName = dependency.getPropertyName();
+
+            // If there's a supported property descriptor then all is okay.
+            final PropertyDescriptor specDescriptor = new PropertyDescriptor.Builder().name(dependentPropertyName).build();
+            final PropertyDescriptor supportedDescriptor = getSupportedPropertyDescriptor(specDescriptor);
+            if (supportedDescriptor != null) {
+                continue;
+            }
+
+            final PropertyDescriptor dynamicPropertyDescriptor = getSupportedDynamicPropertyDescriptor(dependentPropertyName);
+            if (dynamicPropertyDescriptor == null) {
+                results.add(new ValidationResult.Builder()
+                    .subject(descriptor.getDisplayName())
+                    .valid(false)
+                    .explanation("Property depends on property " + dependentPropertyName + ", which is not a known property")
+                    .build());
+            }
+
+            // Dependent property is supported as a dynamic property. This is okay as long as there is a value set.
+            final PropertyValue value = context.getProperty(dynamicPropertyDescriptor);
+            if (value == null || !value.isSet()) {
+                results.add(new ValidationResult.Builder()
+                    .subject(descriptor.getDisplayName())
+                    .valid(false)
+                    .explanation("Property depends on property " + dependentPropertyName + ", which is not a known property")
+                    .build());
+            }
+        }
+
+    }
+
+    /**
+     * Hook method allowing subclasses to eagerly react to a configuration
+     * change for the given property descriptor. As an alternative to using this
+     * method a processor may simply get the latest value whenever it needs it
+     * and if necessary lazily evaluate it.
+     *
+     * @param descriptor of the modified property
+     * @param oldValue non-null property value (previous)
+     * @param newValue the new property value or if null indicates the property
+     * was removed
+     */
+    @Override
+    public void onPropertyModified(final PropertyDescriptor descriptor, final String oldValue, final String newValue) {
+    }
+
+    /**
+     * <p>
+     * Used to allow subclasses to determine what PropertyDescriptor if any to
+     * use when a property is requested for which a descriptor is not already
+     * registered. By default this method simply returns a null descriptor. By
+     * overriding this method processor implementations can support dynamic
+     * properties since this allows them to register properties on demand. It is
+     * acceptable for a dynamically generated property to indicate it is
+     * required so long as it is understood it is only required once set.
+     * Dynamic properties by definition cannot be required until used.</p>
+     *
+     * <p>
+     * This method should be side effect free in the subclasses in terms of how
+     * often it is called for a given property name because there is guarantees
+     * how often it will be called for a given property name.</p>
+     *
+     * <p>
+     * Default is null.
+     *
+     * @param propertyDescriptorName used to lookup if any property descriptors exist for that name
+     * @return new property descriptor if supported
+     */
+    protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
+        return null;
+    }
+
+    /**
+     * Allows subclasses to register which property descriptor objects are
+     * supported. Default return is an empty set.
+     *
+     * @return PropertyDescriptor objects this processor currently supports
+     */
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public final List<PropertyDescriptor> getPropertyDescriptors() {
+        final List<PropertyDescriptor> supported = getSupportedPropertyDescriptors();
+        return supported == null ? Collections.emptyList() : Collections.unmodifiableList(supported);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof ConfigurableComponent)) {
+            return false;
+        }
+
+        final ConfigurableComponent other = (ConfigurableComponent) obj;
+        return getIdentifier().equals(other.getIdentifier());
+    }
+
+    @Override
+    public int hashCode() {
+        return 235 + getIdentifier().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[id=" + getIdentifier() + "]";
+    }
+
+}
diff --git a/src/main/java/org/apache/nifi/components/AllowableValue.java b/src/main/java/org/apache/nifi/components/AllowableValue.java
new file mode 100644
index 0000000..9cae438
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/AllowableValue.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.util.Objects;
+
+/**
+ * <p>
+ * Represents a valid value for a {@link PropertyDescriptor}
+ * </p>
+ */
+public class AllowableValue implements DescribedValue {
+
+    private final String value;
+    private final String displayName;
+    private final String description;
+
+    /**
+     * Constructs a new AllowableValue with the given value and and the same
+     * display name and no description.
+     *
+     * @param value that is allowed
+     */
+    public AllowableValue(final String value) {
+        this(value, value);
+    }
+
+    /**
+     * Constructs a new AllowableValue with the given value and display name and
+     * no description
+     *
+     * @param value       that is allowed
+     * @param displayName to display for the value
+     * @throws NullPointerException if either argument is null
+     */
+    public AllowableValue(final String value, final String displayName) {
+        this(value, displayName, null);
+    }
+
+    /**
+     * Constructs a new AllowableValue with the given value, display name, and
+     * description
+     *
+     * @param value       that is valid
+     * @param displayName to show for the value
+     * @param description of the value
+     * @throws NullPointerException if identifier or value is null
+     */
+    public AllowableValue(final String value, final String displayName, final String description) {
+        this.value = Objects.requireNonNull(value);
+        this.displayName = Objects.requireNonNull(displayName);
+        this.description = description;
+    }
+
+    public static AllowableValue fromDescribedValue(final DescribedValue describedValue) {
+        if (describedValue instanceof AllowableValue allowableValue) {
+            return allowableValue;
+        }
+
+        return new AllowableValue(describedValue.getValue(), describedValue.getDisplayName(), describedValue.getDescription());
+    }
+
+    /**
+     * @return the value of this AllowableValue
+     */
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * @return a human-readable name for this AllowableValue
+     */
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    /**
+     * @return a description for this value, or <code>null</code> if no
+     * description was provided
+     */
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * @return true if <code>this</code> is equal to <code>obj</code> of <code>obj</code> is the
+     * same object as <code>this</code> or if <code>obj</code> is an instance of
+     * <code>AllowableValue</code> and both have the same value, or if
+     * <code>obj</code> is a String and is equal to
+     * {@link #getValue() this.getValue()}.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof AllowableValue other) {
+            return this.value.equals(other.getValue());
+        } else if (obj instanceof String) {
+            return this.value.equals(obj);
+        }
+
+        return false;
+    }
+
+    /**
+     * @return based solely off of the value
+     */
+    @Override
+    public int hashCode() {
+        return 23984731 + 17 * value.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java b/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java
new file mode 100644
index 0000000..9d36193
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/ClassloaderIsolationKeyProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components;
+
+import org.apache.nifi.context.PropertyContext;
+
+/**
+ * <p>
+ * There are times when a component must be created in such a way that each instance gets its own ClassLoader hierarchy,
+ * rather than sharing the ClassLoader with other components (see {@link org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading @RequiresInstanceClassLoading}).
+ * This, however, can be extremely expensive, as all of the classes must be loaded again for each instance of the component. When thousands of these
+ * components are used in a single flow, the startup time can be great, and it can lead to massive amounts of RAM being required.
+ * </p>
+ *
+ * <p>
+ * For components that do require instance ClassLoading that clones ancestor resources, this interface can be optional implemented by the component.
+ * If the interface is implemented, the component is given the opportunity to return a distinct "key" that can be used to identify instances that may share
+ * the same ClassLoader.
+ * </p>
+ */
+public interface ClassloaderIsolationKeyProvider {
+
+    /**
+     * <p>
+     * Determines the key that identifies a shared ClassLoader that this component may use. Any two instances of the same component that return
+     * the same key may be assigned the same base ClassLoader (though it is not guaranteed that this will be the case).
+     * </p>
+     *
+     * <p>
+     * If a subsequent call to this method returns a different value, the component will be recreated with a different ClassLoader.
+     * </p>
+     *
+     * <p>
+     * Implementation Note: for components that implement this interface, this method will be called often. Therefore, performance characteristics
+     * of the implementation are critical. The method is expected to return the value of a configured property or derive a value to return based off
+     * of the values of a few properties. Accessing a remote resource, is too expensive. If the necessary computation is non-trivial, then it should be
+     * performed out of band and the pre-computed value simply returned by this method.
+     * </p>
+     *
+     * @param context the PropertyContext that can be used for determining the key
+     * @return a distinct key that can be used to indicate which shared ClassLoader is allowed to be used
+     */
+    String getClassloaderIsolationKey(PropertyContext context);
+
+}
diff --git a/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java b/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java
new file mode 100644
index 0000000..9f2ab77
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components;
+
+public class ConfigVerificationResult {
+    private final Outcome outcome;
+    private final String verificationStepName;
+    private final String explanation;
+
+    private ConfigVerificationResult(final Builder builder) {
+        outcome = builder.outcome;
+        verificationStepName = builder.verificationStepName;
+        explanation = builder.explanation;
+    }
+
+    public Outcome getOutcome() {
+        return outcome;
+    }
+
+    public String getVerificationStepName() {
+        return verificationStepName;
+    }
+
+    public String getExplanation() {
+        return explanation;
+    }
+
+    @Override
+    public String toString() {
+        return "ConfigVerificationResult[" +
+            "outcome=" + outcome +
+            ", verificationStepName='" + verificationStepName + "'" +
+            ", explanation='" + explanation + "']";
+    }
+
+    public static class Builder {
+        private Outcome outcome = Outcome.SKIPPED;
+        private String verificationStepName = "Unknown Step Name";
+        private String explanation;
+
+        public Builder outcome(final Outcome outcome) {
+            this.outcome = outcome;
+            return this;
+        }
+
+        public Builder verificationStepName(final String verificationStepName) {
+            this.verificationStepName = verificationStepName;
+            return this;
+        }
+
+        public Builder explanation(final String explanation) {
+            this.explanation = explanation;
+            return this;
+        }
+
+        public ConfigVerificationResult build() {
+            return new ConfigVerificationResult(this);
+        }
+    }
+
+    public enum Outcome {
+        SUCCESSFUL,
+
+        FAILED,
+
+        SKIPPED;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/ConfigurableComponent.java b/src/main/java/org/apache/nifi/components/ConfigurableComponent.java
new file mode 100644
index 0000000..4c5537e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/ConfigurableComponent.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.nifi.annotation.lifecycle.OnConfigurationRestored;
+
+public interface ConfigurableComponent {
+
+    /**
+     * Validates a set of properties, returning ValidationResults for any
+     * invalid properties. All defined properties will be validated. If they are
+     * not included in the purposed configuration, the default value will
+     * be used.
+     *
+     * @param context of validation
+     * @return Collection of validation result objects for any invalid findings
+     *         only. If the collection is empty then the component is valid. Guaranteed
+     *         non-null
+     */
+    Collection<ValidationResult> validate(ValidationContext context);
+
+    /**
+     * @param name to lookup the descriptor
+     * @return the PropertyDescriptor with the given name, if it exists;
+     * otherwise, returns <code>null</code>
+     */
+    PropertyDescriptor getPropertyDescriptor(String name);
+
+    /**
+     * Hook method allowing subclasses to eagerly react to a configuration
+     * change for the given property descriptor. This method will be invoked
+     * regardless of property validity. As an alternative to using this method,
+     * a component may simply get the latest value whenever it needs it and if
+     * necessary lazily evaluate it. Any throwable that escapes this method will
+     * simply be ignored.
+     *
+     * When NiFi is restarted, this method will be called for each 'dynamic' property that is
+     * added, as well as for each property that is not set to the default value. I.e., if the
+     * Properties are modified from the default values. If it is undesirable for your use case
+     * to react to properties being modified in this situation, you can add the {@link OnConfigurationRestored}
+     * annotation to a method - this will allow the Processor to know when configuration has
+     * been restored, so that it can determine whether or not to perform some action in the
+     * onPropertyModified method.
+     *
+     * @param descriptor the descriptor for the property being modified
+     * @param oldValue the value that was previously set, or null if no value
+     *            was previously set for this property
+     * @param newValue the new property value or if null indicates the property
+     *            was removed
+     */
+    void onPropertyModified(PropertyDescriptor descriptor, String oldValue, String newValue);
+
+    /**
+     * Returns a {@link List} of all {@link PropertyDescriptor}s that this
+     * component supports.
+     *
+     * @return PropertyDescriptor objects this component currently supports
+     */
+    List<PropertyDescriptor> getPropertyDescriptors();
+
+    /**
+     * @return the unique identifier that the framework assigned to this
+     * component
+     */
+    String getIdentifier();
+
+}
diff --git a/src/main/java/org/apache/nifi/components/DescribedValue.java b/src/main/java/org/apache/nifi/components/DescribedValue.java
new file mode 100644
index 0000000..87ffc2f
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/DescribedValue.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+/**
+ * Describes a component property value with display name and description.
+ */
+public interface DescribedValue {
+
+    /**
+     * @return the property value as a string
+     */
+    String getValue();
+
+    /**
+     * @return the property display name as a string
+     */
+    String getDisplayName();
+
+    /**
+     * @return the proeprty description as a string
+     */
+    String getDescription();
+}
diff --git a/src/main/java/org/apache/nifi/components/PortFunction.java b/src/main/java/org/apache/nifi/components/PortFunction.java
new file mode 100644
index 0000000..a746eb3
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/PortFunction.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components;
+
+public enum PortFunction {
+    /**
+     * Port functions as a standard port, transferring FlowFiles to all outgoing connections.
+     */
+    STANDARD,
+
+    /**
+     * Port denotes that the invocation of the stateless flow has failed. If run using the Standard Engine, will operate as a Standard port. If running
+     * using the Stateless Engine, the transaction is rolled back, and any input FlowFiles will be transferred to this Port.
+     */
+    FAILURE;
+}
diff --git a/src/main/java/org/apache/nifi/components/PropertyDependency.java b/src/main/java/org/apache/nifi/components/PropertyDependency.java
new file mode 100644
index 0000000..275248e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/PropertyDependency.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+public class PropertyDependency {
+    private final String propertyName;
+    private final String displayName;
+    private final Set<String> dependentValues;
+
+    /**
+     * Creates a dependency that is satisfied if any value is set for the property with the given name
+     * @param propertyName the name of the property that is depended upon
+     * @param propertyDisplayName the display name of the property that is depended upon
+     */
+    public PropertyDependency(final String propertyName, final String propertyDisplayName) {
+        this.propertyName = Objects.requireNonNull(propertyName);
+        this.displayName = propertyDisplayName == null ? propertyName : propertyDisplayName;
+        this.dependentValues = null;
+    }
+
+    /**
+     * Creates a dependency that is satisfied only if the property with the given name has a value that is in the given set of dependent values
+     * @param propertyName the name of the property that is depended upon
+     * @param propertyDisplayName the display name of the property that is depended upon
+     * @param dependentValues the values that satisfy the dependency
+     */
+    public PropertyDependency(final String propertyName, final String propertyDisplayName, final Set<String> dependentValues) {
+        this.propertyName = Objects.requireNonNull(propertyName);
+        this.displayName = propertyDisplayName == null ? propertyName : propertyDisplayName;
+        this.dependentValues = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(dependentValues)));
+    }
+
+    /**
+     * @return the name of the property that is depended upon
+     */
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * @return the display name of the property that is depended upon
+     */
+    public String getPropertyDisplayName() {
+        return displayName;
+    }
+
+    /**
+     * @return the Set of values that satisfy the dependency
+     */
+    public Set<String> getDependentValues() {
+        return dependentValues;
+    }
+
+    @Override
+    public String toString() {
+        return "PropertyDependency[propertyName=" + propertyName + ", displayName=" + displayName + ", dependentValues=" + dependentValues + "]";
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final PropertyDependency that = (PropertyDependency) o;
+        return Objects.equals(getPropertyName(), that.getPropertyName())
+                && Objects.equals(getDependentValues(), that.getDependentValues());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getPropertyName(), getDependentValues());
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/PropertyDescriptor.java b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
new file mode 100644
index 0000000..8f61545
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/PropertyDescriptor.java
@@ -0,0 +1,911 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceDefinition;
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.components.resource.StandardResourceDefinition;
+import org.apache.nifi.components.resource.StandardResourceReferenceFactory;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An immutable object for holding information about a type of component
+ * property.
+ */
+public final class PropertyDescriptor implements Comparable<PropertyDescriptor> {
+
+    public static final PropertyDescriptor NULL_DESCRIPTOR = new PropertyDescriptor.Builder().name("").build();
+
+    /**
+     * The proper name for the property. This is the primary mechanism of
+     * comparing equality.
+     */
+    private final String name;
+
+    /**
+     * The name that should be displayed to user when referencing this property
+     */
+    private final String displayName;
+
+    /**
+     * And explanation of the meaning of the given property. This description is
+     * meant to be displayed to a user or simply provide a mechanism of
+     * documenting intent.
+     */
+    private final String description;
+    /**
+     * The default value for this property
+     */
+    private final String defaultValue;
+    /**
+     * The allowable values for this property. If empty then the allowable
+     * values are not constrained
+     */
+    private final List<AllowableValue> allowableValues;
+    /**
+     * Determines whether the property is required for this processor
+     */
+    private final boolean required;
+    /**
+     * indicates that the value for this property should be considered sensitive
+     * and protected whenever stored or represented
+     */
+    private final boolean sensitive;
+    /**
+     * indicates whether this property well-known for this processor or is
+     * user-defined
+     */
+    private final boolean dynamic;
+    /**
+     * indicates whether or nor this property will evaluate expression language
+     * against the flow file attributes
+     */
+    private final ExpressionLanguageScope expressionLanguageScope;
+    /**
+     * indicates whether or not this property represents resources that should be added
+     * to the classpath and used for loading native libraries for this instance of the component
+     */
+    private final boolean dynamicallyModifiesClasspath;
+
+    /**
+     * the interface of the {@link ControllerService} that this Property refers
+     * to; will be null if not identifying a ControllerService.
+     */
+    private final Class<? extends ControllerService> controllerServiceDefinition;
+
+    /**
+     * The validators that should be used whenever an attempt is made to set
+     * this property value. Any allowable values specified will be checked first
+     * and any validators specified will be ignored.
+     */
+    private final List<Validator> validators;
+
+    /**
+     * The list of dependencies that this property has on other properties
+     */
+    private final Set<PropertyDependency> dependencies;
+
+    /**
+     * The definition of the resource(s) that this property references
+     */
+    private final ResourceDefinition resourceDefinition;
+
+    protected PropertyDescriptor(final Builder builder) {
+        this.displayName = builder.displayName == null ? builder.name : builder.displayName;
+        this.name = builder.name;
+        this.description = builder.description;
+        this.defaultValue = builder.defaultValue;
+        this.allowableValues = builder.allowableValues == null ? null : List.copyOf(builder.allowableValues);
+        this.required = builder.required;
+        this.sensitive = builder.sensitive;
+        this.dynamic = builder.dynamic;
+        this.dynamicallyModifiesClasspath = builder.dynamicallyModifiesClasspath;
+        this.expressionLanguageScope = builder.expressionLanguageScope;
+        this.controllerServiceDefinition = builder.controllerServiceDefinition;
+        this.validators = List.copyOf(builder.validators);
+        this.dependencies = builder.dependencies == null ? Collections.emptySet() : Set.copyOf(builder.dependencies);
+        this.resourceDefinition = builder.resourceDefinition;
+    }
+
+    @Override
+    public int compareTo(final PropertyDescriptor o) {
+        if (o == null) {
+            return -1;
+        }
+        return getName().compareTo(o.getName());
+    }
+
+    /**
+     * Validates the given input against this property descriptor's validator.
+     * If this descriptor has a set of allowable values then the given value is
+     * only checked against the allowable values.
+     *
+     * @param input   the value to validate
+     * @param context the context of validation
+     * @return the result of validating the input
+     */
+    public ValidationResult validate(final String input, final ValidationContext context) {
+        ValidationResult lastResult = Validator.INVALID.validate(this.name, input, context);
+
+        if (allowableValues != null && !allowableValues.isEmpty()) {
+            final ConstrainedSetValidator csValidator = new ConstrainedSetValidator(allowableValues);
+            final ValidationResult csResult = csValidator.validate(this.name, input, context);
+
+            if (csResult.isValid()) {
+                lastResult = csResult;
+            } else {
+                return csResult;
+            }
+        }
+
+        final ResourceDefinition resourceDefinition = getResourceDefinition();
+        if (resourceDefinition != null) {
+            final Validator validator = new ResourceDefinitionValidator(resourceDefinition, this.expressionLanguageScope);
+            final ValidationResult result = validator.validate(this.name, input, context);
+            if (!result.isValid()) {
+                return result;
+            }
+
+            lastResult = result;
+        }
+
+        for (final Validator validator : validators) {
+            lastResult = validator.validate(this.name, input, context);
+            if (!lastResult.isValid()) {
+                break;
+            }
+        }
+
+        if (getControllerServiceDefinition() != null) {
+            final ControllerService service = context.getControllerServiceLookup().getControllerService(input);
+            if (service == null) {
+                return new ValidationResult.Builder()
+                        .input(input)
+                        .subject(getDisplayName())
+                        .valid(false)
+                        .explanation("Property references a Controller Service that does not exist")
+                        .build();
+            } else {
+                return new ValidationResult.Builder()
+                        .valid(true)
+                        .build();
+            }
+        }
+
+        return lastResult;
+    }
+
+    public static final class Builder {
+
+        private String displayName = null;
+        private String name = null;
+        private String description = "";
+        private String defaultValue = null;
+        private List<AllowableValue> allowableValues = null;
+        private Set<PropertyDependency> dependencies = null;
+        private boolean required = false;
+        private boolean sensitive = false;
+        private ExpressionLanguageScope expressionLanguageScope = ExpressionLanguageScope.NONE;
+        private boolean dynamic = false;
+        private boolean dynamicallyModifiesClasspath = false;
+        private Class<? extends ControllerService> controllerServiceDefinition;
+        private ResourceDefinition resourceDefinition;
+        private List<Validator> validators = new ArrayList<>();
+
+        public Builder fromPropertyDescriptor(final PropertyDescriptor specDescriptor) {
+            this.name = specDescriptor.name;
+            this.displayName = specDescriptor.displayName;
+            this.description = specDescriptor.description;
+            this.defaultValue = specDescriptor.defaultValue;
+            this.allowableValues = specDescriptor.allowableValues == null ? null : new ArrayList<>(specDescriptor.allowableValues);
+            this.required = specDescriptor.required;
+            this.sensitive = specDescriptor.sensitive;
+            this.dynamic = specDescriptor.dynamic;
+            this.dynamicallyModifiesClasspath = specDescriptor.dynamicallyModifiesClasspath;
+            this.expressionLanguageScope = specDescriptor.expressionLanguageScope;
+            this.controllerServiceDefinition = specDescriptor.getControllerServiceDefinition();
+            this.validators = new ArrayList<>(specDescriptor.validators);
+            this.dependencies = new HashSet<>(specDescriptor.dependencies);
+            this.resourceDefinition = specDescriptor.resourceDefinition;
+            return this;
+        }
+
+        /**
+         * Sets a unique id for the property. This field is optional and if not
+         * specified the PropertyDescriptor's name will be used as the
+         * identifying attribute. However, by supplying an id, the
+         * PropertyDescriptor's name can be changed without causing problems.
+         * This is beneficial because it allows a User Interface to represent
+         * the name differently.
+         *
+         * @param displayName of the property
+         * @return the builder
+         */
+        public Builder displayName(final String displayName) {
+            if (null != displayName) {
+                this.displayName = displayName;
+            }
+
+            return this;
+        }
+
+        /**
+         * Sets the property name.
+         *
+         * @param name of the property
+         * @return the builder
+         */
+        public Builder name(final String name) {
+            if (null != name) {
+                this.name = name;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the scope of the expression language evaluation
+         *
+         * @param expressionLanguageScope scope of the expression language evaluation
+         * @return the builder
+         */
+        public Builder expressionLanguageSupported(final ExpressionLanguageScope expressionLanguageScope) {
+            this.expressionLanguageScope = expressionLanguageScope;
+            return this;
+        }
+
+        /**
+         * @param description of the property
+         * @return the builder
+         */
+        public Builder description(final String description) {
+            if (null != description) {
+                this.description = description;
+            }
+            return this;
+        }
+
+        /**
+         * Specifies the initial value and the default value that will be used if the user does not specify a value.
+         * <p>
+         * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads)
+         * and this value is not one of those Allowable Values, an Exception will be thrown.
+         * If the Allowable Values have been set, the default value should be set to
+         * the "Value" of the {@link DescribedValue} object (see {@link DescribedValue#getValue()}).
+         * There's an overload available for this (see {@link #defaultValue(DescribedValue)}).
+         *
+         * @param value default value
+         * @return the builder
+         */
+        public Builder defaultValue(final String value) {
+            if (null != value) {
+                this.defaultValue = value;
+            }
+            return this;
+        }
+
+        /**
+         * Specifies the initial value and the default value that will be used if the user does not specify a value.
+         * <p>
+         * Sets the default value to the "Value" of the {@link DescribedValue} object.
+         * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads)
+         * and this value is not one of those Allowable Values, an Exception will be thrown.
+         * In case there is not a restricted set of Allowable Values {@link #defaultValue(String)} may be used.
+         *
+         * @param value default value holder
+         * @return the builder
+         */
+        public Builder defaultValue(final DescribedValue value) {
+            return defaultValue(value != null ? value.getValue() : null);
+        }
+
+        /**
+         * Clears the initial value and default value from this Property.
+         *
+         * @return the builder
+         */
+        public Builder clearDefaultValue() {
+            this.defaultValue = null;
+            return this;
+        }
+
+        public Builder dynamic(final boolean dynamic) {
+            this.dynamic = dynamic;
+            return this;
+        }
+
+        /**
+         * Specifies that the value of this property represents one or more resources that the
+         * framework should add to the classpath of as well as consider when looking for native
+         * libraries for the given component.
+         * <p>
+         * NOTE: If a component contains a PropertyDescriptor where dynamicallyModifiesClasspath is set to true,
+         * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will
+         * be loaded by a separate InstanceClassLoader for each processor instance.<br/>
+         * It also allows to load native libraries from the extra classpath.
+         * <p>
+         * One can choose to omit the annotation. In this case the loading of native libraries from the extra classpath
+         * is not supported.
+         * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an
+         * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually
+         * to load required classes on an instance-by-instance basis
+         * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example).
+         * <p>
+         * Any property descriptor that dynamically modifies the classpath should also make use of the {@link #identifiesExternalResource(ResourceCardinality, ResourceType, ResourceType...)} method
+         * to indicate that the property descriptor references external resources and optionally restrict which types of resources and how many resources the property allows.
+         *
+         * @param dynamicallyModifiesClasspath whether or not this property should be used by the framework to modify the classpath
+         * @return the builder
+         */
+        public Builder dynamicallyModifiesClasspath(final boolean dynamicallyModifiesClasspath) {
+            this.dynamicallyModifiesClasspath = dynamicallyModifiesClasspath;
+            return this;
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         *
+         * @param values constrained set of values
+         * @return the builder
+         */
+        public Builder allowableValues(final Set<String> values) {
+            if (null != values) {
+                this.allowableValues = values.stream().map(AllowableValue::new).toList();
+            }
+            return this;
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         * <p>
+         * Uses the {@link Enum#name()} of each value as "Value" for the {@link AllowableValue}.
+         * In case the enum value is a {@link DescribedValue}, uses the information provided instead
+         * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}).
+         *
+         * @param <E> Type of Allowable Value
+         * @param values constrained set of values
+         * @return the builder
+         */
+        public <E extends Enum<E>> Builder allowableValues(final E[] values) {
+            if (null != values) {
+                this.allowableValues = Arrays.stream(values)
+                        .map(enumValue -> enumValue instanceof DescribedValue describedValue
+                                ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name()))
+                        .toList();
+            }
+            return this;
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         * <p>
+         * Uses the {@link Enum#name()} of each value from {@link Class#getEnumConstants()} as "Value" for the {@link AllowableValue}.
+         * In case the enum value is a {@link DescribedValue}, uses the information provided instead
+         * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}).
+         *
+         * @param enumClass an enum class that contains a set of values and optionally implements the DescribedValue interface
+         * @param <E>       generic parameter for an enum class, that may implement the DescribedValue interface
+         * @return the builder
+         */
+        public <E extends Enum<E>> Builder allowableValues(final Class<E> enumClass) {
+            return allowableValues(enumClass.getEnumConstants());
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         * <p>
+         * Uses the {@link Enum#name()} of each value of the {@link EnumSet} as "Value" for the {@link AllowableValue}.
+         * In case the enum value is a {@link DescribedValue}, uses the information provided instead
+         * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}).
+         *
+         * @param enumValues an enum set that contains a set of values and optionally implements the DescribedValue interface
+         * @param <E>        generic parameter for an enum class, that may implement the DescribedValue interface
+         * @return the builder
+         */
+        public <E extends Enum<E>> Builder allowableValues(final EnumSet<E> enumValues) {
+            if (null != enumValues) {
+                this.allowableValues = enumValues.stream()
+                        .map(enumValue -> enumValue instanceof DescribedValue describedValue
+                                ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name()))
+                        .toList();
+            }
+            return this;
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         *
+         * @param values constrained set of values
+         * @return the builder
+         */
+        public Builder allowableValues(final String... values) {
+            if (null != values) {
+                this.allowableValues = Arrays.stream(values).map(AllowableValue::new).toList();
+            }
+            return this;
+        }
+
+        /**
+         * Sets the Allowable Values for this Property.
+         * <p>
+         * Uses the information provided by each {@link DescribedValue} (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()},
+         * {@link DescribedValue#getDescription()}) to populate the {@link AllowableValue}s.
+         *
+         * @param values constrained set of values
+         * @return the builder
+         */
+        public Builder allowableValues(final DescribedValue... values) {
+            if (null != values) {
+                this.allowableValues = Arrays.stream(values).map(AllowableValue::fromDescribedValue).toList();
+            }
+            return this;
+        }
+
+        /**
+         * Clears all Allowable Values from this Property
+         *
+         * @return the builder
+         */
+        public Builder clearAllowableValues() {
+            this.allowableValues = null;
+            return this;
+        }
+
+        /**
+         * @param required true if yes; false otherwise
+         * @return the builder
+         */
+        public Builder required(final boolean required) {
+            this.required = required;
+            return this;
+        }
+
+        /**
+         * @param sensitive true if sensitive; false otherwise
+         * @return the builder
+         */
+        public Builder sensitive(final boolean sensitive) {
+            this.sensitive = sensitive;
+            return this;
+        }
+
+        /**
+         * @param validator for the property
+         * @return the builder
+         */
+        public Builder addValidator(final Validator validator) {
+            if (validator != null) {
+                validators.add(validator);
+            }
+            return this;
+        }
+
+        /**
+         * Clear all Validators from this Property
+         *
+         * @return the builder
+         */
+        public Builder clearValidators() {
+            validators.clear();
+            return this;
+        }
+
+        /**
+         * Specifies that this property provides the identifier of a Controller
+         * Service that implements the given interface
+         *
+         * @param controllerServiceDefinition the interface that is implemented
+         *                                    by the Controller Service
+         * @return the builder
+         */
+        public Builder identifiesControllerService(final Class<? extends ControllerService> controllerServiceDefinition) {
+            if (controllerServiceDefinition != null) {
+                this.controllerServiceDefinition = controllerServiceDefinition;
+            }
+            return this;
+        }
+
+        private boolean isValueAllowed(final String value) {
+            if (allowableValues == null || value == null) {
+                return true;
+            }
+
+            for (final AllowableValue allowableValue : allowableValues) {
+                if (allowableValue.getValue().equals(value)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Specifies that this property references one or more resources that are external to NiFi that the component is meant to consume.
+         * Any property descriptor that identifies an external resource will be automatically validated against the following rules:
+         * <ul>
+         *     <li>If the ResourceCardinality is SINGLE, the given property value must be a file, a directory, or a URL that uses a protocol of http/https/file.</li>
+         *     <li>The given resourceTypes dictate which types of input are allowed. For example, if <code>identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE)</code>
+         *     is used, the input must be a regular file. If <code>identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY)</code> is used, then the input
+         *     must be exactly one file OR directory.
+         *     </li>
+         *     <li>If the ResourceCardinality is MULTIPLE, the given property value may consist of one or more resources, each separted by a comma and optional white space.</li>
+         * </ul>
+         * <p>
+         * Generally, any property descriptor that makes use of the {@link #dynamicallyModifiesClasspath(boolean)} method to dynamically update its classpath should also
+         * make use of this method, specifying which types of resources are allowed and how many.
+         *
+         * @param cardinality             specifies how many resources the property should allow
+         * @param resourceType            the type of resource that is allowed
+         * @param additionalResourceTypes if more than one type of resource is allowed, any resource type in addition to the given resource type may be provided
+         * @return the builder
+         */
+        public Builder identifiesExternalResource(final ResourceCardinality cardinality, final ResourceType resourceType, final ResourceType... additionalResourceTypes) {
+            Objects.requireNonNull(cardinality);
+            Objects.requireNonNull(resourceType);
+
+            final Set<ResourceType> resourceTypes = new HashSet<>();
+            resourceTypes.add(resourceType);
+            resourceTypes.addAll(Arrays.asList(additionalResourceTypes));
+
+            this.resourceDefinition = new StandardResourceDefinition(cardinality, resourceTypes);
+            return this;
+        }
+
+        /**
+         * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a non-null value.
+         * Furthermore, if one or more explicit Allowable Values are provided, this Property will not be relevant unless the given Property's value is equal to one of the given Allowable Values.
+         * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied.
+         * <p>
+         * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given
+         * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for
+         * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component
+         * will still be valid, because the value of this property is irrelevant.
+         * <p>
+         * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid.
+         *
+         * @param property        the property that must be set in order for this property to become relevant
+         * @param dependentValues the possible values for the given property for which this Property is relevant
+         * @return the builder
+         */
+        public Builder dependsOn(final PropertyDescriptor property, final AllowableValue... dependentValues) {
+            if (dependencies == null) {
+                dependencies = new HashSet<>();
+            }
+
+            if (dependentValues.length == 0) {
+                dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName()));
+            } else {
+                final Set<String> dependentValueSet = new HashSet<>();
+                for (final AllowableValue value : dependentValues) {
+                    dependentValueSet.add(value.getValue());
+                }
+
+                dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName(), dependentValueSet));
+            }
+
+            return this;
+        }
+
+        /**
+         * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given
+         * <code>String</code> arguments.
+         * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied.
+         * <p>
+         * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given
+         * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for
+         * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component
+         * will still be valid, because the value of this property is irrelevant.
+         * <p>
+         * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid.
+         *
+         * @param property                  the property that must be set in order for this property to become relevant
+         * @param firstDependentValue       the first value for the given property for which this Property is relevant
+         * @param additionalDependentValues any other values for the given property for which this Property is relevant
+         * @return the builder
+         */
+        public Builder dependsOn(final PropertyDescriptor property, final String firstDependentValue, final String... additionalDependentValues) {
+            final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1];
+            dependentValues[0] = new AllowableValue(firstDependentValue);
+            int i = 1;
+            for (final String additionalDependentValue : additionalDependentValues) {
+                dependentValues[i++] = new AllowableValue(additionalDependentValue);
+            }
+
+            return dependsOn(property, dependentValues);
+        }
+
+        /**
+         * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given
+         * {@link DescribedValue} arguments.
+         * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied.
+         * <p>
+         * In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given
+         * Described Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for
+         * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component
+         * will still be valid, because the value of this property is irrelevant.
+         * <p>
+         * If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid.
+         *
+         * @param property                  the property that must be set in order for this property to become relevant
+         * @param firstDependentValue       the first value for the given property for which this Property is relevant
+         * @param additionalDependentValues any other values for the given property for which this Property is relevant
+         * @return the builder
+         */
+        public Builder dependsOn(final PropertyDescriptor property, final DescribedValue firstDependentValue, final DescribedValue... additionalDependentValues) {
+            final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1];
+            dependentValues[0] = AllowableValue.fromDescribedValue(firstDependentValue);
+            int i = 1;
+            for (final DescribedValue additionalDependentValue : additionalDependentValues) {
+                dependentValues[i++] = AllowableValue.fromDescribedValue(additionalDependentValue);
+            }
+
+            return dependsOn(property, dependentValues);
+        }
+
+        /**
+         * Clear all Dependencies from this Property
+         *
+         * @return the builder
+         */
+        public Builder clearDependsOn() {
+            this.dependencies = new HashSet<>();
+            return this;
+        }
+
+        /**
+         * @return a PropertyDescriptor as configured
+         * @throws IllegalStateException if allowable values are configured but
+         *                               no default value is set, or the default value is not contained within
+         *                               the allowable values.
+         */
+        public PropertyDescriptor build() {
+            if (name == null) {
+                throw new IllegalStateException("Must specify a name");
+            }
+            if (!isValueAllowed(defaultValue)) {
+                throw new IllegalStateException("Default value [" + defaultValue + "] is not in the set of allowable values");
+            }
+
+            return new PropertyDescriptor(this);
+        }
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public boolean isRequired() {
+        return required;
+    }
+
+    public boolean isSensitive() {
+        return sensitive;
+    }
+
+    public boolean isDynamic() {
+        return dynamic;
+    }
+
+    public boolean isExpressionLanguageSupported() {
+        return !expressionLanguageScope.equals(ExpressionLanguageScope.NONE);
+    }
+
+    public ExpressionLanguageScope getExpressionLanguageScope() {
+        return expressionLanguageScope;
+    }
+
+    public boolean isDynamicClasspathModifier() {
+        return dynamicallyModifiesClasspath;
+    }
+
+    public Class<? extends ControllerService> getControllerServiceDefinition() {
+        return controllerServiceDefinition;
+    }
+
+    public List<Validator> getValidators() {
+        return validators;
+    }
+
+    public List<AllowableValue> getAllowableValues() {
+        return allowableValues;
+    }
+
+    public Set<PropertyDependency> getDependencies() {
+        return dependencies;
+    }
+
+    public ResourceDefinition getResourceDefinition() {
+        return resourceDefinition;
+    }
+
+    @Override
+    public boolean equals(final Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof PropertyDescriptor otherPropertyDescriptor) {
+            return this.name.equals(otherPropertyDescriptor.name);
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return 287 + this.name.hashCode() * 47;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + displayName + "]";
+    }
+
+    private static final class ConstrainedSetValidator implements Validator {
+
+        private static final String POSITIVE_EXPLANATION = "Given value found in allowed set";
+        private static final String NEGATIVE_EXPLANATION = "Given value not found in allowed set '%1$s'";
+        private static final String VALUE_DELIMITER = ", ";
+        private final String validStrings;
+        private final Collection<String> validValues;
+
+        /**
+         * Constructs a validator that will check if the given value is in the
+         * given set.
+         *
+         * @param validValues values which are acceptible
+         * @throws NullPointerException if the given validValues is null
+         */
+        private ConstrainedSetValidator(final Collection<AllowableValue> validValues) {
+            this.validValues = validValues.stream().map(AllowableValue::getValue).toList();
+            this.validStrings = String.join(VALUE_DELIMITER, this.validValues);
+        }
+
+        @Override
+        public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
+            final ValidationResult.Builder builder = new ValidationResult.Builder();
+            builder.input(input);
+            builder.subject(subject);
+            if (validValues.contains(input)) {
+                builder.valid(true);
+                builder.explanation(POSITIVE_EXPLANATION);
+            } else {
+                builder.valid(false);
+                builder.explanation(String.format(NEGATIVE_EXPLANATION, validStrings));
+            }
+            return builder.build();
+        }
+    }
+
+    private static class ResourceDefinitionValidator implements Validator {
+        private final ResourceDefinition resourceDefinition;
+        private final ExpressionLanguageScope expressionLanguageScope;
+
+        public ResourceDefinitionValidator(final ResourceDefinition resourceDefinition, final ExpressionLanguageScope expressionLanguageScope) {
+            this.resourceDefinition = resourceDefinition;
+            this.expressionLanguageScope = expressionLanguageScope;
+        }
+
+        @Override
+        public ValidationResult validate(final String subject, final String configuredInput, final ValidationContext context) {
+            final ValidationResult.Builder resultBuilder = new ValidationResult.Builder()
+                    .input(configuredInput)
+                    .subject(subject);
+
+            if (configuredInput == null) {
+                return resultBuilder.valid(false)
+                        .explanation("No value specified")
+                        .build();
+            }
+
+            // If Expression Language is supported and is used in the property value, we cannot perform validation against the configured
+            // input unless the Expression Language is expressly limited to only env/syst properties variables. In that case, we can evaluate
+            // it and then validate the value after evaluating the Expression Language.
+            String input = configuredInput;
+            if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(configuredInput)) {
+                if (expressionLanguageScope != null && expressionLanguageScope == ExpressionLanguageScope.ENVIRONMENT) {
+                    input = context.newPropertyValue(configuredInput).evaluateAttributeExpressions().getValue();
+                    resultBuilder.input(input);
+                } else {
+                    return resultBuilder.valid(true)
+                            .explanation("Expression Language is present, so validation of property value cannot be performed")
+                            .build();
+                }
+            }
+
+            // If the property can be text, then there's nothing to validate. Anything that is entered may be valid.
+            // This will be improved in the future, by allowing the user to specify the type of resource that is being referenced.
+            // Until then, we will simply require that the component perform any necessary validation.
+            final boolean allowsText = resourceDefinition.getResourceTypes().contains(ResourceType.TEXT);
+            if (allowsText) {
+                return resultBuilder.valid(true)
+                        .explanation("Property allows for Resource Type of Text, so validation of property value cannot be performed")
+                        .build();
+            }
+
+            final String[] splits = input.split(",");
+            if (resourceDefinition.getCardinality() == ResourceCardinality.SINGLE && splits.length > 1) {
+                return resultBuilder.valid(false)
+                        .explanation("Property only supports a single Resource but " + splits.length + " resources were specified")
+                        .build();
+            }
+
+            final Set<ResourceType> resourceTypes = resourceDefinition.getResourceTypes();
+            final List<String> nonExistentResources = new ArrayList<>();
+
+            int count = 0;
+            for (final String split : splits) {
+                final ResourceReference resourceReference = new StandardResourceReferenceFactory().createResourceReference(split, resourceDefinition);
+                if (resourceReference == null) {
+                    continue;
+                }
+
+                count++;
+
+                final boolean accessible = resourceReference.isAccessible();
+                if (!accessible) {
+                    nonExistentResources.add(resourceReference.getLocation());
+                    continue;
+                }
+
+                if (!resourceTypes.contains(resourceReference.getResourceType())) {
+                    return resultBuilder.valid(false)
+                            .explanation("Specified Resource is a " + resourceReference.getResourceType().name() + " but this property does not allow this type of resource")
+                            .build();
+                }
+            }
+
+            if (count == 0) {
+                return resultBuilder.valid(false)
+                        .explanation("No resources were specified")
+                        .build();
+            }
+
+            if (!nonExistentResources.isEmpty()) {
+                return resultBuilder.valid(false)
+                        .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentResources)
+                        .build();
+            }
+
+            return resultBuilder.valid(true)
+                    .build();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/PropertyValue.java b/src/main/java/org/apache/nifi/components/PropertyValue.java
new file mode 100644
index 0000000..4e52c54
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/PropertyValue.java
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.components.resource.ResourceReferences;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.expression.AttributeValueDecorator;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.registry.EnvironmentVariables;
+
+/**
+ * <p>
+ * A PropertyValue provides a mechanism whereby the currently configured value
+ * of a processor property can be obtained in different forms.
+ * </p>
+ */
+public interface PropertyValue {
+
+    /**
+     * @return the raw property value as a string
+     */
+    String getValue();
+
+    /**
+     * @return an integer representation of the property value, or
+     * <code>null</code> if not set
+     * @throws NumberFormatException if not able to parse
+     */
+    Integer asInteger();
+
+    /**
+     * @return a Long representation of the property value, or <code>null</code>
+     * if not set
+     * @throws NumberFormatException if not able to parse
+     */
+    Long asLong();
+
+    /**
+     * @return a Boolean representation of the property value, or
+     * <code>null</code> if not set
+     */
+    Boolean asBoolean();
+
+    /**
+     * @return a Float representation of the property value, or
+     * <code>null</code> if not set
+     * @throws NumberFormatException if not able to parse
+     */
+    Float asFloat();
+
+    /**
+     * @return a Double representation of the property value, of
+     * <code>null</code> if not set
+     * @throws NumberFormatException if not able to parse
+     */
+    Double asDouble();
+
+    /**
+     * @param timeUnit specifies the TimeUnit to convert the time duration into
+     * @return a Long value representing the value of the configured time period
+     * in terms of the specified TimeUnit; if the property is not set, returns
+     * <code>null</code>
+     */
+    Long asTimePeriod(TimeUnit timeUnit);
+
+    /**
+     * Returns the value as a Duration
+     *
+     * @return a Duration representing the value, or <code>null</code> if the value is unset
+     */
+    Duration asDuration();
+
+    /**
+     *
+     * @param dataUnit specifies the DataUnit to convert the data size into
+     * @return a Long value representing the value of the configured data size
+     * in terms of the specified DataUnit; if hte property is not set, returns
+     * <code>null</code>
+     */
+    Double asDataSize(DataUnit dataUnit);
+
+    /**
+     * @return the ControllerService whose identifier is the raw value of
+     * <code>this</code>, or <code>null</code> if either the value is not set or
+     * the value does not identify a ControllerService
+     */
+    ControllerService asControllerService();
+
+    /**
+     * @param <T> the generic type of the controller service
+     * @param serviceType the class of the Controller Service
+     * @return the ControllerService whose identifier is the raw value of the
+     * <code>this</code>, or <code>null</code> if either the value is not set or
+     * the value does not identify a ControllerService. The object returned by
+     * this method is explicitly cast to type specified, if the type specified
+     * is valid. Otherwise, throws an IllegalArgumentException
+     *
+     * @throws IllegalArgumentException if the value of <code>this</code> points
+     * to a ControllerService but that service is not of type
+     * <code>serviceType</code> or if <code>serviceType</code> references a
+     * class that is not an interface
+     */
+    <T extends ControllerService> T asControllerService(Class<T> serviceType) throws IllegalArgumentException;
+
+    /**
+     * @return a ResourceReference for the configured property value, or <code>null</code> if no value was specified, or if the property references multiple resources.
+     * @see #asResources()
+     */
+    ResourceReference asResource();
+
+    /**
+     * @return a ResourceReferences for the configured property value. If no property value is set, a ResourceReferences will be returned that references no resources.
+     * I.e., this method will never return <code>null</code>.
+     */
+    ResourceReferences asResources();
+
+    /**
+     * Returns the property value as one of the configured allowableValues, see {@link PropertyDescriptor.Builder#allowableValues(Class)}
+     * <p>
+     * The {@link Class#getEnumConstants()} of the provided enum are searched for an entry matching the value of <code>this</code>.
+     * In case an enum value is a {@link DescribedValue}, uses the defined {@link DescribedValue#getValue()} for comparison.
+     * Otherwise, the {@link Enum#name()} is used.
+     *
+     * @param <E> the generic type of the enum used as allowableValues
+     * @param enumType the class of the enum used as allowableValues
+     * @return the matching enum entry, or <code>null</code> if the value is not set.
+     * @throws IllegalArgumentException if no enum entry matching the value of <code>this</code> is found.
+     */
+    <E extends Enum<E>> E asAllowableValue(Class<E> enumType) throws IllegalArgumentException;
+
+    /**
+     * @return <code>true</code> if the user has configured a value, or if the
+     * {@link PropertyDescriptor} for the associated property has a default
+     * value, <code>false</code> otherwise
+     */
+    boolean isSet();
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language;
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @return a PropertyValue with the new value is returned, supporting call
+     * chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions() throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language;
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param attributes a Map of attributes that the Expression can reference.
+     * These will take precedence over any underlying env/syst properties values.
+     *
+     * @return a PropertyValue with the new value
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(Map<String, String> attributes) throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language.
+     * The supplied decorator is then given a chance to decorate the value, and
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param attributes a Map of attributes that the Expression can reference.
+     * These will take precedence over any variables in any underlying variable
+     * registry.
+     * @param decorator the decorator to use in order to update the values
+     * returned after variable substitution and expression language evaluation.
+     *
+     * @return a PropertyValue with the new value
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(Map<String, String> attributes, AttributeValueDecorator decorator) throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language;
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param flowFile to evaluate attributes of. It's flow file properties and
+     * then flow file attributes will take precedence over any underlying
+     * env/syst properties.
+     * @return a PropertyValue with the new value is returned, supporting call
+     * chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(FlowFile flowFile) throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language;
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param flowFile to evaluate attributes of. It's flow file properties and
+     * then flow file attributes will take precedence over any underlying
+     * env/syst properties.
+     * @param additionalAttributes a Map of additional attributes that the
+     * Expression can reference. These attributes will take precedence over any
+     * conflicting attributes in the provided flowfile or any underlying
+     * env/syst properties.
+     *
+     * @return a PropertyValue with the new value is returned, supporting call
+     * chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes) throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language;
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param flowFile to evaluate attributes of. It's flow file properties and
+     * then flow file attributes will take precedence over any underlying
+     * env/syst properties.
+     * @param additionalAttributes a Map of additional attributes that the
+     * Expression can reference. These attributes will take precedence over any
+     * conflicting attributes in the provided flowfile or any underlying
+     * env/syst properties.
+     * @param decorator the decorator to use in order to update the values
+     * returned after variable substitution and expression language evaluation.
+     *
+     * @return a PropertyValue with the new value is returned, supporting call
+     * chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes, AttributeValueDecorator decorator) throws ProcessException;
+
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression
+     * Language; a PropertyValue with the new value is then returned, supporting
+     * call chaining.
+     * </p>
+     *
+     * @param flowFile to evaluate attributes of
+     * @param additionalAttributes a Map of additional attributes that the Expression can reference. If entries in
+     * this Map conflict with entries in the FlowFile's attributes, the entries in this Map are given a higher priority.
+     * @param decorator the decorator to use in order to update the values returned by the Expression Language
+     * @param stateValues a Map of the state values to be referenced explicitly by specific state accessing functions
+     *
+     * @return a PropertyValue with the new value is returned, supporting call
+     * chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or evaluating
+     * the Expression against the given attributes causes an Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(FlowFile flowFile, Map<String, String> additionalAttributes, AttributeValueDecorator decorator, Map<String, String> stateValues)
+            throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language.
+     * The supplied decorator is then given a chance to decorate the value, and
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param decorator the decorator to use in order to update the values
+     * returned after variable substitution and expression language evaluation.
+     * @return a PropertyValue with the new value is then returned, supporting
+     * call chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(AttributeValueDecorator decorator) throws ProcessException;
+
+    /**
+     * <p>
+     * Replaces values in the Property Value using the NiFi Expression Language.
+     * The supplied decorator is then given a chance to decorate the value, and
+     * a PropertyValue with the new value is then returned, supporting call
+     * chaining. Before executing the expression language statement any
+     * variables names found within any underlying {@link EnvironmentVariables} will
+     * be substituted with their values.
+     * </p>
+     *
+     * @param flowFile to evaluate expressions against
+     * @param decorator the decorator to use in order to update the values
+     * returned after variable substitution and expression language evaluation.
+     *
+     *
+     * @return a PropertyValue with the new value is then returned, supporting
+     * call chaining
+     *
+     * @throws ProcessException if the Expression cannot be compiled or
+     * evaluating the Expression against the given attributes causes an
+     * Exception to be thrown
+     */
+    PropertyValue evaluateAttributeExpressions(FlowFile flowFile, AttributeValueDecorator decorator) throws ProcessException;
+
+    /**
+     * <p>
+     * Indicates whether the value of the property uses Expression Language.
+     * </p>
+     *
+     * @return <code>true</code> if the property value makes use of the Expression Language, <code>false</code> otherwise.
+     */
+    boolean isExpressionLanguagePresent();
+}
diff --git a/src/main/java/org/apache/nifi/components/RequiredPermission.java b/src/main/java/org/apache/nifi/components/RequiredPermission.java
new file mode 100644
index 0000000..81c9ded
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/RequiredPermission.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.util.Arrays;
+
+/**
+ * Enumeration of permission required for annotated components
+ */
+public enum RequiredPermission {
+    READ_FILESYSTEM("read-filesystem", "read filesystem"),
+    WRITE_FILESYSTEM("write-filesystem", "write filesystem"),
+    READ_DISTRIBUTED_FILESYSTEM("read-distributed-filesystem", "read distributed filesystem"),
+    WRITE_DISTRIBUTED_FILESYSTEM("write-distributed-filesystem", "write distributed filesystem"),
+    EXECUTE_CODE("execute-code", "execute code"),
+    ACCESS_KEYTAB("access-keytab", "access keytab"),
+    ACCESS_TICKET_CACHE("access-ticket-cache", "access ticket cache"),
+    ACCESS_ENVIRONMENT_CREDENTIALS("access-environment-credentials", "access environment credentials"),
+    EXPORT_NIFI_DETAILS("export-nifi-details", "export nifi details"),
+    REFERENCE_REMOTE_RESOURCES("reference-remote-resources", "reference remote resources");
+
+    private String permissionIdentifier;
+    private String permissionLabel;
+
+    RequiredPermission(String permissionIdentifier, String permissionLabel) {
+        this.permissionIdentifier = permissionIdentifier;
+        this.permissionLabel = permissionLabel;
+    }
+
+    public String getPermissionIdentifier() {
+        return permissionIdentifier;
+    }
+
+    public String getPermissionLabel() {
+        return permissionLabel;
+    }
+
+    public static RequiredPermission valueOfPermissionIdentifier(final String permissionIdentifier) {
+        return Arrays.stream(values())
+                .filter(candidate -> candidate.getPermissionIdentifier().equals(permissionIdentifier))
+                .findFirst().orElse(null);
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/ValidationContext.java b/src/main/java/org/apache/nifi/components/ValidationContext.java
new file mode 100644
index 0000000..031d39a
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/ValidationContext.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.expression.ExpressionLanguageCompiler;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+public interface ValidationContext extends PropertyContext {
+
+    /**
+     * @return the {@link ControllerServiceLookup} which can be used to obtain
+     * Controller Services
+     */
+    ControllerServiceLookup getControllerServiceLookup();
+
+    /**
+     * @param controllerService to lookup the validation context of
+     * @return a ValidationContext that is appropriate for validating the given
+     * {@link ControllerService}
+     */
+    ValidationContext getControllerServiceValidationContext(ControllerService controllerService);
+
+    /**
+     * @return a new {@link ExpressionLanguageCompiler} that can be used to
+     * compile and evaluate Attribute Expressions
+     */
+    ExpressionLanguageCompiler newExpressionLanguageCompiler();
+
+    /**
+     * @param value to make a PropertyValue object for
+     * @return a PropertyValue that represents the given value
+     */
+    PropertyValue newPropertyValue(String value);
+
+    /**
+     * @return a Map of all configured Properties
+     */
+    Map<PropertyDescriptor, String> getProperties();
+
+    /**
+     * @return the currently configured Annotation Data
+     */
+    String getAnnotationData();
+
+    /**
+     * There are times when the framework needs to consider a component valid,
+     * even if it references an invalid ControllerService. This method will
+     * return <code>false</code> if the component is to be considered valid even
+     * if the given Controller Service is referenced and is invalid.
+     *
+     * @param service to check if validation is required
+     * @return <code>false</code> if the component is to be considered valid
+     * even if the given Controller Service is referenced and is invalid
+     */
+    boolean isValidationRequired(ControllerService service);
+
+    /**
+     * @param value to test whether expression language is present
+     * @return <code>true</code> if the given value contains a NiFi Expression
+     * Language expression, <code>false</code> if it does not
+     */
+    boolean isExpressionLanguagePresent(String value);
+
+    /**
+     * @param propertyName to test whether expression language is supported
+     * @return <code>true</code> if the property with the given name supports
+     * the NiFi Expression Language, <code>false</code> if the property does not
+     * support the Expression Language or is not a valid property name
+     */
+    boolean isExpressionLanguageSupported(String propertyName);
+
+    /**
+     * Returns the identifier of the ProcessGroup that the component being validated lives in
+     *
+     * @return the identifier of the ProcessGroup that the component being validated lives in
+     */
+    String getProcessGroupIdentifier();
+
+    /**
+     * Returns a Collection containing the names of all Parameters that are referenced by the property with the given name
+     *
+     * @param propertyName Name of property for which to get referenced parameters
+     * @return  a Collection containing the names of all Parameters that are referenced by the property with the given name
+     */
+    Collection<String> getReferencedParameters(String propertyName);
+
+    /**
+     * @param parameterName the name of the Parameter
+     * @return <code>true</code> if a Parameter with the given name is defined in the currently selected Parameter Context
+     */
+    boolean isParameterDefined(String parameterName);
+
+    /**
+     * Returns <code>true</code> if a Parameter with the given name is defined and has a non-null value, <code>false</code> if either the Parameter
+     * is not defined or the Parameter is defined but has a value of <code>null</code>.
+     * @param parameterName the name of the parameter
+     * @return <code>true</code> if the Parameter is defined and has a non-null value, false otherwise
+     */
+    boolean isParameterSet(String parameterName);
+
+    /**
+     * Determines whether or not the dependencies of the given Property Descriptor are satisfied.
+     * If the given Property Descriptor has no dependency on another property, then the dependency is satisfied.
+     * If there is at least one dependency, then all dependencies must be satisfied.
+     * In order for a dependency to be considered satisfied, all of the following must be true:
+     * <ul>
+     *     <li>The property that is depended upon has all of its dependencies satisfied.</li>
+     *     <li>If the given Property Descriptor depends on a given AllowableValue, then the property that is depended upon has a value that falls within the given range of Allowable Values for
+     *     the dependency.</li>
+     * </ul>
+     *
+     * @param propertyDescriptor the property descriptor
+     * @param propertyDescriptorLookup a lookup for converting from a property name to the property descriptor with that name
+     * @return <code>true</code> if all dependencies of the given property descriptor are satisfied, <code>false</code> otherwise
+     */
+    default boolean isDependencySatisfied(PropertyDescriptor propertyDescriptor, Function<String, PropertyDescriptor> propertyDescriptorLookup) {
+        return isDependencySatisfied(propertyDescriptor, propertyDescriptorLookup, new HashSet<>());
+    }
+
+    private boolean isDependencySatisfied(final PropertyDescriptor propertyDescriptor, final Function<String, PropertyDescriptor> propertyDescriptorLookup, final Set<String> propertiesSeen) {
+        final Set<PropertyDependency> dependencies = propertyDescriptor.getDependencies();
+        if (dependencies.isEmpty()) {
+            return true;
+        }
+
+        final boolean added = propertiesSeen.add(propertyDescriptor.getName());
+        if (!added) {
+            return false;
+        }
+
+        try {
+            for (final PropertyDependency dependency : dependencies) {
+                final String dependencyName = dependency.getPropertyName();
+
+                // Check if the property being depended upon has its dependencies satisfied.
+                final PropertyDescriptor dependencyDescriptor = propertyDescriptorLookup.apply(dependencyName);
+                if (dependencyDescriptor == null) {
+                    return false;
+                }
+
+                final PropertyValue propertyValue = getProperty(dependencyDescriptor);
+                final String dependencyValue = propertyValue == null ? dependencyDescriptor.getDefaultValue() : propertyValue.getValue();
+                if (dependencyValue == null) {
+                    return false;
+                }
+
+                final boolean transitiveDependencySatisfied = isDependencySatisfied(dependencyDescriptor, propertyDescriptorLookup, propertiesSeen);
+                if (!transitiveDependencySatisfied) {
+                    return false;
+                }
+
+                // Check if the property being depended upon is set to one of the values that satisfies this dependency.
+                // If the dependency has no dependent values, then any non-null value satisfies the dependency.
+                // The value is already known to be non-null due to the check above.
+                final Set<String> dependentValues = dependency.getDependentValues();
+                if (dependentValues != null && !dependentValues.contains(dependencyValue)) {
+                    return false;
+                }
+            }
+
+            return true;
+        } finally {
+            propertiesSeen.remove(propertyDescriptor.getName());
+        }
+    }
+
+    /**
+     * Determines whether or not incoming and outgoing connections should be validated.
+     * If <code>true</code>, then the validation should verify that all Relationships either have one or more connections that include the Relationship,
+     * or that the Relationship is auto-terminated.
+     * Additionally, if <code>true</code>, then any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_REQUIRED}
+     * should be invalid unless it has an incoming (non-looping) connection, and any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_FORBIDDEN}
+     * should be invalid if it does have any incoming connection.
+     *
+     * @return <code>true</code> if Connections should be validated, <code>false</code> if Connections should be ignored
+     */
+    default boolean isValidateConnections() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/ValidationResult.java b/src/main/java/org/apache/nifi/components/ValidationResult.java
new file mode 100644
index 0000000..ca23c1d
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/ValidationResult.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.components;
+
+import java.util.Objects;
+
+/**
+ * Immutable - thread safe
+ */
+public class ValidationResult {
+
+    private final String subject;
+    private final String input;
+    private final String explanation;
+    private final boolean valid;
+
+    protected ValidationResult(final Builder builder) {
+        this.subject = builder.subject;
+        this.input = builder.input;
+        this.explanation = builder.explanation;
+        this.valid = builder.valid;
+    }
+
+    /**
+     * @return true if current result is valid; false otherwise
+     */
+    public boolean isValid() {
+        return this.valid;
+    }
+
+    /**
+     * @return this input value that was tested for validity
+     */
+    public String getInput() {
+        return this.input;
+    }
+
+    /**
+     * @return an explanation of the validation result
+     */
+    public String getExplanation() {
+        return this.explanation;
+    }
+
+    /**
+     * @return the item being validated
+     */
+    public String getSubject() {
+        return this.subject;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 3;
+        hash = 79 * hash + Objects.hashCode(this.subject);
+        hash = 79 * hash + Objects.hashCode(this.input);
+        hash = 79 * hash + Objects.hashCode(this.explanation);
+        hash = 79 * hash + (this.valid ? 1 : 0);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ValidationResult other = (ValidationResult) obj;
+        if (!Objects.equals(this.subject, other.subject)) {
+            return false;
+        }
+        if (!Objects.equals(this.input, other.input)) {
+            return false;
+        }
+        if (!Objects.equals(this.explanation, other.explanation)) {
+            return false;
+        }
+        if (this.valid != other.valid) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        if (input == null) {
+            return String.format("'%s' is %s because %s", subject, valid ? "valid" : "invalid", explanation);
+        } else {
+            return String.format("'%s' validated against '%s' is %s because %s", subject, input, valid ? "valid" : "invalid", explanation);
+        }
+    }
+
+    public static final class Builder {
+
+        private boolean valid = false;
+        private String input = null;
+        private String explanation = "";
+        private String subject = "";
+
+        /**
+         * Defaults to false
+         *
+         * @param valid true if is valid; false otherwise
+         * @return the builder
+         */
+        public Builder valid(final boolean valid) {
+            this.valid = valid;
+            return this;
+        }
+
+        /**
+         * Defaults to empty string
+         *
+         * @param input what was validated
+         * @return the builder
+         */
+        public Builder input(final String input) {
+            if (null != input) {
+                this.input = input;
+            }
+            return this;
+        }
+
+        /**
+         * Defaults to empty string
+         *
+         * @param explanation of validation result
+         * @return the builder
+         */
+        public Builder explanation(final String explanation) {
+            if (null != explanation) {
+                this.explanation = explanation;
+            }
+            return this;
+        }
+
+        /**
+         * Defaults to empty string
+         *
+         * @param subject the thing that was validated
+         * @return the builder
+         */
+        public Builder subject(final String subject) {
+            if (null != subject) {
+                this.subject = subject;
+            }
+            return this;
+        }
+
+        public ValidationResult build() {
+            return new ValidationResult(this);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/Validator.java b/src/main/java/org/apache/nifi/components/Validator.java
new file mode 100644
index 0000000..babdc41
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/Validator.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.components;
+
+public interface Validator {
+
+    /**
+     * Validator object providing validation behavior in which validation always
+     * fails
+     */
+    Validator INVALID = new Validator() {
+        @Override
+        public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
+            return new ValidationResult.Builder().subject(subject).explanation(String.format("'%s' is not a supported property or has no Validator associated with it", subject)).input(input).build();
+        }
+    };
+
+    /**
+     * Validator object providing validation behavior in which validation always
+     * passes
+     */
+    Validator VALID = new Validator() {
+        @Override
+        public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
+            return new ValidationResult.Builder().subject(subject).input(input).valid(true).build();
+        }
+    };
+
+    /**
+     * @param subject what is being validated
+     * @param input the string to be validated
+     * @param context the ValidationContext to use when validating properties
+     * @return ValidationResult
+     * @throws NullPointerException of given input is null
+     */
+    ValidationResult validate(String subject, String input, ValidationContext context);
+}
diff --git a/src/main/java/org/apache/nifi/components/VersionedComponent.java b/src/main/java/org/apache/nifi/components/VersionedComponent.java
new file mode 100644
index 0000000..53fc62c
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/VersionedComponent.java
@@ -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.
+ */
+
+package org.apache.nifi.components;
+
+import java.util.Optional;
+
+public interface VersionedComponent {
+
+    /**
+     * @return the unique identifier that maps this component to a component that is versioned
+     *         in a Flow Registry or has been imported, or <code>Optional.empty</code> if this component has not
+     *         been saved to a Flow Registry or imported.
+     */
+    Optional<String> getVersionedComponentId();
+
+    /**
+     * Updates the versioned component identifier
+     *
+     * @param versionedComponentId the identifier of the versioned component
+     *
+     * @throws IllegalStateException if this component is already under version control with a different ID and
+     *             the given ID is not null
+     */
+    void setVersionedComponentId(String versionedComponentId);
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java b/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java
new file mode 100644
index 0000000..65fc167
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/FileResourceReference.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+
+public class FileResourceReference implements ResourceReference {
+    private final File file;
+    private final ResourceType resourceType;
+
+    public FileResourceReference(final File file) {
+        this.file = Objects.requireNonNull(file);
+        this.resourceType = file.isDirectory() ? ResourceType.DIRECTORY : ResourceType.FILE;
+    }
+
+    @Override
+    public File asFile() {
+        return file;
+    }
+
+    @Override
+    public URL asURL() {
+        try {
+            return file.toURI().toURL();
+        } catch (final MalformedURLException e) {
+            throw new AssertionError("File " + file.getAbsolutePath() + " cannot be represented as a URL"); // we won't encounter this.
+        }
+    }
+
+    @Override
+    public InputStream read() throws IOException {
+        if (resourceType != ResourceType.FILE) {
+            throw new FileNotFoundException("Could not read from file with name " + file.getAbsolutePath() + " because that references a directory");
+        }
+
+        return new FileInputStream(file);
+    }
+
+    @Override
+    public boolean isAccessible() {
+        return file.exists() && file.canRead();
+    }
+
+    @Override
+    public String getLocation() {
+        return file.getAbsolutePath();
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return resourceType;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final FileResourceReference that = (FileResourceReference) o;
+        return Objects.equals(file, that.file)
+            && resourceType == that.resourceType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(file, resourceType);
+    }
+
+    @Override
+    public String toString() {
+        return "FileResourceReference[file=" + file + ", resourceType=" + resourceType + "]";
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java b/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java
new file mode 100644
index 0000000..1563df7
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceCardinality.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+/**
+ * Indicates the cardinality of how many resources can be referenced by a given property.
+ */
+public enum ResourceCardinality {
+
+    /**
+     * Exactly one resource must be specified
+     */
+    SINGLE,
+
+    /**
+     * One or more resources may be supplied, as a comma-separated list
+     */
+    MULTIPLE;
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceContext.java b/src/main/java/org/apache/nifi/components/resource/ResourceContext.java
new file mode 100644
index 0000000..bf02c59
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceContext.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import org.apache.nifi.components.PropertyDescriptor;
+
+/**
+ * Provides the context in which a Reference Reference is to be evaluated
+ */
+public interface ResourceContext {
+    /**
+     * @return a ResourceReferenceFactory that can be used to parse a property value into a {@link ResourceReference} or {@link ResourceReferences}
+     */
+    ResourceReferenceFactory getResourceReferenceFactory();
+
+    /**
+     * @return the PropertyDescriptor that describes the property whose value may be a resource
+     */
+    PropertyDescriptor getPropertyDescriptor();
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.java b/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.java
new file mode 100644
index 0000000..b91065e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceDefinition.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.components.resource;
+
+import java.util.Set;
+
+/**
+ * Defines the number and types of resources that allowed to be referenced by a component property
+ */
+public interface ResourceDefinition {
+
+    /**
+     * Specifies the number of resources that should be reference-able by a component property
+     * @return the cardinality for the number of resources that should be referenced
+     */
+    ResourceCardinality getCardinality();
+
+    /**
+     * @return the types of resources that are allowed to be referenced
+     */
+    Set<ResourceType> getResourceTypes();
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReference.java b/src/main/java/org/apache/nifi/components/resource/ResourceReference.java
new file mode 100644
index 0000000..49b95d3
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceReference.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * A reference to a Resource that is identified by a property value
+ */
+public interface ResourceReference {
+
+    /**
+     * @return a file representation of the resource, or <code>null</code> if the Resource cannot be represented as a File
+     */
+    File asFile();
+
+    /**
+     * @return a URL representation of the resource, or <code>null</code> if the Resource cannot be represented as a URL
+     */
+    URL asURL();
+
+    /**
+     * @return an InputStream to read the contents of the resource
+     *
+     * @throws IOException if unable to obtain an InputStream from the resource
+     */
+    InputStream read() throws IOException;
+
+    /**
+     * Indicates whether or not the resource is accessible. What it means for the resource to be accessible depends on the type of
+     * resource. A File resource, for example, might be accessible only if the file exists and is readable, while a URL resource might
+     * always be considered accessible, or might be accesssible only if the existence of the resource can be confirmed.
+     *
+     * @return <code>true</code> if the file can be accessed, <code>false</code> otherwise
+     */
+    boolean isAccessible();
+
+    /**
+     * @return a String representation of the location, or <code>null</code> for a Resource that does not have an external location.
+     * For a File or a Directory, this will be the full path name; for a URL it will be the String form of the URL
+     */
+    String getLocation();
+
+    /**
+     * @return the type of resource that is being referenced
+     */
+    ResourceType getResourceType();
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java b/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java
new file mode 100644
index 0000000..67b056e
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceReferenceFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+public interface ResourceReferenceFactory {
+    ResourceReference createResourceReference(String value, ResourceDefinition resourceDefinition);
+
+    ResourceReferences createResourceReferences(String value, ResourceDefinition resourceDefinition);
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java b/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java
new file mode 100644
index 0000000..5addba0
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceReferences.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.net.URL;
+import java.util.List;
+
+/**
+ * A representation of zero or more {@link ResourceReference}s
+ */
+public interface ResourceReferences {
+
+    /**
+     * @return a List representation of all Resource References
+     */
+    List<ResourceReference> asList();
+
+    /**
+     * @return a list of all Resource References' locations
+     */
+    List<String> asLocations();
+
+    /**
+     * @return a list of all Resource References' URLs
+     */
+    List<URL> asURLs();
+
+    /**
+     * @return the number of Resource References held
+     */
+    int getCount();
+
+    /**
+     * Iterates through the Resource References and for any reference that may represent more than one
+     * resource, flattens the resource into a List of single-entity references. For example, consider that this ResourceReferences
+     * holds a single ResourceReference, of type DIRECTORY and the referenced directory contains 10 files. Calling {@link #asList()} would
+     * return a single ResourceReference. But calling <code>flatten()</code> would return a new ResourceReferences type whose {@link #asList()}
+     * method would return 10 ResourceReference objects, each with a ResourceType of FILE. The flatten operation is not recursive, meaning that if
+     * a DIRECTORY is flattened, any sub-directories will be dropped. If the contents of the subdirectories are to be retained, use {@link #flattenRecursively()}
+     * instead.
+     *
+     * @return a flattened ResourceReferences
+     */
+    ResourceReferences flatten();
+
+    /**
+     * Recursively iterates through the Resource References and for any reference that may represent more than one
+     * resource, flattens the resource into a List of single-entity references. For example, consider that this ResourceReferences
+     * holds a single ResourceReference, of type DIRECTORY and the referenced directory contains 10 files. Calling {@link #asList()} would
+     * return a single ResourceReference. But calling <code>flatten()</code> would return a new ResourceReferences type whose {@link #asList()}
+     * method would return 10 ResourceReference objects, each with a ResourceType of FILE. The flatten operation is recursive, meaning that if
+     * a DIRECTORY is encountered, its reference will be replaced with a new reference for each file, even if that file exists 100 levels deep
+     * in the directory structure.
+     *
+     * @return a flattened ResourceReferences
+     */
+    ResourceReferences flattenRecursively();
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/ResourceType.java b/src/main/java/org/apache/nifi/components/resource/ResourceType.java
new file mode 100644
index 0000000..60eff90
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/ResourceType.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.components.resource;
+
+public enum ResourceType {
+    /**
+     * Referenced Resource is a File on a local (or mounted) file system
+     */
+    FILE("file"),
+
+    /**
+     * Referenced Resource is a directory on a local (or mounted) file system
+     */
+    DIRECTORY("directory"),
+
+    /**
+     * Referenced Resource is UTF-8 text, rather than an external entity
+     */
+    TEXT("text"),
+
+    /**
+     * Referenced Resource is a URL that uses the HTTP, HTTPS, or file protocol
+     * (i.e., <code>http://...</code>, <code>https://...</code>, or <code>file:...</code>)
+     */
+    URL("URL");
+
+    private final String prettyPrintName;
+    ResourceType(final String prettyPrintName) {
+        this.prettyPrintName = prettyPrintName;
+    }
+
+    @Override
+    public String toString() {
+        return prettyPrintName;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java
new file mode 100644
index 0000000..4e1fa6b
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceContext.java
@@ -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.
+ */
+
+package org.apache.nifi.components.resource;
+
+import org.apache.nifi.components.PropertyDescriptor;
+
+public class StandardResourceContext implements ResourceContext {
+    private final ResourceReferenceFactory resourceReferenceFactory;
+    private final PropertyDescriptor propertyDescriptor;
+
+    public StandardResourceContext(final ResourceReferenceFactory resourceReferenceFactory, final PropertyDescriptor propertyDescriptor) {
+        this.resourceReferenceFactory = resourceReferenceFactory;
+        this.propertyDescriptor = propertyDescriptor;
+    }
+
+    @Override
+    public ResourceReferenceFactory getResourceReferenceFactory() {
+        return resourceReferenceFactory;
+    }
+
+    @Override
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java
new file mode 100644
index 0000000..125dcbf
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceDefinition.java
@@ -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.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.util.Set;
+
+public class StandardResourceDefinition implements ResourceDefinition {
+    private final ResourceCardinality cardinality;
+    private final Set<ResourceType> resourceTypes;
+
+    public StandardResourceDefinition(final ResourceCardinality cardinality, final Set<ResourceType> resourceTypes) {
+        this.cardinality = cardinality;
+        this.resourceTypes = resourceTypes;
+    }
+
+    @Override
+    public ResourceCardinality getCardinality() {
+        return cardinality;
+    }
+
+    @Override
+    public Set<ResourceType> getResourceTypes() {
+        return resourceTypes;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java
new file mode 100644
index 0000000..3980a9b
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferenceFactory.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class StandardResourceReferenceFactory implements ResourceReferenceFactory {
+
+    private static final ResourceReferences EMPTY_RESOURCE_REFERENCES = new StandardResourceReferences(Collections.emptyList());
+
+    public ResourceReferences createResourceReferences(final String value, final ResourceDefinition resourceDefinition) {
+        if (value == null) {
+            return EMPTY_RESOURCE_REFERENCES;
+        }
+
+        final String trimmed = value.trim();
+        if (trimmed.isEmpty()) {
+            return EMPTY_RESOURCE_REFERENCES;
+        }
+
+        if (resourceDefinition == null) {
+            return EMPTY_RESOURCE_REFERENCES;
+        }
+
+        final List<ResourceReference> references;
+        final List<String> locations = parseResourceLocations(value);
+        references = new ArrayList<>(locations.size());
+        locations.forEach(location -> references.add(createResourceReference(location, resourceDefinition)));
+
+        return new StandardResourceReferences(references);
+    }
+
+    public ResourceReference createResourceReference(final String value, final ResourceDefinition resourceDefinition) {
+        if (value == null) {
+            return null;
+        }
+
+        final String trimmed = value.trim();
+        if (trimmed.isEmpty()) {
+            return null;
+        }
+
+        if (resourceDefinition == null) {
+            return null;
+        }
+
+        final Set<ResourceType> allowedResourceTypes = resourceDefinition.getResourceTypes();
+        if (allowedResourceTypes.contains(ResourceType.URL)) {
+            try {
+                if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
+                    return new URLResourceReference(URI.create(trimmed).toURL());
+                }
+
+                if (trimmed.startsWith("file:")) {
+                    final URL url = URI.create(trimmed).toURL();
+                    final String filename = url.getFile();
+                    final File file = new File(filename);
+                    return new FileResourceReference(file);
+                }
+            } catch (MalformedURLException e) {
+                throw new IllegalArgumentException("Invalid URL: " + trimmed);
+            }
+        }
+
+        final boolean fileAllowed = allowedResourceTypes.contains(ResourceType.FILE) || allowedResourceTypes.contains(ResourceType.DIRECTORY);
+        final boolean textAllowed = allowedResourceTypes.contains(ResourceType.TEXT);
+
+        if (fileAllowed && textAllowed) {
+            // We have to make a determination whether this is a file or text. Eventually, it will be best if the user tells us explicitly.
+            // For now, we will make a determination based on a couple of simple rules.
+            final File file = new File(trimmed);
+            if (file.isAbsolute() || file.exists()) {
+                return new FileResourceReference(file);
+            }
+
+            if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) {
+                return new FileResourceReference(file);
+            }
+
+            return new Utf8TextResource(value); // Use explicit value, not trimmed value, as the white space may be important for textual content.
+        }
+
+        if (fileAllowed) {
+            final File file = new File(trimmed);
+            return new FileResourceReference(file);
+        }
+
+        if (textAllowed) {
+            return new Utf8TextResource(value);
+        }
+
+        return null;
+    }
+
+    private List<String> parseResourceLocations(final String rawValue) {
+        final List<String> resourceLocations = new ArrayList<>();
+        final String[] splits = rawValue.split(",");
+        for (final String split : splits) {
+            final String trimmed = split.trim();
+            if (trimmed.isEmpty()) {
+                continue;
+            }
+
+            resourceLocations.add(trimmed);
+        }
+
+        return resourceLocations;
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java
new file mode 100644
index 0000000..11ce057
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/StandardResourceReferences.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class StandardResourceReferences implements ResourceReferences {
+    public final List<ResourceReference> resourceReferences;
+
+    public StandardResourceReferences(final List<ResourceReference> resourceReferences) {
+        this.resourceReferences = new ArrayList<>(Objects.requireNonNull(resourceReferences));
+    }
+
+    @Override
+    public List<ResourceReference> asList() {
+        return Collections.unmodifiableList(resourceReferences);
+    }
+
+    @Override
+    public List<String> asLocations() {
+        final List<String> locations = new ArrayList<>(resourceReferences.size());
+        resourceReferences.forEach(ref -> locations.add(ref.getLocation()));
+        return locations;
+    }
+
+    @Override
+    public List<URL> asURLs() {
+        final List<URL> locations = new ArrayList<>(resourceReferences.size());
+        resourceReferences.forEach(ref -> locations.add(ref.asURL()));
+        return locations;
+    }
+
+    @Override
+    public int getCount() {
+        return resourceReferences.size();
+    }
+
+    @Override
+    public ResourceReferences flatten() {
+        if (resourceReferences.isEmpty()) {
+            return this;
+        }
+
+        final List<ResourceReference> flattened = new ArrayList<>();
+        resourceReferences.forEach(reference -> {
+            if (reference.getResourceType() == ResourceType.DIRECTORY) {
+                addChildren(reference.asFile(), flattened);
+            } else {
+                flattened.add(reference);
+            }
+        });
+
+        return new StandardResourceReferences(flattened);
+    }
+
+    private void addChildren(final File file, final List<ResourceReference> flattened) {
+        if (file == null) {
+            return;
+        }
+
+        if (file.isDirectory()) {
+            final File[] children = file.listFiles();
+            if (children != null) {
+                for (final File child : children) {
+                    if (child.isFile()) {
+                        flattened.add(new FileResourceReference(child));
+                    }
+                }
+            }
+        } else {
+            flattened.add(new FileResourceReference(file));
+        }
+    }
+
+
+    @Override
+    public ResourceReferences flattenRecursively() {
+        if (resourceReferences.isEmpty()) {
+            return this;
+        }
+
+        final List<ResourceReference> flattened = new ArrayList<>();
+        resourceReferences.forEach(reference -> {
+            if (reference.getResourceType() == ResourceType.DIRECTORY) {
+                recurse(reference.asFile(), flattened);
+            } else {
+                flattened.add(reference);
+            }
+        });
+
+        return new StandardResourceReferences(flattened);
+    }
+
+    private void recurse(final File file, final List<ResourceReference> flattened) {
+        if (file == null) {
+            return;
+        }
+
+        if (file.isDirectory()) {
+            final File[] children = file.listFiles();
+            if (children != null) {
+                for (final File child : children) {
+                    recurse(child, flattened);
+                }
+            }
+        } else {
+            flattened.add(new FileResourceReference(file));
+        }
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final StandardResourceReferences that = (StandardResourceReferences) o;
+        return Objects.equals(resourceReferences, that.resourceReferences);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resourceReferences);
+    }
+
+    @Override
+    public String toString() {
+        return "StandardResourceReferences[resources=" + resourceReferences + "]";
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java b/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java
new file mode 100644
index 0000000..8b5f7f7
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/URLResourceReference.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public class URLResourceReference implements ResourceReference {
+    private final URL url;
+
+    public URLResourceReference(final URL url) {
+        this.url = url;
+    }
+
+    @Override
+    public File asFile() {
+        return null;
+    }
+
+    @Override
+    public URL asURL() {
+        return url;
+    }
+
+    @Override
+    public InputStream read() throws IOException {
+        return url.openStream();
+    }
+
+    @Override
+    public boolean isAccessible() {
+        return true;
+    }
+
+    @Override
+    public String getLocation() {
+        return url.toExternalForm();
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.URL;
+    }
+
+    @Override
+    public String toString() {
+        return "URLResourceReference[url=" + url + "]";
+    }
+
+}
diff --git a/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java b/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java
new file mode 100644
index 0000000..78f94e9
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/resource/Utf8TextResource.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.resource;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+public class Utf8TextResource implements ResourceReference {
+    private final String text;
+
+    public Utf8TextResource(final String text) {
+        this.text = text;
+    }
+
+    @Override
+    public File asFile() {
+        return null;
+    }
+
+    @Override
+    public URL asURL() {
+        return null;
+    }
+
+    @Override
+    public InputStream read() throws IOException {
+        return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Override
+    public boolean isAccessible() {
+        return true;
+    }
+
+    @Override
+    public String getLocation() {
+        return null;
+    }
+
+    @Override
+    public ResourceType getResourceType() {
+        return ResourceType.TEXT;
+    }
+
+    @Override
+    public String toString() {
+        return "Utf8TextResource[text=" + text.length() + " characters]";
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/state/Scope.java b/src/main/java/org/apache/nifi/components/state/Scope.java
new file mode 100644
index 0000000..dd0d0aa
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/state/Scope.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.state;
+
+/**
+ * A Scope represents how a NiFi component's state is to be stored and retrieved when running in a cluster.
+ */
+public enum Scope {
+
+    /**
+     * State is to be treated as "global" across the cluster. I.e., the same component on all nodes will
+     * have access to the same state.
+     */
+    CLUSTER,
+
+    /**
+     * State is to be treated local to the node. I.e., the same component will have different state on each
+     * node in the cluster.
+     */
+    LOCAL;
+
+    @Override
+    public String toString() {
+        return name();
+    }
+}
diff --git a/src/main/java/org/apache/nifi/components/state/StateManager.java b/src/main/java/org/apache/nifi/components/state/StateManager.java
new file mode 100644
index 0000000..1669ea1
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/state/StateManager.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.state;
+
+import java.io.IOException;
+import java.util.Map;
+import org.apache.nifi.annotation.behavior.Stateful;
+import org.apache.nifi.components.state.exception.StateTooLargeException;
+
+/**
+ * <p>
+ * The StateManager is responsible for providing NiFi components a mechanism for storing
+ * and retrieving state.
+ * </p>
+ *
+ * <p>
+ * When calling methods in this class, the {@link Scope} is used in order to specify whether
+ * state should be stored/retrieved from the local state or the clustered state. However, if
+ * any instance of NiFi is not clustered (or is disconnected from its cluster), the Scope is
+ * not really relevant and the local state will be used. This allows component
+ * developers to not concern themselves with whether or not a particular instance of NiFi is
+ * clustered. Instead, developers should assume that the instance is indeed clustered and write
+ * the component accordingly. If not clustered, the component will still behave in the same
+ * manner, as a standalone node could be thought of as a "cluster of 1."
+ * </p>
+ *
+ * <p>
+ * This mechanism is designed to allow developers to easily store and retrieve small amounts of state.
+ * The storage mechanism is implementation-specific, but is typically either stored on a remote system
+ * or on disk. For that reason, one should consider the cost of storing and retrieving this data, and the
+ * amount of data should be kept to the minimum required.
+ * </p>
+ *
+ * <p>
+ * Any component that wishes to use the StateManager should also use the {@link Stateful} annotation to provide
+ * a description of what state is being stored and what Scope is used. If this annotation is not present, the UI
+ * will not expose such information or allow DFMs to clear the state.
+ * </p>
+ */
+public interface StateManager {
+
+    /**
+     * Updates the value of the component's state, setting it to given value
+     *
+     * @param state the value to change the state to
+     * @param scope the scope to use when storing the state
+     *
+     * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism
+     * @throws IOException if unable to communicate with the underlying storage mechanism
+     */
+    void setState(Map<String, String> state, Scope scope) throws IOException;
+
+    /**
+     * Returns the current state for the component. This return value will never be <code>null</code>.
+     * If the state has not yet been set, the StateMap's version will be -1, and the map of values will be empty.
+     *
+     * @param scope the scope to use when fetching the state
+     * @return the current state for the component
+     * @throws IOException if unable to communicate with the underlying storage mechanism
+     */
+    StateMap getState(Scope scope) throws IOException;
+
+    /**
+     * Updates the value of the component's state to the new value if and only if the value currently
+     * is the same as the given oldValue.
+     *
+     * @param oldValue the old value to compare against
+     * @param newValue the new value to use if and only if the state's value is the same as the given oldValue
+     * @param scope the scope to use for storing the new state
+     * @return <code>true</code> if the state was updated to the new value, <code>false</code> if the state's value was not
+     *         equal to oldValue
+     *
+     * @throws StateTooLargeException if attempting to store more state than is allowed by the backing storage mechanism
+     * @throws IOException if unable to communicate with the underlying storage mechanism
+     */
+    boolean replace(StateMap oldValue, Map<String, String> newValue, Scope scope) throws IOException;
+
+    /**
+     * Clears all keys and values from the component's state
+     *
+     * @param scope the scope whose values should be cleared
+     *
+     * @throws IOException if unable to communicate with the underlying storage mechanism
+     */
+    void clear(Scope scope) throws IOException;
+}
diff --git a/src/main/java/org/apache/nifi/components/state/StateMap.java b/src/main/java/org/apache/nifi/components/state/StateMap.java
new file mode 100644
index 0000000..95829a7
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/state/StateMap.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.components.state;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Provides a representation of a component's state at some point in time.
+ */
+public interface StateMap {
+
+    /**
+     * Get state version is not guaranteed to be numeric, but can be used to compare against an expected version.
+     * The default implementation uses the available version number and considers -1 as indicating an empty version
+     *
+     * @return State version or empty when not known
+     */
+    Optional<String> getStateVersion();
+
+    /**
+     * Returns the value associated with the given key
+     *n
+     * @param key the key whose value should be retrieved
+     * @return the value associated with the given key, or <code>null</code> if no value is associated
+     *         with this key.
+     */
+    String get(String key);
+
+    /**
+     * Returns an immutable Map representation of all keys and values for the state of a component.
+     *
+     * @return an immutable Map representation of all keys and values for the state of a component.
+     */
+    Map<String, String> toMap();
+}
diff --git a/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java b/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java
new file mode 100644
index 0000000..41c8b24
--- /dev/null
+++ b/src/main/java/org/apache/nifi/components/state/exception/StateTooLargeException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.components.state.exception;
+
+import java.io.IOException;
+import org.apache.nifi.components.state.StateManager;
+
+/**
+ * Thrown when attempting to store state via the {@link StateManager} but the state being
+ * stored is larger than is allowed by the backing storage mechanism.
+ */
+public class StateTooLargeException extends IOException {
+    private static final long serialVersionUID = 1L;
+
+    public StateTooLargeException(final String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/apache/nifi/context/ClusterContext.java b/src/main/java/org/apache/nifi/context/ClusterContext.java
new file mode 100644
index 0000000..aba5244
--- /dev/null
+++ b/src/main/java/org/apache/nifi/context/ClusterContext.java
@@ -0,0 +1,30 @@
+/*
+ * 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.context;
+
+/**
+ * A context for retrieving information about the state of the cluster.
+ */
+public interface ClusterContext {
+
+    /**
+     * Retrieves the current state of the cluster connection of this node.
+     *
+     * @return True if this node is connected to the cluster. False otherwise.
+     */
+    boolean isConnectedToCluster();
+}
diff --git a/src/main/java/org/apache/nifi/context/PropertyContext.java b/src/main/java/org/apache/nifi/context/PropertyContext.java
new file mode 100644
index 0000000..89ffb45
--- /dev/null
+++ b/src/main/java/org/apache/nifi/context/PropertyContext.java
@@ -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.
+ */
+package org.apache.nifi.context;
+
+import java.util.Map;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+
+/**
+ * A context for retrieving a PropertyValue from a PropertyDescriptor.
+ */
+public interface PropertyContext {
+
+    /**
+     * Retrieves the current value set for the given descriptor, if a value is
+     * set - else uses the descriptor to determine the appropriate default value
+     *
+     * @param descriptor to lookup the value of
+     * @return the property value of the given descriptor
+     */
+    PropertyValue getProperty(PropertyDescriptor descriptor);
+
+
+    Map<String, String> getAllProperties();
+
+}
diff --git a/src/main/java/org/apache/nifi/controller/AbstractControllerService.java b/src/main/java/org/apache/nifi/controller/AbstractControllerService.java
new file mode 100644
index 0000000..30773ee
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/AbstractControllerService.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+import org.apache.nifi.annotation.lifecycle.OnDisabled;
+import org.apache.nifi.annotation.lifecycle.OnEnabled;
+import org.apache.nifi.components.AbstractConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.reporting.InitializationException;
+
+public abstract class AbstractControllerService extends AbstractConfigurableComponent implements ControllerService {
+
+    private String identifier;
+    private ControllerServiceLookup serviceLookup;
+    private ComponentLog logger;
+    private StateManager stateManager;
+    private volatile ConfigurationContext configurationContext;
+    private volatile boolean enabled = false;
+    private NodeTypeProvider nodeTypeProvider;
+
+    @Override
+    public final void initialize(final ControllerServiceInitializationContext context) throws InitializationException {
+        this.identifier = context.getIdentifier();
+        serviceLookup = context.getControllerServiceLookup();
+        logger = context.getLogger();
+        stateManager = context.getStateManager();
+        nodeTypeProvider = context.getNodeTypeProvider();
+        init(context);
+    }
+
+    @Override
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * @return the {@link ControllerServiceLookup} that was passed to the
+     * {@link #init(ControllerServiceInitializationContext)} method
+     */
+    protected final ControllerServiceLookup getControllerServiceLookup() {
+        return serviceLookup;
+    }
+
+    /**
+     * @return the {@link NodeTypeProvider} that was passed to the
+     * {@link #init(ControllerServiceInitializationContext)} method
+     */
+    protected final NodeTypeProvider getNodeTypeProvider() {
+        return nodeTypeProvider;
+    }
+
+    /**
+     * Provides a mechanism by which subclasses can perform initialization of
+     * the Controller Service before it is scheduled to be run
+     *
+     * @param config of initialization context
+     * @throws InitializationException if unable to init
+     */
+    protected void init(final ControllerServiceInitializationContext config) throws InitializationException {
+    }
+
+    @OnEnabled
+    public final void enabled() {
+        this.enabled = true;
+    }
+
+    @OnDisabled
+    public final void disabled() {
+        this.enabled = false;
+    }
+
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    /**
+     * @return the logger that has been provided to the component by the
+     * framework in its initialize method
+     */
+    protected ComponentLog getLogger() {
+        return logger;
+    }
+
+    /**
+     * @return the StateManager that can be used to store and retrieve state for this Controller Service
+     */
+    protected StateManager getStateManager() {
+        return stateManager;
+    }
+
+    @OnEnabled
+    public final void abstractStoreConfigContext(final ConfigurationContext configContext) {
+        this.configurationContext = configContext;
+    }
+
+    @OnDisabled
+    public final void abstractClearConfigContext() {
+        this.configurationContext = null;
+    }
+
+    protected ConfigurationContext getConfigurationContext() {
+        final ConfigurationContext context = this.configurationContext;
+        if (context == null) {
+            throw new IllegalStateException("No Configuration Context exists");
+        }
+
+        return configurationContext;
+    }
+
+    protected PropertyValue getProperty(final PropertyDescriptor descriptor) {
+        return getConfigurationContext().getProperty(descriptor);
+    }
+}
diff --git a/src/main/java/org/apache/nifi/controller/ConfigurationContext.java b/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
new file mode 100644
index 0000000..2886a2a
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.context.PropertyContext;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This context is passed to ControllerServices and Reporting Tasks in order
+ * to expose their configuration to them.
+ */
+public interface ConfigurationContext extends PropertyContext {
+
+    /**
+     * @return an unmodifiable map of all configured properties for this
+     * {@link ControllerService}
+     */
+    Map<PropertyDescriptor, String> getProperties();
+
+    /**
+     * @return the annotation data configured for the component
+     */
+    String getAnnotationData();
+
+    /**
+     * @return a String representation of the scheduling period, or <code>null</code> if
+     *         the component does not have a scheduling period (e.g., for ControllerServices)
+     */
+    String getSchedulingPeriod();
+
+    /**
+     * Returns the amount of time, in the given {@link TimeUnit} that will
+     * elapsed between the return of one execution of the
+     * component's <code>onTrigger</code> method and
+     * the time at which the method is invoked again. This method will return
+     * null if the component does not have a scheduling period (e.g., for ControllerServices)
+     *
+     * @param timeUnit unit of time for scheduling
+     * @return period of time or <code>null</code> if component does not have a scheduling
+     *         period
+     */
+    Long getSchedulingPeriod(TimeUnit timeUnit);
+
+    /**
+     * Returns the component's (ControllerService, ReportingTask, ParameterProvider, e.g.) name
+     * @return the String name of this component
+     */
+    String getName();
+}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerService.java b/src/main/java/org/apache/nifi/controller/ControllerService.java
new file mode 100644
index 0000000..e8f9850
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/ControllerService.java
@@ -0,0 +1,206 @@
+/*
+ * 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;
+
+import org.apache.nifi.annotation.behavior.Stateful;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.flowanalysis.FlowAnalysisRule;
+import org.apache.nifi.migration.PropertyConfiguration;
+import org.apache.nifi.parameter.ParameterProvider;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSessionFactory;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.reporting.ReportingTask;
+
+/**
+ * <p>
+ * This interface provides a mechanism for creating services that are shared
+ * among all {@link Processor}s, {@link ReportingTask}s, {@link FlowAnalysisRule}s, {@link ParameterProvider}s and other
+ * {@code ControllerService}s.
+ * </p>
+ *
+ * <p>
+ * <code>ControllerService</code>s are discovered using Java's
+ * <code>ServiceLoader</code> mechanism. As a result, all implementations must
+ * follow these rules:
+ * </p>
+ *
+ * <ul>
+ * <li>The implementation must implement this interface.</li>
+ * <li>The implementation must have a file named
+ * org.apache.nifi.controller.ControllerService located within the jar's
+ * <code>META-INF/services</code> directory. This file contains a list of
+ * fully-qualified class names of all <code>ControllerService</code>s in the
+ * jar, one-per-line.
+ * <li>The implementation must support a default constructor.</li>
+ * </ul>
+ *
+ * <p>
+ * <b>All implementations of this interface must be thread-safe.</b>
+ * </p>
+ *
+ * <h2>Accessing Controller Services</h2>
+ * <p>
+ * A ControllerService is accessible only through its interface. The framework
+ * provides access to a ControllerService through two different mechanisms:
+ * <ul>
+ * <li>
+ * A {@link PropertyDescriptor} can be created via the
+ * {@link PropertyDescriptor.Builder} after calling the
+ * {@link PropertyDescriptor.Builder#identifiesControllerService(Class) identifiesControllerService(Class)}
+ * method and then the ControllerService is accessed via
+ * {@link PropertyValue#asControllerService(Class)} method.
+ * <p>
+ * For example:
+ * </p>
+ * <pre><code>
+ *    public static final PropertyDescriptor MY_PROPERTY = new PropertyDescriptor.Builder()
+ *     .name("My Property")
+ *     .description("Example Property")
+ *     .identifiesControllerService( MyControllerServiceInterface.class )
+ *     .build();
+ *
+ *    ...
+ *    public void onTrigger(ProcessContext context, ProcessSessionFactory sessionFactory) throws ProcessException {
+ *     // Obtain the user-selected controller service
+ *     final MyControllerServiceInterface service = context.getProperty(MY_PROPERTY).asControllerService( MyControllerServiceInterface.class );
+ *     ...
+ *    }
+ *
+ * </code></pre>
+ * <li>A Controller Service can be obtained via a
+ * {@link ControllerServiceLookup}. This lookup may be obtained, for example,
+ * from the {@link ProcessContext} that is provided to a {@link Processor}'s
+ * {@link Processor#onTrigger(ProcessContext, ProcessSessionFactory) onTrigger}
+ * method.
+ * <p>
+ * For example:
+ * </p>
+ *
+ * <pre><code>
+ *    public void onTrigger(ProcessContext context, ProcessSessionFactory sessionFactory) throws ProcessException {
+ *      final MyControllerServiceInterface service = (MyControllerServiceInterface) context.getControllerServiceLookup().getControllerService("service_identifier");
+ *    }
+ * </code></pre>
+ * </li>
+ * </ul>
+ *
+ * <h2>Defining a Controller Service</h2>
+ * <p>
+ * Note in both of the examples above, that the Controller Service was accessed
+ * only by its interface, and this interface extends ControllerService. If we
+ * have an implementation named MyServiceImpl, for example, that implements
+ * MyControllerServiceInterface, we cannot, in either case, attempt to cast the
+ * ControllerService to the desired implementation. Doing so will result in a
+ * {@link ClassCastException}. This is by design and is done for the following
+ * reasons:
+ *
+ * <ul>
+ * <li>
+ * It is a good coding practice to implement such a service as an interface in
+ * general.
+ * </li>
+ * <li>
+ * A Controller Service can be referenced from different NiFi Archives (NARs).
+ * This means that the Controller Service may be defined in one ClassLoader and
+ * referenced from another unrelated ClassLoader. In order to account for this,
+ * NiFi will change the current thread's ClassLoader as appropriate when
+ * entering the Controller Service's code and revert back to the previous
+ * ClassLoader after exiting the Controller Service's code.
+ * </li>
+ * </ul>
+ *
+ * <h2>Controller Services and NARs</h2>
+ * <p>
+ * Due to the fact that a Controller Service may be referenced from a different
+ * NAR than the one in which the implementation lives, it is crucial that both
+ * the Controller Service's interface and the code referencing the interface
+ * inherit from the same ClassLoader. This is accomplished by ensuring that the
+ * NAR that contains the Controller Service interface is the parent (or
+ * ancestor) of the NAR that references the Controller Service interface.
+ * </p>
+ *
+ * <p>
+ * Typically, this is done by creating a NAR structure as follows:
+ * <pre>
+ *   + my-services-api-nar
+ *   +--- service-X-implementation-nar
+ *   +--- service-Y-implementation-nar
+ *   +--- service-Z-implementation-nar
+ *   +--- processor-A-nar
+ *   +--- processor-B-nar
+ * </pre>
+ *
+ * <p>
+ * In this case, the {@code MyControllerServiceInterface} interface, and any
+ * other Controller Service interfaces, will be defined in the
+ * {@code my-services-api-nar} NAR. Implementations are then encapsulated within
+ * the {@code service-X-implementation-nar},
+ * {@code service-Y-implementation-nar}, and
+ * {@code service-Z-implementation-nar} NARs. All Controller Services and all
+ * Processors defined in these NARs are able to reference any other Controller
+ * Services whose interfaces are provided in the {@code my-services-api-nar}
+ * NAR.
+ * </p>
+ *
+ * <p>
+ * For more information on NARs, see the NiFi Developer Guide.
+ * </p>
+ */
+public interface ControllerService extends ConfigurableComponent {
+
+    /**
+     * Provides the Controller Service with access to objects that may be of use
+     * throughout the life of the service. This method will be called before any
+     * properties are set
+     *
+     * @param context of initialization
+     * @throws org.apache.nifi.reporting.InitializationException if unable to init
+     */
+    void initialize(ControllerServiceInitializationContext context) throws InitializationException;
+
+    /**
+     * Indicates whether this controller service, configured with the given {@link ConfigurationContext}, stores state.
+     * @param context provides access to convenience methods for obtaining property values
+     * @return True if this controller service stores state
+     */
+    default boolean isStateful(ConfigurationContext context) {
+        return this.getClass().isAnnotationPresent(Stateful.class);
+    }
+
+    /**
+     * <p>
+     * Allows for the migration of an old property configuration to a new configuration. This allows the Controller Service to evolve over time,
+     * as it allows properties to be renamed, removed, or reconfigured.
+     * </p>
+     *
+     * <p>
+     * This method is called only when a Controller Service is restored from a previous configuration. For example, when NiFi is restarted and the
+     * flow is restored from disk, when a previously configured flow is imported (e.g., from a JSON file that was exported or a NiFi Registry),
+     * or when a node joins a cluster and inherits a flow that has a new Controller Service. Once called, the method will not be invoked again for this
+     * Controller Service until NiFi is restarted.
+     * </p>
+     *
+     * @param config the current property configuration
+     */
+    default void migrateProperties(PropertyConfiguration config) {
+    }
+
+}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java b/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java
new file mode 100644
index 0000000..c778869
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/ControllerServiceInitializationContext.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.kerberos.KerberosContext;
+import org.apache.nifi.logging.ComponentLog;
+
+public interface ControllerServiceInitializationContext extends KerberosContext {
+
+    /**
+     * @return the identifier associated with the {@link ControllerService} with
+     * which this context is associated
+     */
+    String getIdentifier();
+
+    /**
+     * @return the {@link ControllerServiceLookup} which can be used to obtain
+     * Controller Services
+     */
+    ControllerServiceLookup getControllerServiceLookup();
+
+    /**
+     * @return a logger that can be used to log important events in a standard
+     * way and generate bulletins when appropriate
+     */
+    ComponentLog getLogger();
+
+    /**
+     * @return the StateManager that can be used to store and retrieve state for this component
+     */
+    StateManager getStateManager();
+
+    /**
+     * @return the {@link NodeTypeProvider} which can be used to detect the node
+     * type of this NiFi instance.
+     */
+    NodeTypeProvider getNodeTypeProvider();
+}
diff --git a/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java b/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java
new file mode 100644
index 0000000..0144438
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/ControllerServiceLookup.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+public interface ControllerServiceLookup {
+
+    /**
+     * @param serviceIdentifier of controller service
+     * @return the ControllerService that is registered with the given
+     * identifier
+     */
+    ControllerService getControllerService(String serviceIdentifier);
+
+    /**
+     * @param serviceIdentifier identifier of service to check
+     * @return <code>true</code> if the Controller Service with the given
+     * identifier is enabled, <code>false</code> otherwise. If the given
+     * identifier is not known by this ControllerServiceLookup, returns
+     * <code>false</code>
+     */
+    boolean isControllerServiceEnabled(String serviceIdentifier);
+
+    /**
+     * @param serviceIdentifier identifier of service to check
+     * @return <code>true</code> if the Controller Service with the given
+     * identifier has been enabled but is still in the transitioning state,
+     * otherwise returns <code>false</code>. If the given identifier is not
+     * known by this ControllerServiceLookup, returns <code>false</code>
+     */
+    boolean isControllerServiceEnabling(String serviceIdentifier);
+
+    /**
+     * @param service service to check
+     * @return <code>true</code> if the given Controller Service is enabled,
+     * <code>false</code> otherwise. If the given Controller Service is not
+     * known by this ControllerServiceLookup, returns <code>false</code>
+     */
+    boolean isControllerServiceEnabled(ControllerService service);
+
+    /**
+     *
+     * @param serviceType type of service to get identifiers for
+     *
+     * @return the set of all Controller Service Identifiers whose Controller
+     *         Service is of the given type.
+     * @throws IllegalArgumentException if the given class is not an interface
+     */
+    Set<String> getControllerServiceIdentifiers(Class<? extends ControllerService> serviceType) throws IllegalArgumentException;
+
+    /**
+     * @param serviceIdentifier identifier to look up
+     * @return the name of the Controller service with the given identifier. If
+     * no service can be found with this identifier, returns {@code null}
+     */
+    String getControllerServiceName(String serviceIdentifier);
+}
diff --git a/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java b/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java
new file mode 100644
index 0000000..1552282
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/NodeTypeProvider.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * This interface provides a set of methods for checking NiFi node type.
+ */
+public interface NodeTypeProvider {
+
+    /**
+     * @return true if this instance is clustered, false otherwise.MockProcessContext
+     * Clustered means that a node is either connected or trying to connect to the cluster.
+     */
+    boolean isClustered();
+
+    /**
+     * @return true if the expected state of clustering is true, false otherwise. Contrary to {{@link #isClustered()}}
+     * this does not dynamically change with the state of this node.
+     */
+    default boolean isConfiguredForClustering() {
+        return false;
+    }
+
+    /**
+     * @return true if this instances is clustered and connected to the cluster.
+     */
+    default boolean isConnected() {
+        return false;
+    }
+
+    /**
+     * @return true if this instance is the primary node in the cluster; false otherwise
+     */
+    boolean isPrimary();
+
+    /**
+     * @return Returns with the hostname of the current node, if clustered. For For a standalone
+     * NiFi this returns an empty instead.
+     */
+    default Optional<String> getCurrentNode() {
+        if (isClustered()) {
+            throw new IllegalStateException("Clustered environment is not handled!");
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    /**
+     * @return Names/IP addresses of all expected hosts in the cluster (including the current one). For a standalone
+     * NiFi this returns an empty set instead.
+     */
+    default Set<String> getClusterMembers() {
+        if (isClustered()) {
+            throw new IllegalStateException("Clustered environment is not handled!");
+        } else {
+            return Collections.emptySet();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/nifi/controller/ScheduledState.java b/src/main/java/org/apache/nifi/controller/ScheduledState.java
new file mode 100644
index 0000000..f457a9c
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/ScheduledState.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * Indicates the valid values for the state of a <code>Triggerable</code> entity
+ * with respect to scheduling the entity to run.
+ */
+public enum ScheduledState {
+
+    /**
+     * Entity cannot be scheduled to run
+     */
+    DISABLED,
+    /**
+     * Entity can be scheduled to run but currently is not
+     */
+    STOPPED,
+    /**
+     * Entity is currently scheduled to run
+     */
+    RUNNING,
+
+    STARTING,
+
+    STOPPING,
+
+    RUN_ONCE;
+}
diff --git a/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java b/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java
new file mode 100644
index 0000000..c9978f8
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ *     Any Controller Service that implements this interface will be provided the opportunity to verify
+ *     a given configuration of the Controller Service. This allows the Controller Service to provide meaningful feedback
+ *     to users when configuring the dataflow.
+ * </p>
+ *
+ * <p>
+ *     Generally speaking, verification differs from validation in that validation is expected to be very
+ *     quick and run often. If a Controller Service is not valid, it cannot be started. However, verification may be
+ *     more expensive or time-consuming to complete. For example, validation may ensure that a username is
+ *     provided for connecting to an external service but should not perform any sort of network connection
+ *     in order to verify that the username is accurate. Verification, on the other hand, may create resources
+ *     such as network connections, may be more expensive to complete, and may be run only when a user invokes
+ *     the action (though verification may later occur at other stages, such as when starting a component).
+ * </p>
+ *
+ * <p>
+ *     Verification is allowed to be run only when a Controller Service is fully disabled.
+ *     Therefore, any initialization logic that may need to be performed
+ *     before the Controller Service is triggered may also be required for verification. However, the framework is not responsible
+ *     for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such
+ *     methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog, Map)} itself.
+ *     The {@link #verify(ConfigurationContext, ComponentLog, Map)} method will only be called if the configuration is valid according to the
+ *     validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid).
+ * </p>
+ */
+public interface VerifiableControllerService {
+
+    /**
+     * Verifies that the configuration defined by the given ConfigurationContext is valid.
+     * @param context the ProcessContext that contains the necessary configuration
+     * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result
+     * in producing bulletins, which can be confusing.
+     * @param variables a Map of key/value pairs that can be used to resolve variables referenced in property values via Expression Language
+     *
+     * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed
+     */
+    List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger, Map<String, String> variables);
+
+}
diff --git a/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java b/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java
new file mode 100644
index 0000000..899bcbc
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/VersionedControllerServiceLookup.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import org.apache.nifi.flow.VersionedControllerService;
+
+public interface VersionedControllerServiceLookup {
+
+    /**
+     * @param serviceIdentifier of controller service
+     * @return the VersionedControllerService with the given
+     * identifier
+     */
+    VersionedControllerService getVersionedControllerService(String serviceIdentifier);
+}
diff --git a/src/main/java/org/apache/nifi/controller/queue/QueueSize.java b/src/main/java/org/apache/nifi/controller/queue/QueueSize.java
new file mode 100644
index 0000000..ef6a145
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/queue/QueueSize.java
@@ -0,0 +1,94 @@
+/*
+ * 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.queue;
+
+import java.text.NumberFormat;
+
+public class QueueSize {
+    private final int objectCount;
+    private final long totalSizeBytes;
+    private final int hashCode;
+
+    public QueueSize(final int numberObjects, final long totalSizeBytes) {
+        if (numberObjects < 0 || totalSizeBytes < 0) {
+            throw new IllegalArgumentException();
+        }
+        objectCount = numberObjects;
+        this.totalSizeBytes = totalSizeBytes;
+        hashCode = (int) (41 + 47 * objectCount + 51 * totalSizeBytes);
+    }
+
+    /**
+     * @return number of objects present on the queue
+     */
+    public int getObjectCount() {
+        return objectCount;
+    }
+
+    /**
+     * @return total size in bytes of the content for the data on the queue
+     */
+    public long getByteCount() {
+        return totalSizeBytes;
+    }
+
+    /**
+     * Returns a new QueueSize that is the sum of this QueueSize and the provided QueueSize
+     *
+     * @param other the other QueueSize to add to this QueueSize
+     * @return a new QueueSize that is the sum of this QueueSize and the provided QueueSize
+     */
+    public QueueSize add(final QueueSize other) {
+        if (other == null) {
+            return new QueueSize(objectCount, totalSizeBytes);
+        }
+
+        return new QueueSize(objectCount + other.getObjectCount(), totalSizeBytes + other.getByteCount());
+    }
+
+    public QueueSize add(final int count, final long bytes) {
+        return new QueueSize(objectCount + count, totalSizeBytes + bytes);
+    }
+
+    @Override
+    public String toString() {
+        return "QueueSize[FlowFiles=" + objectCount + ", ContentSize=" + NumberFormat.getNumberInstance().format(totalSizeBytes) + " Bytes]";
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj == null) {
+            return false;
+        }
+
+        if (!(obj instanceof QueueSize)) {
+            return false;
+        }
+
+        final QueueSize other = (QueueSize) obj;
+        return getObjectCount() == other.getObjectCount() && getByteCount() == other.getByteCount();
+    }
+}
diff --git a/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java b/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java
new file mode 100644
index 0000000..bd2cd29
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/status/ConnectionStatus.java
@@ -0,0 +1,313 @@
+/*
+ * 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.status;
+
+import org.apache.nifi.controller.status.analytics.ConnectionStatusPredictions;
+import org.apache.nifi.processor.DataUnit;
+
+public class ConnectionStatus implements Cloneable {
+
+    private String id;
+    private String groupId;
+    private String name;
+    private String sourceId;
+    private String sourceName;
+    private String destinationId;
+    private String destinationName;
+    private String backPressureDataSizeThreshold;
+    private ConnectionStatusPredictions predictions;
+    private long backPressureBytesThreshold;
+    private long backPressureObjectThreshold;
+    private int inputCount;
+    private long inputBytes;
+    private int queuedCount;
+    private long queuedBytes;
+    private int outputCount;
+    private long outputBytes;
+    private int maxQueuedCount;
+    private long maxQueuedBytes;
+    private long totalQueuedDuration;
+    private long maxQueuedDuration;
+    private FlowFileAvailability flowFileAvailability;
+    private LoadBalanceStatus loadBalanceStatus;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(final String id) {
+        this.id = id;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(final String groupId) {
+        this.groupId = groupId;
+    }
+
+    public LoadBalanceStatus getLoadBalanceStatus() {
+        return loadBalanceStatus;
+    }
+
+    public void setLoadBalanceStatus(final LoadBalanceStatus loadBalanceStatus) {
+        this.loadBalanceStatus = loadBalanceStatus;
+    }
+
+    public int getQueuedCount() {
+        return queuedCount;
+    }
+
+    public void setQueuedCount(final int queuedCount) {
+        this.queuedCount = queuedCount;
+    }
+
+    public long getQueuedBytes() {
+        return queuedBytes;
+    }
+
+    public void setQueuedBytes(final long queuedBytes) {
+        this.queuedBytes = queuedBytes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSourceId() {
+        return sourceId;
+    }
+
+    public void setSourceId(String sourceId) {
+        this.sourceId = sourceId;
+    }
+
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    public void setSourceName(String sourceName) {
+        this.sourceName = sourceName;
+    }
+
+    public String getDestinationId() {
+        return destinationId;
+    }
+
+    public void setDestinationId(String destinationId) {
+        this.destinationId = destinationId;
+    }
+
+    public String getDestinationName() {
+        return destinationName;
+    }
+
+    public void setDestinationName(String destinationName) {
+        this.destinationName = destinationName;
+    }
+
+    public String getBackPressureDataSizeThreshold() {
+        return backPressureDataSizeThreshold;
+    }
+
+    public void setBackPressureDataSizeThreshold(String backPressureDataSizeThreshold) {
+        this.backPressureDataSizeThreshold = backPressureDataSizeThreshold;
+        setBackPressureBytesThreshold(DataUnit.parseDataSize(backPressureDataSizeThreshold, DataUnit.B).longValue());
+    }
+
+    public ConnectionStatusPredictions getPredictions() {
+        return predictions;
+    }
+
+    public void setPredictions(ConnectionStatusPredictions predictions) {
+        this.predictions = predictions;
+    }
+
+    public long getBackPressureObjectThreshold() {
+        return backPressureObjectThreshold;
+    }
+
+    public void setBackPressureObjectThreshold(long backPressureObjectThreshold) {
+        this.backPressureObjectThreshold = backPressureObjectThreshold;
+    }
+
+    public long getInputBytes() {
+        return inputBytes;
+    }
+
+    public void setInputBytes(long inputBytes) {
+        this.inputBytes = inputBytes;
+    }
+
+    public int getInputCount() {
+        return inputCount;
+    }
+
+    public void setInputCount(int inputCount) {
+        this.inputCount = inputCount;
+    }
+
+    public long getOutputBytes() {
+        return outputBytes;
+    }
+
+    public void setOutputBytes(long outputBytes) {
+        this.outputBytes = outputBytes;
+    }
+
+    public int getOutputCount() {
+        return outputCount;
+    }
+
+    public void setOutputCount(int outputCount) {
+        this.outputCount = outputCount;
+    }
+
+    public int getMaxQueuedCount() {
+        return maxQueuedCount;
+    }
+
+    public void setMaxQueuedCount(int maxQueuedCount) {
+        this.maxQueuedCount = maxQueuedCount;
+    }
+
+    public long getMaxQueuedBytes() {
+        return maxQueuedBytes;
+    }
+
+    public void setMaxQueuedBytes(long maxQueueBytes) {
+        this.maxQueuedBytes = maxQueueBytes;
+    }
+
+    public long getBackPressureBytesThreshold() {
+        return backPressureBytesThreshold;
+    }
+
+    public void setBackPressureBytesThreshold(long backPressureBytesThreshold) {
+        this.backPressureBytesThreshold = backPressureBytesThreshold;
+    }
+
+    public long getTotalQueuedDuration() {
+        return totalQueuedDuration;
+    }
+
+    public void setTotalQueuedDuration(long totalQueuedDuration) {
+        this.totalQueuedDuration = totalQueuedDuration;
+    }
+
+    public long getMaxQueuedDuration() {
+        return maxQueuedDuration;
+    }
+
+    public void setMaxQueuedDuration(long maxQueuedDuration) {
+        this.maxQueuedDuration = maxQueuedDuration;
+    }
+
+    public FlowFileAvailability getFlowFileAvailability() {
+        return flowFileAvailability;
+    }
+
+    public void setFlowFileAvailability(final FlowFileAvailability availability) {
+        this.flowFileAvailability = availability;
+    }
+
+    @Override
+    public ConnectionStatus clone() {
+        final ConnectionStatus clonedObj = new ConnectionStatus();
+        clonedObj.groupId = groupId;
+        clonedObj.id = id;
+        clonedObj.loadBalanceStatus = loadBalanceStatus;
+        clonedObj.inputBytes = inputBytes;
+        clonedObj.inputCount = inputCount;
+        clonedObj.name = name;
+        clonedObj.outputBytes = outputBytes;
+        clonedObj.outputCount = outputCount;
+        clonedObj.queuedBytes = queuedBytes;
+        clonedObj.queuedCount = queuedCount;
+        clonedObj.sourceId = sourceId;
+        clonedObj.sourceName = sourceName;
+        clonedObj.destinationId = destinationId;
+        clonedObj.destinationName = destinationName;
+        clonedObj.flowFileAvailability = flowFileAvailability;
+
+        if (predictions != null) {
+            clonedObj.setPredictions(predictions.clone());
+        }
+
+        clonedObj.backPressureDataSizeThreshold = backPressureDataSizeThreshold;
+        clonedObj.backPressureObjectThreshold = backPressureObjectThreshold;
+        clonedObj.maxQueuedBytes = maxQueuedBytes;
+        clonedObj.maxQueuedCount = maxQueuedCount;
+        clonedObj.totalQueuedDuration = totalQueuedDuration;
+        clonedObj.maxQueuedDuration = maxQueuedDuration;
+        return clonedObj;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("ConnectionStatus [id=");
+        builder.append(id);
+        builder.append(", groupId=");
+        builder.append(groupId);
+        builder.append(", name=");
+        builder.append(name);
+        builder.append(", sourceId=");
+        builder.append(sourceId);
+        builder.append(", sourceName=");
+        builder.append(sourceName);
+        builder.append(", destinationId=");
+        builder.append(destinationId);
+        builder.append(", destinationName=");
+        builder.append(destinationName);
+        builder.append(", backPressureDataSizeThreshold=");
+        builder.append(backPressureDataSizeThreshold);
+        builder.append(", backPressureObjectThreshold=");
+        builder.append(backPressureObjectThreshold);
+        builder.append(", flowFileAvailability=");
+        builder.append(flowFileAvailability);
+        builder.append(", inputCount=");
+        builder.append(inputCount);
+        builder.append(", inputBytes=");
+        builder.append(inputBytes);
+        builder.append(", queuedCount=");
+        builder.append(queuedCount);
+        builder.append(", queuedBytes=");
+        builder.append(queuedBytes);
+        builder.append(", outputCount=");
+        builder.append(outputCount);
+        builder.append(", outputBytes=");
+        builder.append(outputBytes);
+        builder.append(", maxQueuedCount=");
+        builder.append(maxQueuedCount);
+        builder.append(", maxQueueBytes=");
+        builder.append(maxQueuedBytes);
+        builder.append(", totalActiveQueuedDuration=");
+        builder.append(totalQueuedDuration);
+        builder.append(", maxActiveQueuedDuration=");
+        builder.append(maxQueuedDuration);
+        builder.append(", loadBalanceStatus=");
+        builder.append(loadBalanceStatus);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java b/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java
new file mode 100644
index 0000000..d805312
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/status/FlowFileAvailability.java
@@ -0,0 +1,26 @@
+/*
+ * 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.status;
+
+public enum FlowFileAvailability {
+    ACTIVE_QUEUE_EMPTY,
+
+    HEAD_OF_QUEUE_PENALIZED,
+
+    FLOWFILE_AVAILABLE;
+}
diff --git a/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java b/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java
new file mode 100644
index 0000000..bd3bed4
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/status/LoadBalanceStatus.java
@@ -0,0 +1,26 @@
+/*
+ * 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.status;
+
+public enum LoadBalanceStatus {
+    LOAD_BALANCE_NOT_CONFIGURED,
+
+    LOAD_BALANCE_ACTIVE,
+
+    LOAD_BALANCE_INACTIVE;
+}
diff --git a/src/main/java/org/apache/nifi/controller/status/NodeStatus.java b/src/main/java/org/apache/nifi/controller/status/NodeStatus.java
new file mode 100644
index 0000000..0414ee2
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/status/NodeStatus.java
@@ -0,0 +1,207 @@
+/*
+ * 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.status;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The status of a NiFi node.
+ */
+public class NodeStatus implements Cloneable {
+    private long createdAtInMs;
+
+    private long freeHeap;
+    private long usedHeap;
+    private long heapUtilization;
+    private long freeNonHeap;
+    private long usedNonHeap;
+
+    private long openFileHandlers;
+    private double processorLoadAverage;
+
+    private long totalThreads;
+    private long timerDrivenThreads;
+
+    private long flowFileRepositoryFreeSpace;
+    private long flowFileRepositoryUsedSpace;
+
+    private List<StorageStatus> contentRepositories = new ArrayList<>();
+    private List<StorageStatus> provenanceRepositories = new ArrayList<>();
+
+    public long getCreatedAtInMs() {
+        return createdAtInMs;
+    }
+
+    public void setCreatedAtInMs(final long createdAtInMs) {
+        this.createdAtInMs = createdAtInMs;
+    }
+
+    public long getFreeHeap() {
+        return freeHeap;
+    }
+
+    public void setFreeHeap(final long freeHeap) {
+        this.freeHeap = freeHeap;
+    }
+
+    public long getUsedHeap() {
+        return usedHeap;
+    }
+
+    public void setUsedHeap(final long usedHeap) {
+        this.usedHeap = usedHeap;
+    }
+
+    public long getHeapUtilization() {
+        return heapUtilization;
+    }
+
+    public void setHeapUtilization(final long heapUtilization) {
+        this.heapUtilization = heapUtilization;
+    }
+
+    public long getFreeNonHeap() {
+        return freeNonHeap;
+    }
+
+    public void setFreeNonHeap(final long freeNonHeap) {
+        this.freeNonHeap = freeNonHeap;
+    }
+
+    public long getUsedNonHeap() {
+        return usedNonHeap;
+    }
+
+    public void setUsedNonHeap(final long usedNonHeap) {
+        this.usedNonHeap = usedNonHeap;
+    }
+
+    public long getOpenFileHandlers() {
+        return openFileHandlers;
+    }
+
+    public void setOpenFileHandlers(final long openFileHandlers) {
+        this.openFileHandlers = openFileHandlers;
+    }
+
+    public double getProcessorLoadAverage() {
+        return processorLoadAverage;
+    }
+
+    public void setProcessorLoadAverage(final double processorLoadAverage) {
+        this.processorLoadAverage = processorLoadAverage;
+    }
+
+    public long getTotalThreads() {
+        return totalThreads;
+    }
+
+    public void setTotalThreads(final long totalThreads) {
+        this.totalThreads = totalThreads;
+    }
+
+    public long getTimerDrivenThreads() {
+        return timerDrivenThreads;
+    }
+
+    public void setTimerDrivenThreads(final long timerDrivenThreads) {
+        this.timerDrivenThreads = timerDrivenThreads;
+    }
+
+    public long getFlowFileRepositoryFreeSpace() {
+        return flowFileRepositoryFreeSpace;
+    }
+
+    public void setFlowFileRepositoryFreeSpace(final long flowFileRepositoryFreeSpace) {
+        this.flowFileRepositoryFreeSpace = flowFileRepositoryFreeSpace;
+    }
+
+    public long getFlowFileRepositoryUsedSpace() {
+        return flowFileRepositoryUsedSpace;
+    }
+
+    public void setFlowFileRepositoryUsedSpace(final long flowFileRepositoryUsedSpace) {
+        this.flowFileRepositoryUsedSpace = flowFileRepositoryUsedSpace;
+    }
+
+    public List<StorageStatus> getContentRepositories() {
+        return contentRepositories;
+    }
+
+    public void setContentRepositories(final List<StorageStatus> contentRepositories) {
+        this.contentRepositories = new ArrayList<>();
+        this.contentRepositories.addAll(contentRepositories);
+    }
+
+    public List<StorageStatus> getProvenanceRepositories() {
+        return provenanceRepositories;
+    }
+
+    public void setProvenanceRepositories(final List<StorageStatus> provenanceRepositories) {
+        this.provenanceRepositories = new ArrayList<>();
+        this.provenanceRepositories.addAll(provenanceRepositories);
+    }
+
+    @Override
+    protected NodeStatus clone() {
+        final NodeStatus clonedObj = new NodeStatus();
+        clonedObj.createdAtInMs = createdAtInMs;
+        clonedObj.freeHeap = freeHeap;
+        clonedObj.usedHeap = usedHeap;
+        clonedObj.heapUtilization = heapUtilization;
+        clonedObj.freeNonHeap = freeNonHeap;
+        clonedObj.usedNonHeap = usedNonHeap;
+        clonedObj.openFileHandlers = openFileHandlers;
+        clonedObj.processorLoadAverage = processorLoadAverage;
+        clonedObj.totalThreads = totalThreads;
+        clonedObj.timerDrivenThreads = timerDrivenThreads;
+        clonedObj.flowFileRepositoryFreeSpace = flowFileRepositoryFreeSpace;
+        clonedObj.flowFileRepositoryUsedSpace = flowFileRepositoryUsedSpace;
+
+        final List<StorageStatus> clonedContentRepositories = new ArrayList<>();
+        contentRepositories.stream().map(r -> r.clone()).forEach(r -> clonedContentRepositories.add(r));
+        clonedObj.contentRepositories = clonedContentRepositories;
+
+        final List<StorageStatus> clonedProvenanceRepositories = new ArrayList<>();
+        provenanceRepositories.stream().map(r -> r.clone()).forEach(r -> clonedProvenanceRepositories.add(r));
+        clonedObj.provenanceRepositories = clonedProvenanceRepositories;
+
+        return clonedObj;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("NodeStatus{");
+        sb.append("createdAtInMs=").append(createdAtInMs);
+        sb.append(", freeHeap=").append(freeHeap);
+        sb.append(", usedHeap=").append(usedHeap);
+        sb.append(", heapUtilization=").append(heapUtilization);
+        sb.append(", freeNonHeap=").append(freeNonHeap);
+        sb.append(", usedNonHeap=").append(usedNonHeap);
+        sb.append(", openFileHandlers=").append(openFileHandlers);
+        sb.append(", processorLoadAverage=").append(processorLoadAverage);
+        sb.append(", totalThreads=").append(totalThreads);
+        sb.append(", timerDrivenThreads=").append(timerDrivenThreads);
+        sb.append(", flowFileRepositoryFreeSpace=").append(flowFileRepositoryFreeSpace);
+        sb.append(", flowFileRepositoryUsedSpace=").append(flowFileRepositoryUsedSpace);
+        sb.append(", contentRepositories=").append(contentRepositories);
+        sb.append(", provenanceRepositories=").append(provenanceRepositories);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/nifi/controller/status/PortStatus.java b/src/main/java/org/apache/nifi/controller/status/PortStatus.java
new file mode 100644
index 0000000..0d248cd
--- /dev/null
+++ b/src/main/java/org/apache/nifi/controller/status/PortStatus.java
@@ -0,0 +1,201 @@
+/*
+ * 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.status;
+
+/**
+ * The status of a port.
+ */
+public class PortStatus implements Cloneable {
+
+    private String id;
+    private String groupId;
+    private String name;
+    private Integer activeThreadCount;
+    private int inputCount;
+    private long inputBytes;
+    private int outputCount;
+    private long outputBytes;
+    private int flowFilesReceived;
+    private long bytesReceived;
+    private int flowFilesSent;
+    private long bytesSent;
+    private Boolean transmitting;
+    private RunStatus runStatus;
+
+    public Boolean isTransmitting() {
+        return transmitting;
+    }
+
+    public void setTransmitting(Boolean transmitting) {
+        this.transmitting = transmitting;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getActiveThreadCount() {
+        return activeThreadCount;
+    }
+
+    public void setActiveThreadCount(Integer activeThreadCount) {
+        this.activeThreadCount = activeThreadCount;
+    }
+
+    public int getInputCount() {
+        return inputCount;
+    }
+
+    public void setInputCount(int inputCount) {
+        this.inputCount = inputCount;
+    }
+
+    public long getInputBytes() {
+        return inputBytes;
+    }
+
+    public void setInputBytes(long inputBytes) {
+        this.inputBytes = inputBytes;
+    }
+
+    public int getOutputCount() {
+        return outputCount;
+    }
+
+    public void setOutputCount(final int outputCount) {
+        this.outputCount = outputCount;
+    }
+
+    public long getOutputBytes() {
+        return outputBytes;
+    }
+
+    public void setOutputBytes(final long outputBytes) {
+        this.outputBytes = outputBytes;
+    }
+
+    public RunStatus getRunStatus() {
+        return runStatus;
+    }
+
+    public void setRunStatus(RunStatus runStatus) {
+        this.runStatus = runStatus;
+    }
+
+    public int getFlowFilesReceived() {
+        return flowFilesReceived;
+    }
+
+    public void setFlowFilesReceived(int flowFilesReceived) {
+        this.flowFilesReceived = flowFilesReceived;
+    }
+
+    public long getBytesReceived() {
+        return bytesReceived;
+    }
+
+    public void setBytesReceived(long bytesReceived) {
+        this.bytesReceived = bytesReceived;
+    }
+
+    public int getFlowFilesSent() {
+        return flowFilesSent;
... 20258 lines suppressed ...