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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0f02dac  NIFI-9452 Generate a RuntimeManifest for NiFi at build time - 
Write additional fields to extnesion-manifest.xml for processors - Update C2 
model classes to support new fields for processors, properties, and scheduling 
- Create converter between NiFi model and C2 model - Create generator and 
execute during the build
0f02dac is described below

commit 0f02dac3fa57885ed47957211f1abd36af69f7ed
Author: Bryan Bende <[email protected]>
AuthorDate: Mon Dec 6 17:05:03 2021 -0500

    NIFI-9452 Generate a RuntimeManifest for NiFi at build time
    - Write additional fields to extnesion-manifest.xml for processors
    - Update C2 model classes to support new fields for processors, properties, 
and scheduling
    - Create converter between NiFi model and C2 model
    - Create generator and execute during the build
    
    Add profile to nifi-assembly that skips the binary assembly, update github 
workflow to enable this profile instead of excluding nifi-assembly
    
    Add additional documentation on new fields in processor definition and 
reporting task definition
    
    Set charset to UTF-8 on the OutputStreamWriter
    
    Signed-off-by: Matthew Burgess <[email protected]>
    
    This closes #5612
---
 .github/workflows/ci-workflow.yml                  |   2 +-
 c2/c2-protocol/c2-protocol-component-api/pom.xml   |   6 +-
 .../component/api/ProcessorDefinition.java         | 159 ++++++-
 .../protocol/component/api/PropertyDependency.java |  60 +++
 .../protocol/component/api/PropertyDescriptor.java |  20 +
 .../component/api/PropertyResourceDefinition.java  |  52 +++
 .../component/api/ReportingTaskDefinition.java     |  18 +-
 .../protocol/component/api/SchedulingDefaults.java |  23 +
 .../documentation/AbstractDocumentationWriter.java |  39 +-
 .../documentation/xml/XmlDocumentationWriter.java  |  91 ++++
 nifi-assembly/pom.xml                              |  21 +
 nifi-manifest/nifi-runtime-manifest-core/pom.xml   |  43 ++
 .../runtime/manifest/ComponentManifestBuilder.java |  52 +++
 .../manifest/ExtensionManifestProvider.java        |  30 ++
 .../runtime/manifest/RuntimeManifestBuilder.java   |  89 ++++
 .../manifest/RuntimeManifestSerializer.java        |  38 ++
 .../impl/DirectoryExtensionManifestProvider.java   |  86 ++++
 .../impl/JacksonRuntimeManifestSerializer.java     |  45 ++
 .../manifest/impl/RuntimeManifestGenerator.java    | 145 ++++++
 .../manifest/impl/SchedulingDefaultsFactory.java   |  48 ++
 .../impl/StandardComponentManifestBuilder.java     |  73 +++
 .../impl/StandardRuntimeManifestBuilder.java       | 502 +++++++++++++++++++++
 nifi-manifest/nifi-runtime-manifest-test/pom.xml   |  63 +++
 .../nifi/runtime/manifest/TestRuntimeManifest.java | 251 +++++++++++
 nifi-manifest/nifi-runtime-manifest/pom.xml        | 155 +++++++
 .../src/main/resources/build.properties            |  29 ++
 nifi-manifest/pom.xml                              |  32 ++
 .../docs/TestJacksonExtensionManifestParser.java   |  43 ++
 .../extension-manifest-test-components.xml         |  17 +
 .../component/manifest/DefaultSchedule.java        |  60 +++
 .../component/manifest/DefaultSettings.java        |  60 +++
 .../extension/component/manifest/Extension.java    |  91 ++++
 pom.xml                                            |   1 +
 33 files changed, 2424 insertions(+), 20 deletions(-)

diff --git a/.github/workflows/ci-workflow.yml 
b/.github/workflows/ci-workflow.yml
index 2cd3065..0da281b 100644
--- a/.github/workflows/ci-workflow.yml
+++ b/.github/workflows/ci-workflow.yml
@@ -36,8 +36,8 @@ env:
   MAVEN_PROFILES: >-
     -P contrib-check
     -P include-grpc
+    -P skip-nifi-bin-assembly
   MAVEN_PROJECTS: >-
-    -pl -nifi-assembly
     -pl -nifi-toolkit/nifi-toolkit-assembly
     -pl -nifi-registry/nifi-registry-assembly
     -pl -minifi/minifi-assembly
diff --git a/c2/c2-protocol/c2-protocol-component-api/pom.xml 
b/c2/c2-protocol/c2-protocol-component-api/pom.xml
index 66d33e7..19bcfa7 100644
--- a/c2/c2-protocol/c2-protocol-component-api/pom.xml
+++ b/c2/c2-protocol/c2-protocol-component-api/pom.xml
@@ -15,9 +15,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
  -->
-<project xmlns="https://maven.apache.org/POM/4.0.0";
-         xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance";
-         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
     <parent>
         <artifactId>c2-protocol</artifactId>
         <groupId>org.apache.nifi</groupId>
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ProcessorDefinition.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ProcessorDefinition.java
index 385342e..0d61495 100644
--- 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ProcessorDefinition.java
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ProcessorDefinition.java
@@ -37,8 +37,25 @@ public class ProcessorDefinition extends ExtensionComponent 
implements Configura
     private List<Relationship> supportedRelationships;
     private boolean supportsDynamicRelationships;
 
+    private boolean triggerSerially;
+    private boolean triggerWhenEmpty;
+    private boolean triggerWhenAnyDestinationAvailable;
+    private boolean supportsBatching;
+    private boolean supportsEventDriven;
+    private boolean primaryNodeOnly;
+    private boolean sideEffectFree;
+
+    private List<String> supportedSchedulingStrategies;
+    private String defaultSchedulingStrategy;
+    private Map<String, Integer> defaultConcurrentTasksBySchedulingStrategy;
+    private Map<String, String> defaultSchedulingPeriodBySchedulingStrategy;
+
+    private String defaultPenaltyDuration;
+    private String defaultYieldDuration;
+    private String defaultBulletinLevel;
+
     @Override
-    @ApiModelProperty("Descriptions of configuration properties applicable to 
this reporting task")
+    @ApiModelProperty("Descriptions of configuration properties applicable to 
this processor.")
     public Map<String, PropertyDescriptor> getPropertyDescriptors() {
         return (propertyDescriptors != null ? 
Collections.unmodifiableMap(propertyDescriptors) : null);
     }
@@ -49,7 +66,7 @@ public class ProcessorDefinition extends ExtensionComponent 
implements Configura
     }
 
     @Override
-    @ApiModelProperty("Whether or not this processor makes use of dynamic 
(user-set) properties")
+    @ApiModelProperty("Whether or not this processor makes use of dynamic 
(user-set) properties.")
     public boolean getSupportsDynamicProperties() {
         return supportsDynamicProperties;
     }
@@ -59,7 +76,7 @@ public class ProcessorDefinition extends ExtensionComponent 
implements Configura
         this.supportsDynamicProperties = supportsDynamicProperties;
     }
 
-    @ApiModelProperty("Any input requirements this processor has")
+    @ApiModelProperty("Any input requirements this processor has.")
     public InputRequirement.Requirement getInputRequirement() {
         return inputRequirement;
     }
@@ -68,7 +85,7 @@ public class ProcessorDefinition extends ExtensionComponent 
implements Configura
         this.inputRequirement = inputRequirement;
     }
 
-    @ApiModelProperty("The supported relationships for this processor")
+    @ApiModelProperty("The supported relationships for this processor.")
     public List<Relationship> getSupportedRelationships() {
         return (supportedRelationships == null ? Collections.emptyList() : 
Collections.unmodifiableList(supportedRelationships));
     }
@@ -77,7 +94,7 @@ public class ProcessorDefinition extends ExtensionComponent 
implements Configura
         this.supportedRelationships = supportedRelationships;
     }
 
-    @ApiModelProperty("Whether or not this processor supports dynamic 
relationships")
+    @ApiModelProperty("Whether or not this processor supports dynamic 
relationships.")
     public boolean getSupportsDynamicRelationships() {
         return supportsDynamicRelationships;
     }
@@ -85,4 +102,136 @@ public class ProcessorDefinition extends 
ExtensionComponent implements Configura
     public void setSupportsDynamicRelationships(boolean 
supportsDynamicRelationships) {
         this.supportsDynamicRelationships = supportsDynamicRelationships;
     }
+
+    @ApiModelProperty("Whether or not this processor should be triggered 
serially (i.e. no concurrent execution).")
+    public boolean getTriggerSerially() {
+        return triggerSerially;
+    }
+
+    public void setTriggerSerially(boolean triggerSerially) {
+        this.triggerSerially = triggerSerially;
+    }
+
+    @ApiModelProperty("Whether or not this processor should be triggered when 
incoming queues are empty.")
+    public boolean getTriggerWhenEmpty() {
+        return triggerWhenEmpty;
+    }
+
+    public void setTriggerWhenEmpty(boolean triggerWhenEmpty) {
+        this.triggerWhenEmpty = triggerWhenEmpty;
+    }
+
+    @ApiModelProperty("Whether or not this processor should be triggered when 
any destination queue has room.")
+    public boolean getTriggerWhenAnyDestinationAvailable() {
+        return triggerWhenAnyDestinationAvailable;
+    }
+
+    public void setTriggerWhenAnyDestinationAvailable(boolean 
triggerWhenAnyDestinationAvailable) {
+        this.triggerWhenAnyDestinationAvailable = 
triggerWhenAnyDestinationAvailable;
+    }
+
+    @ApiModelProperty("Whether or not this processor supports batching. If a 
Processor uses this annotation, " +
+            "it allows the Framework to batch calls to session commits, as 
well as allowing the Framework to return " +
+            "the same session multiple times.")
+    public boolean getSupportsBatching() {
+        return supportsBatching;
+    }
+
+    public void setSupportsBatching(boolean supportsBatching) {
+        this.supportsBatching = supportsBatching;
+    }
+
+    @ApiModelProperty("Whether or not this processor supports event driven 
scheduling. Indicates to the framework that the " +
+            "Processor is eligible to be scheduled to run based on the 
occurrence of an \"Event\" " +
+            "(e.g., when a FlowFile is enqueued in an incoming Connection), 
rather than being triggered periodically.")
+    public boolean getSupportsEventDriven() {
+        return supportsEventDriven;
+    }
+
+    public void setSupportsEventDriven(boolean supportsEventDriven) {
+        this.supportsEventDriven = supportsEventDriven;
+    }
+
+    @ApiModelProperty("Whether or not this processor should be scheduled only 
on the primary node in a cluster.")
+    public boolean getPrimaryNodeOnly() {
+        return primaryNodeOnly;
+    }
+
+    public void setPrimaryNodeOnly(boolean primaryNodeOnly) {
+        this.primaryNodeOnly = primaryNodeOnly;
+    }
+
+    @ApiModelProperty("Whether or not this processor is considered side-effect 
free. Side-effect free indicate that the " +
+            "processor's operations on FlowFiles can be safely repeated across 
process sessions.")
+    public boolean getSideEffectFree() {
+        return sideEffectFree;
+    }
+
+    public void setSideEffectFree(boolean sideEffectFree) {
+        this.sideEffectFree = sideEffectFree;
+    }
+
+    @ApiModelProperty("The supported scheduling strategies, such as 
TIME_DRIVER, CRON, or EVENT_DRIVEN.")
+    public List<String> getSupportedSchedulingStrategies() {
+        return supportedSchedulingStrategies;
+    }
+
+    public void setSupportedSchedulingStrategies(List<String> 
supportedSchedulingStrategies) {
+        this.supportedSchedulingStrategies = supportedSchedulingStrategies;
+    }
+
+    @ApiModelProperty("The default scheduling strategy for the processor.")
+    public String getDefaultSchedulingStrategy() {
+        return defaultSchedulingStrategy;
+    }
+
+    public void setDefaultSchedulingStrategy(String defaultSchedulingStrategy) 
{
+        this.defaultSchedulingStrategy = defaultSchedulingStrategy;
+    }
+
+    @ApiModelProperty("The default concurrent tasks for each scheduling 
strategy.")
+    public Map<String, Integer> 
getDefaultConcurrentTasksBySchedulingStrategy() {
+        return defaultConcurrentTasksBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultConcurrentTasksBySchedulingStrategy) : null;
+    }
+
+    public void setDefaultConcurrentTasksBySchedulingStrategy(Map<String, 
Integer> defaultConcurrentTasksBySchedulingStrategy) {
+        this.defaultConcurrentTasksBySchedulingStrategy = 
defaultConcurrentTasksBySchedulingStrategy;
+    }
+
+    @ApiModelProperty("The default scheduling period for each scheduling 
strategy. " +
+            "The scheduling period is expected to be a time period, such as 
\"30 sec\".")
+    public Map<String, String> 
getDefaultSchedulingPeriodBySchedulingStrategy() {
+        return defaultSchedulingPeriodBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultSchedulingPeriodBySchedulingStrategy) : null;
+    }
+
+    public void setDefaultSchedulingPeriodBySchedulingStrategy(Map<String, 
String> defaultSchedulingPeriodBySchedulingStrategy) {
+        this.defaultSchedulingPeriodBySchedulingStrategy = 
defaultSchedulingPeriodBySchedulingStrategy;
+    }
+
+    @ApiModelProperty("The default penalty duration as a time period, such as 
\"30 sec\".")
+    public String getDefaultPenaltyDuration() {
+        return defaultPenaltyDuration;
+    }
+
+    public void setDefaultPenaltyDuration(String defaultPenaltyDuration) {
+        this.defaultPenaltyDuration = defaultPenaltyDuration;
+    }
+
+    @ApiModelProperty("The default yield duration as a time period, such as 
\"1 sec\".")
+    public String getDefaultYieldDuration() {
+        return defaultYieldDuration;
+    }
+
+    public void setDefaultYieldDuration(String defaultYieldDuration) {
+        this.defaultYieldDuration = defaultYieldDuration;
+    }
+
+    @ApiModelProperty("The default bulletin level, such as WARN, INFO, DEBUG, 
etc.")
+    public String getDefaultBulletinLevel() {
+        return defaultBulletinLevel;
+    }
+
+    public void setDefaultBulletinLevel(String defaultBulletinLevel) {
+        this.defaultBulletinLevel = defaultBulletinLevel;
+    }
 }
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDependency.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDependency.java
new file mode 100644
index 0000000..dbac2d1
--- /dev/null
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDependency.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.c2.protocol.component.api;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.List;
+
+@ApiModel
+public class PropertyDependency implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String propertyName;
+    private String propertyDisplayName;
+    private List<String> dependentValues;
+
+    @ApiModelProperty("The name of the property that is depended upon")
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    public void setPropertyName(String propertyName) {
+        this.propertyName = propertyName;
+    }
+
+    @ApiModelProperty("The name of the property that is depended upon")
+    public String getPropertyDisplayName() {
+        return propertyDisplayName;
+    }
+
+    public void setPropertyDisplayName(String propertyDisplayName) {
+        this.propertyDisplayName = propertyDisplayName;
+    }
+
+    @ApiModelProperty("The values that satisfy the dependency")
+    public List<String> getDependentValues() {
+        return dependentValues;
+    }
+
+    public void setDependentValues(List<String> dependentValues) {
+        this.dependentValues = dependentValues;
+    }
+
+}
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDescriptor.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDescriptor.java
index 0a43e5d..60b60ba 100644
--- 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDescriptor.java
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyDescriptor.java
@@ -42,6 +42,8 @@ public class PropertyDescriptor implements Serializable {
     private String validRegex;
     private String validator;
     private boolean dynamic;
+    private PropertyResourceDefinition resourceDefinition;
+    private List<PropertyDependency> dependencies;
 
     @ApiModelProperty(value = "The name of the property key", required = true)
     public String getName() {
@@ -156,4 +158,22 @@ public class PropertyDescriptor implements Serializable {
     public void setDynamic(boolean dynamic) {
         this.dynamic = dynamic;
     }
+
+    @ApiModelProperty("Indicates that this property references external 
resources")
+    public PropertyResourceDefinition getResourceDefinition() {
+        return resourceDefinition;
+    }
+
+    public void setResourceDefinition(PropertyResourceDefinition 
resourceDefinition) {
+        this.resourceDefinition = resourceDefinition;
+    }
+
+    @ApiModelProperty("The dependencies that this property has on other 
properties")
+    public List<PropertyDependency> getDependencies() {
+        return dependencies;
+    }
+
+    public void setDependencies(List<PropertyDependency> dependencies) {
+        this.dependencies = dependencies;
+    }
 }
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyResourceDefinition.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyResourceDefinition.java
new file mode 100644
index 0000000..ebc6d2b
--- /dev/null
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/PropertyResourceDefinition.java
@@ -0,0 +1,52 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceType;
+
+import java.io.Serializable;
+import java.util.Set;
+
+@ApiModel
+public class PropertyResourceDefinition implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private ResourceCardinality cardinality;
+    private Set<ResourceType> resourceTypes;
+
+    @ApiModelProperty("The cardinality of the resource definition (i.e. single 
or multiple)")
+    public ResourceCardinality getCardinality() {
+        return cardinality;
+    }
+
+    public void setCardinality(ResourceCardinality cardinality) {
+        this.cardinality = cardinality;
+    }
+
+    @ApiModelProperty("The types of resources that can be referenced")
+    public Set<ResourceType> getResourceTypes() {
+        return resourceTypes;
+    }
+
+    public void setResourceTypes(Set<ResourceType> resourceTypes) {
+        this.resourceTypes = resourceTypes;
+    }
+
+}
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ReportingTaskDefinition.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ReportingTaskDefinition.java
index 81433dd..d231796 100644
--- 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ReportingTaskDefinition.java
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ReportingTaskDefinition.java
@@ -32,7 +32,7 @@ public class ReportingTaskDefinition extends 
ExtensionComponent implements Confi
     private Map<String, PropertyDescriptor> propertyDescriptors;
     private List<String> supportedSchedulingStrategies;
     private String defaultSchedulingStrategy;
-    private Map<String, Map<String, String>> defaultValuesBySchedulingStrategy;
+    private Map<String, String> defaultSchedulingPeriodBySchedulingStrategy;
     private boolean supportsDynamicProperties;
 
     @Override
@@ -57,7 +57,7 @@ public class ReportingTaskDefinition extends 
ExtensionComponent implements Confi
         this.supportsDynamicProperties = supportsDynamicProperties;
     }
 
-    @ApiModelProperty
+    @ApiModelProperty("The supported scheduling strategies, such as 
TIME_DRIVER or CRON.")
     public List<String> getSupportedSchedulingStrategies() {
         return (supportedSchedulingStrategies != null ? 
Collections.unmodifiableList(supportedSchedulingStrategies) : null);
     }
@@ -66,7 +66,7 @@ public class ReportingTaskDefinition extends 
ExtensionComponent implements Confi
         this.supportedSchedulingStrategies = supportedSchedulingStrategies;
     }
 
-    @ApiModelProperty
+    @ApiModelProperty("The default scheduling strategy for the reporting 
task.")
     public String getDefaultSchedulingStrategy() {
         return defaultSchedulingStrategy;
     }
@@ -75,12 +75,14 @@ public class ReportingTaskDefinition extends 
ExtensionComponent implements Confi
         this.defaultSchedulingStrategy = defaultSchedulingStrategy;
     }
 
-    @ApiModelProperty
-    public Map<String, Map<String, String>> 
getDefaultValuesBySchedulingStrategy() {
-        return (defaultValuesBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultValuesBySchedulingStrategy) : null);
+    @ApiModelProperty("The default scheduling period for each scheduling 
strategy. " +
+            "The scheduling period is expected to be a time period, such as 
\"30 sec\".")
+    public Map<String, String> 
getDefaultSchedulingPeriodBySchedulingStrategy() {
+        return defaultSchedulingPeriodBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultSchedulingPeriodBySchedulingStrategy) : null;
     }
 
-    public void setDefaultValuesBySchedulingStrategy(Map<String, Map<String, 
String>> defaultValuesBySchedulingStrategy) {
-        this.defaultValuesBySchedulingStrategy = 
defaultValuesBySchedulingStrategy;
+    public void setDefaultSchedulingPeriodBySchedulingStrategy(Map<String, 
String> defaultSchedulingPeriodBySchedulingStrategy) {
+        this.defaultSchedulingPeriodBySchedulingStrategy = 
defaultSchedulingPeriodBySchedulingStrategy;
     }
+
 }
diff --git 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/SchedulingDefaults.java
 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/SchedulingDefaults.java
index ec7654b..9a9d6d9 100644
--- 
a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/SchedulingDefaults.java
+++ 
b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/SchedulingDefaults.java
@@ -22,6 +22,8 @@ import io.swagger.annotations.ApiModelProperty;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 
 import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
 
 @ApiModel
 public class SchedulingDefaults implements Serializable {
@@ -34,6 +36,9 @@ public class SchedulingDefaults implements Serializable {
     private long defaultRunDurationNanos;
     private String defaultMaxConcurrentTasks;
 
+    private Map<String, Integer> defaultConcurrentTasksBySchedulingStrategy;
+    private Map<String, String> defaultSchedulingPeriodsBySchedulingStrategy;
+
     @ApiModelProperty("The name of the default scheduling strategy")
     public SchedulingStrategy getDefaultSchedulingStrategy() {
         return defaultSchedulingStrategy;
@@ -88,4 +93,22 @@ public class SchedulingDefaults implements Serializable {
         this.defaultMaxConcurrentTasks = defaultMaxConcurrentTasks;
     }
 
+    @ApiModelProperty("The default concurrent tasks for each scheduling 
strategy")
+    public Map<String, Integer> 
getDefaultConcurrentTasksBySchedulingStrategy() {
+        return defaultConcurrentTasksBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultConcurrentTasksBySchedulingStrategy) : null;
+    }
+
+    public void setDefaultConcurrentTasksBySchedulingStrategy(Map<String, 
Integer> defaultConcurrentTasksBySchedulingStrategy) {
+        this.defaultConcurrentTasksBySchedulingStrategy = 
defaultConcurrentTasksBySchedulingStrategy;
+    }
+
+    @ApiModelProperty("The default scheduling period for each scheduling 
strategy")
+    public Map<String, String> 
getDefaultSchedulingPeriodsBySchedulingStrategy() {
+        return defaultSchedulingPeriodsBySchedulingStrategy != null ? 
Collections.unmodifiableMap(defaultSchedulingPeriodsBySchedulingStrategy) : 
null;
+    }
+
+    public void setDefaultSchedulingPeriodsBySchedulingStrategy(Map<String, 
String> defaultSchedulingPeriodsBySchedulingStrategy) {
+        this.defaultSchedulingPeriodsBySchedulingStrategy = 
defaultSchedulingPeriodsBySchedulingStrategy;
+    }
+
 }
diff --git 
a/nifi-api/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java
 
b/nifi-api/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java
index 2276090..fe1ac2b 100644
--- 
a/nifi-api/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java
+++ 
b/nifi-api/src/main/java/org/apache/nifi/documentation/AbstractDocumentationWriter.java
@@ -19,14 +19,23 @@ package org.apache.nifi.documentation;
 import org.apache.nifi.annotation.behavior.DynamicProperties;
 import org.apache.nifi.annotation.behavior.DynamicProperty;
 import org.apache.nifi.annotation.behavior.DynamicRelationship;
+import org.apache.nifi.annotation.behavior.EventDriven;
 import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.PrimaryNodeOnly;
 import org.apache.nifi.annotation.behavior.ReadsAttribute;
 import org.apache.nifi.annotation.behavior.ReadsAttributes;
 import org.apache.nifi.annotation.behavior.Restricted;
+import org.apache.nifi.annotation.behavior.SideEffectFree;
 import org.apache.nifi.annotation.behavior.Stateful;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
 import org.apache.nifi.annotation.behavior.SystemResourceConsideration;
+import org.apache.nifi.annotation.behavior.TriggerSerially;
+import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable;
+import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
 import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.annotation.configuration.DefaultSchedule;
+import org.apache.nifi.annotation.configuration.DefaultSettings;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.DeprecationNotice;
 import org.apache.nifi.annotation.documentation.SeeAlso;
@@ -127,6 +136,15 @@ public abstract class AbstractDocumentationWriter 
implements ExtensionDocumentat
             writeDynamicRelationship(getDynamicRelationship(processor));
             writeReadsAttributes(getReadsAttributes(processor));
             writeWritesAttributes(getWritesAttributes(processor));
+
+            
writeTriggerSerially(processor.getClass().getAnnotation(TriggerSerially.class));
+            
writeTriggerWhenEmpty(processor.getClass().getAnnotation(TriggerWhenEmpty.class));
+            
writeTriggerWhenAnyDestinationAvailable(processor.getClass().getAnnotation(TriggerWhenAnyDestinationAvailable.class));
+            
writeSupportsBatching(processor.getClass().getAnnotation(SupportsBatching.class));
+            
writeEventDriven(processor.getClass().getAnnotation(EventDriven.class));
+            
writePrimaryNodeOnly(processor.getClass().getAnnotation(PrimaryNodeOnly.class));
+            
writeSideEffectFree(processor.getClass().getAnnotation(SideEffectFree.class));
+            
writeDefaultSettings(processor.getClass().getAnnotation(DefaultSettings.class));
         }
 
         writeStatefulInfo(component.getClass().getAnnotation(Stateful.class));
@@ -134,9 +152,9 @@ public abstract class AbstractDocumentationWriter 
implements ExtensionDocumentat
         writeInputRequirementInfo(getInputRequirement(component));
         
writeSystemResourceConsiderationInfo(getSystemResourceConsiderations(component));
         writeSeeAlso(component.getClass().getAnnotation(SeeAlso.class));
+        
writeDefaultSchedule(component.getClass().getAnnotation(DefaultSchedule.class));
     }
 
-
     protected String getDescription(final ConfigurableComponent component) {
         final CapabilityDescription capabilityDescription = 
component.getClass().getAnnotation(CapabilityDescription.class);
         if (capabilityDescription == null) {
@@ -265,7 +283,7 @@ public abstract class AbstractDocumentationWriter 
implements ExtensionDocumentat
 
     protected abstract void writeSeeAlso(SeeAlso seeAlso) throws IOException;
 
-
+    protected abstract void writeDefaultSchedule(DefaultSchedule 
defaultSchedule) throws IOException;
 
     // Processor-specific methods
     protected abstract void writeRelationships(Set<Relationship> 
relationships) throws IOException;
@@ -276,10 +294,25 @@ public abstract class AbstractDocumentationWriter 
implements ExtensionDocumentat
 
     protected abstract void writeWritesAttributes(List<WritesAttribute> 
attributes) throws IOException;
 
+    protected abstract void writeTriggerSerially(TriggerSerially 
triggerSerially) throws IOException;
+
+    protected abstract void writeTriggerWhenEmpty(TriggerWhenEmpty 
triggerWhenEmpty) throws IOException;
+
+    protected abstract void 
writeTriggerWhenAnyDestinationAvailable(TriggerWhenAnyDestinationAvailable 
triggerWhenAnyDestinationAvailable) throws IOException;
+
+    protected abstract void writeSupportsBatching(SupportsBatching 
supportsBatching) throws IOException;
+
+    protected abstract void writeEventDriven(EventDriven eventDriven) throws 
IOException;
+
+    protected abstract void writePrimaryNodeOnly(PrimaryNodeOnly 
primaryNodeOnly) throws IOException;
+
+    protected abstract void writeSideEffectFree(SideEffectFree sideEffectFree) 
throws IOException;
+
+    protected abstract void writeDefaultSettings(DefaultSettings 
defaultSettings) throws IOException;
 
     // ControllerService-specific methods
     protected abstract void writeProvidedServices(Collection<ServiceAPI> 
providedServices) throws IOException;
 
-
     protected abstract void writeFooter(ConfigurableComponent component) 
throws IOException;
+
 }
diff --git 
a/nifi-api/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
 
b/nifi-api/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
index 4306f09..8b87237 100644
--- 
a/nifi-api/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
+++ 
b/nifi-api/src/main/java/org/apache/nifi/documentation/xml/XmlDocumentationWriter.java
@@ -18,13 +18,22 @@ package org.apache.nifi.documentation.xml;
 
 import org.apache.nifi.annotation.behavior.DynamicProperty;
 import org.apache.nifi.annotation.behavior.DynamicRelationship;
+import org.apache.nifi.annotation.behavior.EventDriven;
 import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.PrimaryNodeOnly;
 import org.apache.nifi.annotation.behavior.ReadsAttribute;
 import org.apache.nifi.annotation.behavior.Restricted;
 import org.apache.nifi.annotation.behavior.Restriction;
+import org.apache.nifi.annotation.behavior.SideEffectFree;
 import org.apache.nifi.annotation.behavior.Stateful;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
 import org.apache.nifi.annotation.behavior.SystemResourceConsideration;
+import org.apache.nifi.annotation.behavior.TriggerSerially;
+import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable;
+import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.configuration.DefaultSchedule;
+import org.apache.nifi.annotation.configuration.DefaultSettings;
 import org.apache.nifi.annotation.documentation.DeprecationNotice;
 import org.apache.nifi.annotation.documentation.SeeAlso;
 import org.apache.nifi.components.AllowableValue;
@@ -422,6 +431,88 @@ public class XmlDocumentationWriter extends 
AbstractDocumentationWriter {
     }
 
     @Override
+    protected void writeTriggerSerially(TriggerSerially triggerSerially) 
throws IOException {
+        if (triggerSerially == null) {
+            return;
+        }
+        writeBooleanElement("triggerSerially", true);
+    }
+
+    @Override
+    protected void writeTriggerWhenEmpty(TriggerWhenEmpty triggerWhenEmpty) 
throws IOException {
+        if (triggerWhenEmpty == null) {
+            return;
+        }
+        writeBooleanElement("triggerWhenEmpty", true);
+    }
+
+    @Override
+    protected void 
writeTriggerWhenAnyDestinationAvailable(TriggerWhenAnyDestinationAvailable 
triggerWhenAnyDestinationAvailable) throws IOException {
+        if (triggerWhenAnyDestinationAvailable == null) {
+            return;
+        }
+        writeBooleanElement("triggerWhenAnyDestinationAvailable", true);
+    }
+
+    @Override
+    protected void writeSupportsBatching(SupportsBatching supportsBatching) 
throws IOException {
+        if (supportsBatching == null) {
+            return;
+        }
+        writeBooleanElement("supportsBatching", true);
+    }
+
+    @Override
+    protected void writeEventDriven(EventDriven eventDriven) throws 
IOException {
+        if (eventDriven == null) {
+            return;
+        }
+        writeBooleanElement("eventDriven", true);
+    }
+
+    @Override
+    protected void writePrimaryNodeOnly(PrimaryNodeOnly primaryNodeOnly) 
throws IOException {
+        if (primaryNodeOnly == null) {
+            return;
+        }
+        writeBooleanElement("primaryNodeOnly", true);
+    }
+
+    @Override
+    protected void writeSideEffectFree(SideEffectFree sideEffectFree) throws 
IOException {
+        if (sideEffectFree == null) {
+            return;
+        }
+        writeBooleanElement("sideEffectFree", true);
+    }
+
+    @Override
+    protected void writeDefaultSchedule(DefaultSchedule defaultSchedule) 
throws IOException {
+        if (defaultSchedule == null) {
+            return;
+        }
+
+        writeStartElement("defaultSchedule");
+        writeTextElement("strategy", defaultSchedule.strategy().name());
+        writeTextElement("period", defaultSchedule.period());
+        writeTextElement("concurrentTasks", 
String.valueOf(defaultSchedule.concurrentTasks()));
+        writeEndElement();
+    }
+
+    @Override
+    protected void writeDefaultSettings(DefaultSettings defaultSettings) 
throws IOException {
+        if (defaultSettings == null) {
+            return;
+        }
+
+        writeStartElement("defaultSettings");
+        writeTextElement("yieldDuration", defaultSettings.yieldDuration());
+        writeTextElement("penaltyDuration", defaultSettings.penaltyDuration());
+        writeTextElement("bulletinLevel", 
defaultSettings.bulletinLevel().name());
+        writeEndElement();
+    }
+
+    @Override
     protected void writeFooter(final ConfigurableComponent component) throws 
IOException {
         writeEndElement();
     }
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 2141897..7caa9c2 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -1480,5 +1480,26 @@ language governing permissions and limitations under the 
License. -->
                 </plugins>
             </build>
         </profile>
+        <profile>
+            <id>skip-nifi-bin-assembly</id>
+            <activation>
+                <property>
+                    <name>skip-nifi-bin-assembly</name>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>make shared resource</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
     </profiles>
 </project>
diff --git a/nifi-manifest/nifi-runtime-manifest-core/pom.xml 
b/nifi-manifest/nifi-runtime-manifest-core/pom.xml
new file mode 100644
index 0000000..8b9aef3
--- /dev/null
+++ b/nifi-manifest/nifi-runtime-manifest-core/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-manifest</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-runtime-manifest-core</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>c2-protocol-component-api</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-bundle-utils</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-data-model</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
new file mode 100644
index 0000000..b649f69
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * 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.runtime.manifest;
+
+import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
+import org.apache.nifi.c2.protocol.component.api.ProcessorDefinition;
+import org.apache.nifi.c2.protocol.component.api.ReportingTaskDefinition;
+
+/**
+ * Builder for creating a ComponentManifest.
+ */
+public interface ComponentManifestBuilder {
+
+    /**
+     * @param processorDefinition a processor definition to add
+     * @return the builder
+     */
+    ComponentManifestBuilder addProcessor(ProcessorDefinition 
processorDefinition);
+
+    /**
+     * @param controllerServiceDefinition a controller service definition to 
add
+     * @return the builder
+     */
+    ComponentManifestBuilder addControllerService(ControllerServiceDefinition 
controllerServiceDefinition);
+
+    /**
+     * @param reportingTaskDefinition a reporting task definition to add
+     * @return the builder
+     */
+    ComponentManifestBuilder addReportingTask(ReportingTaskDefinition 
reportingTaskDefinition);
+
+    /**
+     * @return a component manifest containing all the added definitions
+     */
+    ComponentManifest build();
+
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.java
new file mode 100644
index 0000000..64a6bdd
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ExtensionManifestProvider.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.runtime.manifest;
+
+import org.apache.nifi.registry.extension.component.manifest.ExtensionManifest;
+
+import java.util.List;
+
+/**
+ * Provides a list of extension manifests.
+ */
+public interface ExtensionManifestProvider {
+
+    List<ExtensionManifest> getExtensionManifests();
+
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.java
new file mode 100644
index 0000000..04f3451
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestBuilder.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.runtime.manifest;
+
+import org.apache.nifi.c2.protocol.component.api.BuildInfo;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults;
+import org.apache.nifi.registry.extension.component.manifest.ExtensionManifest;
+
+/**
+ * Builder for creating a RuntimeManifest.
+ */
+public interface RuntimeManifestBuilder {
+
+    /**
+     * @param identifier the identifier for the manifest
+     * @return the builder
+     */
+    RuntimeManifestBuilder identifier(String identifier);
+
+    /**
+     * @param version the version for the manifest
+     * @return the builder
+     */
+    RuntimeManifestBuilder version(String version);
+
+    /**
+     * @param runtimeType the runtime type (i.e. nifi, nifi-stateless, 
minifi-cpp, etc)
+     * @return the builder
+     */
+    RuntimeManifestBuilder runtimeType(String runtimeType);
+
+    /**
+     * @param buildInfo the build info for the manifest
+     * @return the builder
+     */
+    RuntimeManifestBuilder buildInfo(BuildInfo buildInfo);
+
+    /**
+     * Adds a Bundle from the given ExtensionManifest.
+     *
+     * @param extensionManifest the extension manifest to add
+     * @return the builder
+     */
+    RuntimeManifestBuilder addBundle(ExtensionManifest extensionManifest);
+
+    /**
+     * Adds a Bundle for each of the given ExtensionManifests.
+     *
+     * @param extensionManifests the extension manifests to add
+     * @return the builder
+     */
+    RuntimeManifestBuilder addBundles(Iterable<ExtensionManifest> 
extensionManifests);
+
+    /**
+     * Adds the given Bundle.
+     *
+     * @param bundle the bundle to add
+     * @return the builder
+     */
+    RuntimeManifestBuilder addBundle(Bundle bundle);
+
+    /**
+     * @param schedulingDefaults the scheduling defaults
+     * @return the builder
+     */
+    RuntimeManifestBuilder schedulingDefaults(SchedulingDefaults 
schedulingDefaults);
+
+    /**
+     * @return a RuntimeManifest containing the added bundles
+     */
+    RuntimeManifest build();
+
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestSerializer.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestSerializer.java
new file mode 100644
index 0000000..ccf176a
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/RuntimeManifestSerializer.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.runtime.manifest;
+
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Serializer for runtime manifests.
+ */
+public interface RuntimeManifestSerializer {
+
+    /**
+     * Serializes the given RuntimeManifest to the given OutputStream.
+     *
+     * @param runtimeManifest the runtime manifest
+     * @param outputStream the output stream
+     * @throws IOException if an I/O error occurs during serialization
+     */
+    void write(RuntimeManifest runtimeManifest, OutputStream outputStream) 
throws IOException;
+
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java
new file mode 100644
index 0000000..3da8135
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/DirectoryExtensionManifestProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.runtime.manifest.impl;
+
+import 
org.apache.nifi.registry.bundle.extract.nar.docs.ExtensionManifestParser;
+import org.apache.nifi.registry.extension.component.manifest.ExtensionManifest;
+import org.apache.nifi.runtime.manifest.ExtensionManifestProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ExtensionManifestProvider that loads extension manifests from a directory 
where the nifi-assembly-manifests
+ * artifact was unpacked.
+ */
+public class DirectoryExtensionManifestProvider implements 
ExtensionManifestProvider {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(DirectoryExtensionManifestProvider.class);
+
+    private final File baseDir;
+    private final ExtensionManifestParser extensionManifestParser;
+
+    public DirectoryExtensionManifestProvider(final File baseDir, final 
ExtensionManifestParser extensionManifestParser) {
+        this.baseDir = baseDir;
+        this.extensionManifestParser = extensionManifestParser;
+    }
+
+    @Override
+    public List<ExtensionManifest> getExtensionManifests() {
+        if (!baseDir.exists()) {
+            throw new IllegalArgumentException("The specified manifest 
directory does not exist");
+        }
+        if (!baseDir.isDirectory()) {
+            throw new IllegalArgumentException("The specified manifest 
location is not a directory");
+        }
+
+        LOGGER.info("Loading extension manifests from: {}", 
baseDir.getAbsolutePath());
+
+        final List<ExtensionManifest> extensionManifests = new ArrayList<>();
+        for (final File manifestDir : baseDir.listFiles()) {
+            if (!manifestDir.isDirectory()) {
+                LOGGER.debug("Skipping [{}], not a directory...", 
manifestDir.getAbsolutePath());
+                continue;
+            }
+
+            final File manifestFile = new File(manifestDir, 
"extension-manifest.xml");
+            LOGGER.debug("Loading extension manifest file [{}]", 
manifestFile.getAbsolutePath());
+
+            final ExtensionManifest extensionManifest = 
loadExtensionManifest(manifestFile);
+            extensionManifests.add(extensionManifest);
+            LOGGER.debug("Successfully loaded extension manifest for 
[{}-{}-{}]",
+                    extensionManifest.getGroupId(), 
extensionManifest.getArtifactId(), extensionManifest.getVersion());
+        }
+
+        LOGGER.info("Loaded {} extension manifests", 
extensionManifests.size());
+        return extensionManifests;
+    }
+
+    private ExtensionManifest loadExtensionManifest(final File manifestFile) {
+        try (final InputStream inputStream = new 
FileInputStream(manifestFile)) {
+            return extensionManifestParser.parse(inputStream);
+        } catch (final IOException ioException) {
+            throw new RuntimeException("Unable to load extension manifest: " + 
manifestFile.getAbsolutePath(), ioException);
+        }
+    }
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/JacksonRuntimeManifestSerializer.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/JacksonRuntimeManifestSerializer.java
new file mode 100644
index 0000000..0550c3d
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/JacksonRuntimeManifestSerializer.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.runtime.manifest.impl;
+
+import com.fasterxml.jackson.databind.ObjectWriter;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.apache.nifi.runtime.manifest.RuntimeManifestSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Jackson implementation of RuntimeManifestSerializer.
+ */
+public class JacksonRuntimeManifestSerializer implements 
RuntimeManifestSerializer {
+
+    private final ObjectWriter objectWriter;
+
+    public JacksonRuntimeManifestSerializer(final ObjectWriter objectWriter) {
+        this.objectWriter = objectWriter;
+    }
+
+    @Override
+    public void write(final RuntimeManifest runtimeManifest, final 
OutputStream outputStream) throws IOException {
+        try (final OutputStreamWriter outputStreamWriter = new 
OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
+            objectWriter.writeValue(outputStreamWriter, runtimeManifest);
+        }
+    }
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java
new file mode 100644
index 0000000..f37016c
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/RuntimeManifestGenerator.java
@@ -0,0 +1,145 @@
+/*
+ * 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.runtime.manifest.impl;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import org.apache.nifi.c2.protocol.component.api.BuildInfo;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import 
org.apache.nifi.registry.bundle.extract.nar.docs.ExtensionManifestParser;
+import 
org.apache.nifi.registry.bundle.extract.nar.docs.JacksonExtensionManifestParser;
+import org.apache.nifi.runtime.manifest.ExtensionManifestProvider;
+import org.apache.nifi.runtime.manifest.RuntimeManifestSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+/**
+ * Runner class to be called during the build to generate a runtime manifest 
json file from a directory where
+ * all the extension-manifest.xml files have been unpacked.
+ */
+public class RuntimeManifestGenerator {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RuntimeManifestGenerator.class);
+
+    private static final String PROJECT_VERSION_PROPERTY = "Project-Version";
+    private static final String BUILD_REVISION = "Build-Revision";
+    private static final String BUILD_TIMESTAMP = "Build-Timestamp";
+    private static final String BUILD_JDK = "Build-Jdk";
+    private static final String BUILD_JDK_VENDOR = "Build-Jdk-Vendor";
+
+    private final File extensionManifestBaseDir;
+    private final File buildPropertiesFile;
+    private final File runtimeManifestFile;
+    private final String runtimeManifestId;
+
+    public RuntimeManifestGenerator(final File extensionManifestBaseDir,
+                                    final File buildPropertiesFile,
+                                    final File runtimeManifestFile,
+                                    final String runtimeManifestId) {
+        this.extensionManifestBaseDir = extensionManifestBaseDir;
+        this.buildPropertiesFile = buildPropertiesFile;
+        this.runtimeManifestFile = runtimeManifestFile;
+        this.runtimeManifestId = runtimeManifestId;
+    }
+
+    public void execute() throws IOException {
+        final ExtensionManifestProvider extensionManifestProvider = 
createExtensionManifestProvider();
+
+        final Properties buildProperties = createBuildProperties();
+        final String runtimeVersion = 
buildProperties.getProperty(PROJECT_VERSION_PROPERTY);
+        final String buildRevision = 
buildProperties.getProperty(BUILD_REVISION);
+        final String buildTimestamp = 
buildProperties.getProperty(BUILD_TIMESTAMP);
+        final String buildJdk = buildProperties.getProperty(BUILD_JDK);
+        final String buildJdkVendor = 
buildProperties.getProperty(BUILD_JDK_VENDOR);
+
+        final BuildInfo buildInfo = new BuildInfo();
+        buildInfo.setVersion(runtimeVersion);
+        buildInfo.setRevision(buildRevision);
+        buildInfo.setTimestamp(Long.valueOf(buildTimestamp));
+        buildInfo.setCompiler(buildJdkVendor + " " + buildJdk);
+
+        final RuntimeManifest runtimeManifest = new 
StandardRuntimeManifestBuilder()
+                .identifier(runtimeManifestId)
+                .version(runtimeVersion)
+                .runtimeType("nifi")
+                .buildInfo(buildInfo)
+                .addBundles(extensionManifestProvider.getExtensionManifests())
+                
.schedulingDefaults(SchedulingDefaultsFactory.getNifiSchedulingDefaults())
+                .build();
+
+        final RuntimeManifestSerializer runtimeManifestSerializer = 
createRuntimeManifestSerializer();
+        try (final OutputStream outputStream = new 
FileOutputStream(runtimeManifestFile)) {
+            runtimeManifestSerializer.write(runtimeManifest, outputStream);
+        }
+    }
+
+    private ExtensionManifestProvider createExtensionManifestProvider() {
+        final ExtensionManifestParser extensionManifestParser = new 
JacksonExtensionManifestParser();
+        return new 
DirectoryExtensionManifestProvider(extensionManifestBaseDir, 
extensionManifestParser);
+    }
+
+    private Properties createBuildProperties() throws IOException {
+        final Properties properties = new Properties();
+        try (final InputStream inputStream = new 
FileInputStream(buildPropertiesFile)) {
+            properties.load(inputStream);
+        }
+        return properties;
+    }
+
+    private RuntimeManifestSerializer createRuntimeManifestSerializer() {
+        final ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+        final ObjectWriter objectWriter = 
objectMapper.writerWithDefaultPrettyPrinter();
+        return new JacksonRuntimeManifestSerializer(objectWriter);
+    }
+
+    /**
+     * Called from maven-exec-plugin during build of nifi-runtime-manifest.
+     */
+    public static void main(String[] args) throws IOException {
+        if (args == null || args.length != 4) {
+            System.out.println("USAGE: <extension-manifest-base-dir> 
<build-props-file> <output-file> <manifest-id>");
+            return;
+        }
+
+        final File extensionManifestBaseDir = new File(args[0]);
+        final File buildPropertiesFile = new File(args[1]);
+        final File runtimeManifestFile = new File(args[2]);
+        final String runtimeManifestId = args[3];
+
+        final File runtimeManifestDir = runtimeManifestFile.getParentFile();
+        if (runtimeManifestDir != null) {
+            runtimeManifestDir.mkdirs();
+        }
+
+        LOGGER.info("Writing runtime manifest to: {}", 
runtimeManifestFile.getAbsolutePath());
+
+        final RuntimeManifestGenerator runner = new RuntimeManifestGenerator(
+                extensionManifestBaseDir, buildPropertiesFile, 
runtimeManifestFile, runtimeManifestId);
+        runner.execute();
+    }
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/SchedulingDefaultsFactory.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/SchedulingDefaultsFactory.java
new file mode 100644
index 0000000..d0c6909
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/SchedulingDefaultsFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.runtime.manifest.impl;
+
+import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class SchedulingDefaultsFactory {
+
+    public static SchedulingDefaults getNifiSchedulingDefaults() {
+        final Map<String, Integer> defaultConcurrentTasks = new 
LinkedHashMap<>(3);
+        defaultConcurrentTasks.put(SchedulingStrategy.TIMER_DRIVEN.name(), 
SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks());
+        defaultConcurrentTasks.put(SchedulingStrategy.EVENT_DRIVEN.name(), 
SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks());
+        defaultConcurrentTasks.put(SchedulingStrategy.CRON_DRIVEN.name(), 
SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks());
+
+        final Map<String, String> defaultSchedulingPeriods = new 
LinkedHashMap<>(2);
+        defaultSchedulingPeriods.put(SchedulingStrategy.TIMER_DRIVEN.name(), 
SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+        defaultSchedulingPeriods.put(SchedulingStrategy.CRON_DRIVEN.name(), 
SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+
+        final SchedulingDefaults schedulingDefaults = new SchedulingDefaults();
+        
schedulingDefaults.setDefaultSchedulingStrategy(SchedulingStrategy.TIMER_DRIVEN);
+        schedulingDefaults.setDefaultSchedulingPeriodMillis(0);
+        schedulingDefaults.setPenalizationPeriodMillis(30000);
+        schedulingDefaults.setYieldDurationMillis(1000);
+        schedulingDefaults.setDefaultRunDurationNanos(0);
+        schedulingDefaults.setDefaultMaxConcurrentTasks("1");
+        
schedulingDefaults.setDefaultConcurrentTasksBySchedulingStrategy(defaultConcurrentTasks);
+        
schedulingDefaults.setDefaultSchedulingPeriodsBySchedulingStrategy(defaultSchedulingPeriods);
+        return schedulingDefaults;
+    }
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java
new file mode 100644
index 0000000..8b76e72
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.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.runtime.manifest.impl;
+
+import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
+import org.apache.nifi.c2.protocol.component.api.ProcessorDefinition;
+import org.apache.nifi.c2.protocol.component.api.ReportingTaskDefinition;
+import org.apache.nifi.runtime.manifest.ComponentManifestBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Standard implementation of ComponentManifestBuilder.
+ */
+public class StandardComponentManifestBuilder implements 
ComponentManifestBuilder {
+
+    private final List<ProcessorDefinition> processors = new ArrayList<>();
+    private final List<ControllerServiceDefinition> controllerServices = new 
ArrayList<>();
+    private final List<ReportingTaskDefinition> reportingTasks = new 
ArrayList<>();
+
+    @Override
+    public ComponentManifestBuilder addProcessor(final ProcessorDefinition 
processorDefinition) {
+        if (processorDefinition == null) {
+            throw new IllegalArgumentException("Processor definition cannot be 
null");
+        }
+        processors.add(processorDefinition);
+        return this;
+    }
+
+    @Override
+    public ComponentManifestBuilder addControllerService(final 
ControllerServiceDefinition controllerServiceDefinition) {
+        if (controllerServiceDefinition == null) {
+            throw new IllegalArgumentException("Controller Service definition 
cannot be null");
+        }
+        controllerServices.add(controllerServiceDefinition);
+        return this;
+    }
+
+    @Override
+    public ComponentManifestBuilder addReportingTask(final 
ReportingTaskDefinition reportingTaskDefinition) {
+        if (reportingTaskDefinition == null) {
+            throw new IllegalArgumentException("Reporting task definition 
cannot be null");
+        }
+        reportingTasks.add(reportingTaskDefinition);
+        return this;
+    }
+
+    @Override
+    public ComponentManifest build() {
+        final ComponentManifest componentManifest = new ComponentManifest();
+        componentManifest.setProcessors(new ArrayList<>(processors));
+        componentManifest.setControllerServices(new 
ArrayList<>(controllerServices));
+        componentManifest.setReportingTasks(new ArrayList<>(reportingTasks));
+        return componentManifest;
+    }
+
+}
diff --git 
a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
new file mode 100644
index 0000000..ae88c85
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
@@ -0,0 +1,502 @@
+/*
+ * 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.runtime.manifest.impl;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.c2.protocol.component.api.BuildInfo;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
+import 
org.apache.nifi.c2.protocol.component.api.ConfigurableComponentDefinition;
+import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
+import org.apache.nifi.c2.protocol.component.api.DefinedType;
+import org.apache.nifi.c2.protocol.component.api.ExtensionComponent;
+import org.apache.nifi.c2.protocol.component.api.ProcessorDefinition;
+import org.apache.nifi.c2.protocol.component.api.PropertyAllowableValue;
+import org.apache.nifi.c2.protocol.component.api.PropertyDependency;
+import org.apache.nifi.c2.protocol.component.api.PropertyDescriptor;
+import org.apache.nifi.c2.protocol.component.api.PropertyResourceDefinition;
+import org.apache.nifi.c2.protocol.component.api.Relationship;
+import org.apache.nifi.c2.protocol.component.api.ReportingTaskDefinition;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults;
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.logging.LogLevel;
+import org.apache.nifi.registry.extension.component.manifest.AllowableValue;
+import org.apache.nifi.registry.extension.component.manifest.DefaultSchedule;
+import org.apache.nifi.registry.extension.component.manifest.DefaultSettings;
+import org.apache.nifi.registry.extension.component.manifest.Dependency;
+import org.apache.nifi.registry.extension.component.manifest.DependentValues;
+import org.apache.nifi.registry.extension.component.manifest.DeprecationNotice;
+import org.apache.nifi.registry.extension.component.manifest.Extension;
+import org.apache.nifi.registry.extension.component.manifest.ExtensionManifest;
+import org.apache.nifi.registry.extension.component.manifest.Property;
+import 
org.apache.nifi.registry.extension.component.manifest.ProvidedServiceAPI;
+import 
org.apache.nifi.registry.extension.component.manifest.ResourceDefinition;
+import org.apache.nifi.runtime.manifest.ComponentManifestBuilder;
+import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Standard builder for RuntimeManifest.
+ */
+public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
+
+    private static final String DEFAULT_YIELD_PERIOD = "1 sec";
+    private static final String DEFAULT_PENALIZATION_PERIOD = "30 sec";
+    private static final String DEFAULT_BULLETIN_LEVEL = LogLevel.WARN.name();
+
+    private String identifier;
+    private String version;
+    private String runtimeType;
+    private BuildInfo buildInfo;
+    private List<Bundle> bundles = new ArrayList<>();
+    private SchedulingDefaults schedulingDefaults;
+
+    @Override
+    public RuntimeManifestBuilder identifier(final String identifier) {
+        this.identifier = identifier;
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder version(final String version) {
+        this.version = version;
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder runtimeType(final String runtimeType) {
+        this.runtimeType = runtimeType;
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder buildInfo(final BuildInfo buildInfo) {
+        this.buildInfo = buildInfo;
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder addBundle(final ExtensionManifest 
extensionManifest) {
+        Validate.notNull(extensionManifest, "Extension manifest is required");
+        Validate.notBlank(extensionManifest.getGroupId(), "Extension manifest 
groupId is required");
+        Validate.notBlank(extensionManifest.getArtifactId(), "Extension 
manifest artifactId is required");
+        Validate.notBlank(extensionManifest.getVersion(), "Extension manifest 
version is required");
+
+        final Bundle bundle = new Bundle();
+        bundle.setGroup(extensionManifest.getGroupId());
+        bundle.setArtifact(extensionManifest.getArtifactId());
+        bundle.setVersion(extensionManifest.getVersion());
+
+        if (extensionManifest.getExtensions() != null) {
+            final ComponentManifestBuilder componentManifestBuilder = new 
StandardComponentManifestBuilder();
+            extensionManifest.getExtensions().forEach(extension -> 
addExtension(extensionManifest, extension, componentManifestBuilder));
+            bundle.setComponentManifest(componentManifestBuilder.build());
+        }
+        bundles.add(bundle);
+
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder addBundles(final Iterable<ExtensionManifest> 
extensionManifests) {
+        extensionManifests.forEach(em -> addBundle(em));
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder addBundle(Bundle bundle) {
+        if (bundle == null) {
+            throw new IllegalArgumentException("Bundle is required");
+        }
+        bundles.add(bundle);
+        return this;
+    }
+
+    @Override
+    public RuntimeManifestBuilder schedulingDefaults(final SchedulingDefaults 
schedulingDefaults) {
+        this.schedulingDefaults = schedulingDefaults;
+        return this;
+    }
+
+    @Override
+    public RuntimeManifest build() {
+        final RuntimeManifest runtimeManifest = new RuntimeManifest();
+        runtimeManifest.setIdentifier(identifier);
+        runtimeManifest.setVersion(version);
+        runtimeManifest.setAgentType(runtimeType);
+        runtimeManifest.setBuildInfo(buildInfo);
+        runtimeManifest.setBundles(new ArrayList<>(bundles));
+        runtimeManifest.setSchedulingDefaults(schedulingDefaults);
+        return runtimeManifest;
+    }
+
+    private void addExtension(final ExtensionManifest extensionManifest, final 
Extension extension, final ComponentManifestBuilder componentManifestBuilder) {
+        if (extension == null) {
+            throw new IllegalArgumentException("Extension cannot be null");
+        }
+
+        switch(extension.getType()) {
+            case PROCESSOR:
+                addProcessorDefinition(extensionManifest, extension, 
componentManifestBuilder);
+                break;
+            case CONTROLLER_SERVICE:
+                addControllerServiceDefinition(extensionManifest, extension, 
componentManifestBuilder);
+                break;
+            case REPORTING_TASK:
+                addReportingTaskDefinition(extensionManifest, extension, 
componentManifestBuilder);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown extension type: " 
+ extension.getType());
+        }
+    }
+
+    private void addProcessorDefinition(final ExtensionManifest 
extensionManifest, final Extension extension, final ComponentManifestBuilder 
componentManifestBuilder) {
+        final ProcessorDefinition processorDefinition = new 
ProcessorDefinition();
+        populateDefinedType(extensionManifest, extension, processorDefinition);
+        populateExtensionComponent(extensionManifest, extension, 
processorDefinition);
+        populateConfigurableComponent(extension, processorDefinition);
+
+        // processor specific fields
+        
processorDefinition.setInputRequirement(getInputRequirement(extension.getInputRequirement()));
+        
processorDefinition.setSupportedRelationships(getSupportedRelationships(extension.getRelationships()));
+        
processorDefinition.setSupportsDynamicRelationships(extension.getDynamicRelationship()
 != null);
+        
processorDefinition.setTriggerWhenEmpty(extension.getTriggerWhenEmpty());
+        processorDefinition.setTriggerSerially(extension.getTriggerSerially());
+        
processorDefinition.setTriggerWhenAnyDestinationAvailable(extension.getTriggerWhenAnyDestinationAvailable());
+        
processorDefinition.setSupportsBatching(extension.getSupportsBatching());
+        processorDefinition.setSupportsEventDriven(extension.getEventDriven());
+        processorDefinition.setPrimaryNodeOnly(extension.getPrimaryNodeOnly());
+        processorDefinition.setSideEffectFree(extension.getSideEffectFree());
+
+        final DefaultSettings defaultSettings = extension.getDefaultSettings();
+        processorDefinition.setDefaultPenaltyDuration(defaultSettings == null 
? DEFAULT_PENALIZATION_PERIOD : defaultSettings.getPenaltyDuration());
+        processorDefinition.setDefaultYieldDuration(defaultSettings == null ? 
DEFAULT_YIELD_PERIOD : defaultSettings.getYieldDuration());
+        processorDefinition.setDefaultBulletinLevel(defaultSettings == null ? 
DEFAULT_BULLETIN_LEVEL : defaultSettings.getBulletinLevel());
+
+        final List<String> schedulingStrategies = new ArrayList<>();
+        schedulingStrategies.add(SchedulingStrategy.TIMER_DRIVEN.name());
+        schedulingStrategies.add(SchedulingStrategy.CRON_DRIVEN.name());
+        if (extension.getEventDriven()) {
+            schedulingStrategies.add(SchedulingStrategy.EVENT_DRIVEN.name());
+        }
+
+        // If a default schedule is provided then use that, otherwise default 
to TIMER_DRIVEN
+        final DefaultSchedule defaultSchedule = extension.getDefaultSchedule();
+        final String defaultSchedulingStrategy = defaultSchedule == null
+                ? SchedulingStrategy.TIMER_DRIVEN.name() : 
extension.getDefaultSchedule().getStrategy();
+
+        final Map<String, Integer> defaultConcurrentTasks = new 
LinkedHashMap<>(3);
+        defaultConcurrentTasks.put(SchedulingStrategy.TIMER_DRIVEN.name(), 
SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks());
+        defaultConcurrentTasks.put(SchedulingStrategy.CRON_DRIVEN.name(), 
SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks());
+        if (extension.getEventDriven()) {
+            defaultConcurrentTasks.put(SchedulingStrategy.EVENT_DRIVEN.name(), 
SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks());
+        }
+
+        final Map<String, String> defaultSchedulingPeriods = new 
LinkedHashMap<>(2);
+        defaultSchedulingPeriods.put(SchedulingStrategy.TIMER_DRIVEN.name(), 
SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+        defaultSchedulingPeriods.put(SchedulingStrategy.CRON_DRIVEN.name(), 
SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+
+        // If a default schedule is provided then replace the default values 
for the default strategy
+        if (defaultSchedule != null) {
+            defaultSchedulingPeriods.put(defaultSchedule.getStrategy(), 
defaultSchedule.getPeriod());
+            defaultConcurrentTasks.put(defaultSchedule.getStrategy(), 
Integer.valueOf(defaultSchedule.getConcurrentTasks()));
+        }
+
+        
processorDefinition.setSupportedSchedulingStrategies(schedulingStrategies);
+        
processorDefinition.setDefaultSchedulingStrategy(defaultSchedulingStrategy);
+        
processorDefinition.setDefaultConcurrentTasksBySchedulingStrategy(defaultConcurrentTasks);
+        
processorDefinition.setDefaultSchedulingPeriodBySchedulingStrategy(defaultSchedulingPeriods);
+
+        componentManifestBuilder.addProcessor(processorDefinition);
+    }
+
+    private InputRequirement.Requirement getInputRequirement(final 
org.apache.nifi.registry.extension.component.manifest.InputRequirement 
inputRequirement) {
+        if (inputRequirement == null) {
+            return null;
+        }
+
+        switch (inputRequirement) {
+            case INPUT_ALLOWED:
+                return InputRequirement.Requirement.INPUT_ALLOWED;
+            case INPUT_REQUIRED:
+                return InputRequirement.Requirement.INPUT_REQUIRED;
+            case INPUT_FORBIDDEN:
+                return InputRequirement.Requirement.INPUT_FORBIDDEN;
+            default:
+                throw new IllegalArgumentException("Unknown input requirement: 
" + inputRequirement.name());
+        }
+    }
+
+    private List<Relationship> getSupportedRelationships(final 
List<org.apache.nifi.registry.extension.component.manifest.Relationship> 
relationships) {
+        if (relationships == null || relationships.isEmpty()) {
+            return null;
+        }
+
+        final List<Relationship> componentRelationships = new ArrayList<>();
+        for (final 
org.apache.nifi.registry.extension.component.manifest.Relationship relationship 
: relationships) {
+            final Relationship componentRelationship = new Relationship();
+            componentRelationship.setName(relationship.getName());
+            
componentRelationship.setDescription(relationship.getDescription());
+            componentRelationships.add(componentRelationship);
+        }
+        return componentRelationships;
+    }
+
+    private void addControllerServiceDefinition(final ExtensionManifest 
extensionManifest, final Extension extension, final ComponentManifestBuilder 
componentManifestBuilder) {
+        final ControllerServiceDefinition controllerServiceDefinition = new 
ControllerServiceDefinition();
+        populateDefinedType(extensionManifest, extension, 
controllerServiceDefinition);
+        populateExtensionComponent(extensionManifest, extension, 
controllerServiceDefinition);
+        populateConfigurableComponent(extension, controllerServiceDefinition);
+        
componentManifestBuilder.addControllerService(controllerServiceDefinition);
+    }
+
+    private void addReportingTaskDefinition(final ExtensionManifest 
extensionManifest, final Extension extension, final ComponentManifestBuilder 
componentManifestBuilder) {
+        final ReportingTaskDefinition reportingTaskDefinition = new 
ReportingTaskDefinition();
+        populateDefinedType(extensionManifest, extension, 
reportingTaskDefinition);
+        populateDefinedType(extensionManifest, extension, 
reportingTaskDefinition);
+        populateConfigurableComponent(extension, reportingTaskDefinition);
+
+        final List<String> schedulingStrategies = new ArrayList<>();
+        schedulingStrategies.add(SchedulingStrategy.TIMER_DRIVEN.name());
+        schedulingStrategies.add(SchedulingStrategy.CRON_DRIVEN.name());
+
+        // If a default schedule is provided then use that, otherwise default 
to TIMER_DRIVEN
+        final DefaultSchedule defaultSchedule = extension.getDefaultSchedule();
+        final String defaultSchedulingStrategy = defaultSchedule == null
+                ? SchedulingStrategy.TIMER_DRIVEN.name() : 
extension.getDefaultSchedule().getStrategy();
+
+        final Map<String, String> defaultSchedulingPeriods = new 
LinkedHashMap<>(2);
+        defaultSchedulingPeriods.put(SchedulingStrategy.TIMER_DRIVEN.name(), 
SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod());
+        defaultSchedulingPeriods.put(SchedulingStrategy.CRON_DRIVEN.name(), 
SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod());
+
+        // If a default schedule is provided then replace the default values 
for the default strategy
+        if (defaultSchedule != null) {
+            defaultSchedulingPeriods.put(defaultSchedule.getStrategy(), 
defaultSchedule.getPeriod());
+        }
+
+        
reportingTaskDefinition.setSupportedSchedulingStrategies(schedulingStrategies);
+        
reportingTaskDefinition.setDefaultSchedulingStrategy(defaultSchedulingStrategy);
+        
reportingTaskDefinition.setDefaultSchedulingPeriodBySchedulingStrategy(defaultSchedulingPeriods);
+
+        componentManifestBuilder.addReportingTask(reportingTaskDefinition);
+    }
+
+    private void populateDefinedType(final ExtensionManifest 
extensionManifest, final Extension extension, final DefinedType definedType) {
+        definedType.setType(extension.getName());
+        definedType.setTypeDescription(extension.getDescription());
+        definedType.setGroup(extensionManifest.getGroupId());
+        definedType.setArtifact(extensionManifest.getArtifactId());
+        definedType.setVersion(extensionManifest.getVersion());
+    }
+
+    private void populateExtensionComponent(final ExtensionManifest 
extensionManifest, final Extension extension, final ExtensionComponent 
extensionComponent) {
+        final org.apache.nifi.registry.extension.component.manifest.BuildInfo 
buildInfo = extensionManifest.getBuildInfo();
+        if (buildInfo != null) {
+            final BuildInfo componentBuildInfo = new BuildInfo();
+            componentBuildInfo.setRevision(buildInfo.getRevision());
+            extensionComponent.setBuildInfo(componentBuildInfo);
+        }
+
+        final List<String> tags = extension.getTags();
+        if (isNotEmpty(tags)) {
+            extensionComponent.setTags(new HashSet<>(tags));
+        }
+
+        // the extension-manifest.xml will have <deprecationNotice/> for 
non-deprecated components which unmarshalls into
+        // a non-null DeprecationNotice, so we need to check if the reason is 
also non-null before setting the boolean here
+        final DeprecationNotice deprecationNotice = 
extension.getDeprecationNotice();
+        if (deprecationNotice != null && deprecationNotice.getReason() != 
null) {
+            extensionComponent.setDeprecated(true);
+            
extensionComponent.setDeprecationReason(deprecationNotice.getReason());
+        }
+
+        final List<ProvidedServiceAPI> providedServiceApis = 
extension.getProvidedServiceAPIs();
+        if (isNotEmpty(providedServiceApis)) {
+            final List<DefinedType> providedApiTypes = new ArrayList<>();
+            providedServiceApis.forEach(providedServiceApi -> 
providedApiTypes.add(createProvidedApiType(providedServiceApi)));
+            extensionComponent.setProvidedApiImplementations(providedApiTypes);
+        }
+    }
+
+    private DefinedType createProvidedApiType(final ProvidedServiceAPI 
providedServiceApi) {
+        final DefinedType providedApiType = new DefinedType();
+        providedApiType.setType(providedServiceApi.getClassName());
+        providedApiType.setGroup(providedServiceApi.getGroupId());
+        providedApiType.setArtifact(providedServiceApi.getArtifactId());
+        providedApiType.setVersion(providedServiceApi.getVersion());
+        return providedApiType;
+    }
+
+    private void populateConfigurableComponent(final Extension extension, 
final ConfigurableComponentDefinition configurableComponentDefinition) {
+        final List<Property> properties = extension.getProperties();
+        if (isNotEmpty(properties)) {
+            final LinkedHashMap<String, PropertyDescriptor> 
propertyDescriptors = new LinkedHashMap<>();
+            properties.forEach(property -> 
addPropertyDescriptor(propertyDescriptors, property));
+            
configurableComponentDefinition.setPropertyDescriptors(propertyDescriptors);
+        }
+
+        if (isNotEmpty(extension.getDynamicProperties())) {
+            configurableComponentDefinition.setSupportsDynamicProperties(true);
+        }
+    }
+
+    private void addPropertyDescriptor(final Map<String, PropertyDescriptor> 
propertyDescriptors, final Property property) {
+        final PropertyDescriptor propertyDescriptor = 
createPropertyDescriptor(property);
+        propertyDescriptors.put(propertyDescriptor.getName(), 
propertyDescriptor);
+    }
+
+    private PropertyDescriptor createPropertyDescriptor(final Property 
property) {
+        final PropertyDescriptor descriptor = new PropertyDescriptor();
+        descriptor.setName(property.getName());
+        descriptor.setDisplayName(property.getDisplayName());
+        descriptor.setDescription(property.getDescription());
+        descriptor.setDefaultValue(property.getDefaultValue());
+        descriptor.setRequired(property.isRequired());
+        descriptor.setSensitive(property.isSensitive());
+        
descriptor.setExpressionLanguageScope(getELScope(property.getExpressionLanguageScope()));
+        descriptor.setDynamic(property.isDynamic());
+        
descriptor.setAllowableValues(getPropertyAllowableValues(property.getAllowableValues()));
+        
descriptor.setTypeProvidedByValue(getControllerServiceDefinedType(property.getControllerServiceDefinition()));
+        
descriptor.setResourceDefinition(getPropertyResourceDefinition(property.getResourceDefinition()));
+        
descriptor.setDependencies(getPropertyDependencies(property.getDependencies()));
+        return descriptor;
+    }
+
+    private List<PropertyDependency> getPropertyDependencies(final 
List<Dependency> dependencies) {
+        if (dependencies == null || dependencies.isEmpty()) {
+            return null;
+        }
+
+        final List<PropertyDependency> propertyDependencies = new 
ArrayList<>(dependencies.size());
+        for (final Dependency dependency : dependencies) {
+            final PropertyDependency propertyDependency = new 
PropertyDependency();
+            propertyDependency.setPropertyName(dependency.getPropertyName());
+            
propertyDependency.setPropertyDisplayName(dependency.getPropertyDisplayName());
+
+            final List<String> values = new ArrayList<>();
+            final DependentValues dependentValues = 
dependency.getDependentValues();
+            if (dependentValues != null && dependentValues.getValues() != 
null) {
+                values.addAll(dependentValues.getValues());
+            }
+            propertyDependency.setDependentValues(values);
+            propertyDependencies.add(propertyDependency);
+        }
+        return propertyDependencies;
+    }
+
+    private PropertyResourceDefinition getPropertyResourceDefinition(final 
ResourceDefinition resourceDefinition) {
+        if (resourceDefinition == null) {
+            return null;
+        }
+
+        final PropertyResourceDefinition propertyResourceDefinition = new 
PropertyResourceDefinition();
+        switch (resourceDefinition.getCardinality()) {
+            case SINGLE:
+                
propertyResourceDefinition.setCardinality(ResourceCardinality.SINGLE);
+                break;
+            case MULTIPLE:
+                
propertyResourceDefinition.setCardinality(ResourceCardinality.MULTIPLE);
+                break;
+        }
+
+        propertyResourceDefinition.setResourceTypes(
+                resourceDefinition.getResourceTypes().stream()
+                        .map(rt -> getResourceType(rt))
+                        .collect(Collectors.toSet())
+        );
+
+        return propertyResourceDefinition;
+    }
+
+    private ResourceType getResourceType(final 
org.apache.nifi.registry.extension.component.manifest.ResourceType 
resourceType) {
+        switch (resourceType) {
+            case URL:
+                return ResourceType.URL;
+            case FILE:
+                return ResourceType.FILE;
+            case TEXT:
+                return ResourceType.TEXT;
+            case DIRECTORY:
+                return ResourceType.DIRECTORY;
+            default:
+                throw new IllegalArgumentException("Unknown resource type: " + 
resourceType);
+        }
+    }
+
+    private ExpressionLanguageScope getELScope(final 
org.apache.nifi.registry.extension.component.manifest.ExpressionLanguageScope 
elScope) {
+        if (elScope == null) {
+            return null;
+        }
+
+        switch (elScope) {
+            case NONE:
+                return ExpressionLanguageScope.NONE;
+            case FLOWFILE_ATTRIBUTES:
+                return ExpressionLanguageScope.FLOWFILE_ATTRIBUTES;
+            case VARIABLE_REGISTRY:
+                return ExpressionLanguageScope.VARIABLE_REGISTRY;
+            default:
+                throw new IllegalArgumentException("Unknown Expression 
Language Scope: " + elScope.name());
+        }
+    }
+
+    private List<PropertyAllowableValue> getPropertyAllowableValues(final 
List<AllowableValue> allowableValues) {
+        if (allowableValues == null || allowableValues.isEmpty()) {
+            return null;
+        }
+
+        final List<PropertyAllowableValue> propertyAllowableValues = new 
ArrayList<>();
+        for (final AllowableValue allowableValue : allowableValues) {
+            final PropertyAllowableValue propertyAllowableValue = new 
PropertyAllowableValue();
+            propertyAllowableValue.setValue(allowableValue.getValue());
+            
propertyAllowableValue.setDisplayName(allowableValue.getDisplayName());
+            
propertyAllowableValue.setDescription(allowableValue.getDescription());
+            propertyAllowableValues.add(propertyAllowableValue);
+        }
+        return propertyAllowableValues;
+    }
+
+    private DefinedType getControllerServiceDefinedType(
+            final 
org.apache.nifi.registry.extension.component.manifest.ControllerServiceDefinition
 controllerServiceDefinition) {
+        if (controllerServiceDefinition == null) {
+            return null;
+        }
+
+        final DefinedType serviceDefinitionType = new DefinedType();
+        
serviceDefinitionType.setType(controllerServiceDefinition.getClassName());
+        
serviceDefinitionType.setGroup(controllerServiceDefinition.getGroupId());
+        
serviceDefinitionType.setArtifact(controllerServiceDefinition.getArtifactId());
+        
serviceDefinitionType.setVersion(controllerServiceDefinition.getVersion());
+        return serviceDefinitionType;
+    }
+
+    private <T> boolean isNotEmpty(final Collection<T> collection) {
+        return collection != null && !collection.isEmpty();
+    }
+
+}
diff --git a/nifi-manifest/nifi-runtime-manifest-test/pom.xml 
b/nifi-manifest/nifi-runtime-manifest-test/pom.xml
new file mode 100644
index 0000000..7e70bf8
--- /dev/null
+++ b/nifi-manifest/nifi-runtime-manifest-test/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-manifest</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-runtime-manifest-test</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-runtime-manifest</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-runtime-manifest-core</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>extract-runtime-manifest</id>
+                        <goals>
+                            <goal>unpack-dependencies</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            
<includeArtifactIds>nifi-runtime-manifest</includeArtifactIds>
+                            
<outputDirectory>${project.build.directory}/nifi-runtime-manifest</outputDirectory>
+                            <excludeTransitive>true</excludeTransitive>
+                            <silent>false</silent>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git 
a/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java
 
b/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java
new file mode 100644
index 0000000..657d0de
--- /dev/null
+++ 
b/nifi-manifest/nifi-runtime-manifest-test/src/test/java/org/apache/nifi/runtime/manifest/TestRuntimeManifest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.runtime.manifest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.c2.protocol.component.api.BuildInfo;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
+import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ProcessorDefinition;
+import org.apache.nifi.c2.protocol.component.api.PropertyDependency;
+import org.apache.nifi.c2.protocol.component.api.PropertyDescriptor;
+import org.apache.nifi.c2.protocol.component.api.PropertyResourceDefinition;
+import org.apache.nifi.c2.protocol.component.api.Relationship;
+import org.apache.nifi.c2.protocol.component.api.ReportingTaskDefinition;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults;
+import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceType;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class TestRuntimeManifest {
+
+    @Test
+    public void testRuntimeManifest() throws IOException {
+        final ObjectMapper objectMapper = new ObjectMapper();
+
+        final RuntimeManifest runtimeManifest;
+        try (final InputStream inputStream = new 
FileInputStream("target/nifi-runtime-manifest/nifi-runtime-manifest.json")) {
+            runtimeManifest = objectMapper.readValue(inputStream, 
RuntimeManifest.class);
+        }
+        assertNotNull(runtimeManifest);
+        assertEquals("apache-nifi", runtimeManifest.getIdentifier());
+        assertEquals("nifi", runtimeManifest.getAgentType());
+        assertNotNull(runtimeManifest.getVersion());
+
+        final BuildInfo buildInfo = runtimeManifest.getBuildInfo();
+        assertNotNull(buildInfo);
+        assertNotNull(buildInfo.getCompiler());
+        assertNotNull(buildInfo.getRevision());
+        assertNotNull(buildInfo.getTimestamp());
+        assertNotNull(buildInfo.getVersion());
+
+        final SchedulingDefaults schedulingDefaults = 
runtimeManifest.getSchedulingDefaults();
+        assertNotNull(schedulingDefaults);
+        assertEquals(SchedulingStrategy.TIMER_DRIVEN, 
schedulingDefaults.getDefaultSchedulingStrategy());
+
+        final Map<String, Integer> defaultConcurrentTasks = 
schedulingDefaults.getDefaultConcurrentTasksBySchedulingStrategy();
+        assertNotNull(defaultConcurrentTasks);
+        assertEquals(3, defaultConcurrentTasks.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks(), 
defaultConcurrentTasks.get(SchedulingStrategy.TIMER_DRIVEN.name()).intValue());
+        
assertEquals(SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks(), 
defaultConcurrentTasks.get(SchedulingStrategy.EVENT_DRIVEN.name()).intValue());
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks(), 
defaultConcurrentTasks.get(SchedulingStrategy.CRON_DRIVEN.name()).intValue());
+
+        final Map<String, String> defaultSchedulingPeriods = 
schedulingDefaults.getDefaultSchedulingPeriodsBySchedulingStrategy();
+        assertEquals(2, defaultSchedulingPeriods.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod(), 
defaultSchedulingPeriods.get(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod(), 
defaultSchedulingPeriods.get(SchedulingStrategy.CRON_DRIVEN.name()));
+
+        final List<Bundle> bundles = runtimeManifest.getBundles();
+        assertNotNull(bundles);
+        assertTrue(bundles.size() > 0);
+
+        // Verify ListHDFS definition
+        final ProcessorDefinition listHdfsDefinition = 
getProcessorDefinition(bundles, "nifi-hadoop-nar", 
"org.apache.nifi.processors.hadoop.ListHDFS");
+        assertNotNull(listHdfsDefinition);
+        assertTrue(listHdfsDefinition.getPrimaryNodeOnly());
+        assertTrue(listHdfsDefinition.getTriggerSerially());
+        assertTrue(listHdfsDefinition.getTriggerWhenEmpty());
+        assertFalse(listHdfsDefinition.getSupportsBatching());
+        assertFalse(listHdfsDefinition.getSupportsEventDriven());
+        assertFalse(listHdfsDefinition.getSideEffectFree());
+        
assertFalse(listHdfsDefinition.getTriggerWhenAnyDestinationAvailable());
+        assertFalse(listHdfsDefinition.getSupportsDynamicProperties());
+        assertFalse(listHdfsDefinition.getSupportsDynamicRelationships());
+        assertEquals(InputRequirement.Requirement.INPUT_FORBIDDEN, 
listHdfsDefinition.getInputRequirement());
+
+        assertEquals("30 sec", listHdfsDefinition.getDefaultPenaltyDuration());
+        assertEquals("1 sec", listHdfsDefinition.getDefaultYieldDuration());
+        assertEquals("WARN", listHdfsDefinition.getDefaultBulletinLevel());
+
+        assertEquals(SchedulingStrategy.TIMER_DRIVEN.name(), 
listHdfsDefinition.getDefaultSchedulingStrategy());
+
+        final List<String> listHdfsSchedulingStrategies = 
listHdfsDefinition.getSupportedSchedulingStrategies();
+        assertNotNull(listHdfsSchedulingStrategies);
+        assertEquals(2, listHdfsSchedulingStrategies.size());
+        
assertTrue(listHdfsSchedulingStrategies.contains(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertTrue(listHdfsSchedulingStrategies.contains(SchedulingStrategy.CRON_DRIVEN.name()));
+
+        final Map<String, Integer> listHdfsDefaultConcurrentTasks = 
listHdfsDefinition.getDefaultConcurrentTasksBySchedulingStrategy();
+        assertNotNull(listHdfsDefaultConcurrentTasks);
+        assertEquals(2, listHdfsDefaultConcurrentTasks.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks(), 
listHdfsDefaultConcurrentTasks.get(SchedulingStrategy.TIMER_DRIVEN.name()).intValue());
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks(), 
listHdfsDefaultConcurrentTasks.get(SchedulingStrategy.CRON_DRIVEN.name()).intValue());
+
+        final Map<String, String> listHdfsDefaultSchedulingPeriods = 
listHdfsDefinition.getDefaultSchedulingPeriodBySchedulingStrategy();
+        assertNotNull(listHdfsDefaultSchedulingPeriods);
+        assertEquals(2, listHdfsDefaultSchedulingPeriods.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod(), 
listHdfsDefaultSchedulingPeriods.get(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod(), 
listHdfsDefaultSchedulingPeriods.get(SchedulingStrategy.CRON_DRIVEN.name()));
+
+        final List<Relationship> relationships = 
listHdfsDefinition.getSupportedRelationships();
+        assertNotNull(relationships);
+        assertEquals(1, relationships.size());
+        assertEquals("success", relationships.get(0).getName());
+
+        final PropertyDescriptor configResourcesProp = 
getPropertyDescriptor(listHdfsDefinition, "Hadoop Configuration Resources");
+
+        final PropertyResourceDefinition resourceDefinition = 
configResourcesProp.getResourceDefinition();
+        assertNotNull(resourceDefinition);
+        assertEquals(ResourceCardinality.MULTIPLE, 
resourceDefinition.getCardinality());
+        assertNotNull(resourceDefinition.getResourceTypes());
+        assertEquals(1, resourceDefinition.getResourceTypes().size());
+        assertEquals(ResourceType.FILE, 
resourceDefinition.getResourceTypes().stream().findFirst().get());
+
+        // Verify ConsumeKafka_2_6 definition which has properties with 
dependencies
+        final ProcessorDefinition consumeKafkaDefinition = 
getProcessorDefinition(bundles, "nifi-kafka-2-6-nar",
+                "org.apache.nifi.processors.kafka.pubsub.ConsumeKafka_2_6");
+
+        final PropertyDescriptor maxUncommitProp = 
getPropertyDescriptor(consumeKafkaDefinition, "max-uncommit-offset-wait");
+        final List<PropertyDependency> propertyDependencies = 
maxUncommitProp.getDependencies();
+        assertNotNull(propertyDependencies);
+        assertEquals(1, propertyDependencies.size());
+
+        final PropertyDependency propertyMaxUncommitDependency = 
propertyDependencies.get(0);
+        assertEquals("Commit Offsets", 
propertyMaxUncommitDependency.getPropertyName());
+        assertNotNull(propertyMaxUncommitDependency.getDependentValues());
+        assertEquals(1, 
propertyMaxUncommitDependency.getDependentValues().size());
+        assertEquals("true", 
propertyMaxUncommitDependency.getDependentValues().get(0));
+
+        // Verify AmbariReportingTask definition which also has 
@DefaultSchedule
+        final ReportingTaskDefinition ambariReportingTaskDef = 
getReportingTaskDefinition(bundles, "nifi-ambari-nar",
+                "org.apache.nifi.reporting.ambari.AmbariReportingTask");
+
+        assertEquals(SchedulingStrategy.TIMER_DRIVEN.name(), 
ambariReportingTaskDef.getDefaultSchedulingStrategy());
+
+        final List<String> ambariSchedulingStrategies = 
ambariReportingTaskDef.getSupportedSchedulingStrategies();
+        assertNotNull(ambariSchedulingStrategies);
+        assertEquals(2, ambariSchedulingStrategies.size());
+        
assertTrue(ambariSchedulingStrategies.contains(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertTrue(ambariSchedulingStrategies.contains(SchedulingStrategy.CRON_DRIVEN.name()));
+
+        final Map<String, String> ambariDefaultSchedulingPeriods = 
ambariReportingTaskDef.getDefaultSchedulingPeriodBySchedulingStrategy();
+        assertNotNull(ambariDefaultSchedulingPeriods);
+        assertEquals(2, ambariDefaultSchedulingPeriods.size());
+        // TIMER_DRIVEN period should come from the @DefaultSchedule 
annotation that overrides the default value
+        assertEquals("1 min", 
ambariDefaultSchedulingPeriods.get(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod(), 
ambariDefaultSchedulingPeriods.get(SchedulingStrategy.CRON_DRIVEN.name()));
+
+        // Verify JoltTransformRecord which has @EventDriven
+        final ProcessorDefinition joltTransformDef = 
getProcessorDefinition(bundles, "nifi-jolt-record-nar",
+                "org.apache.nifi.processors.jolt.record.JoltTransformRecord");
+
+        assertEquals(SchedulingStrategy.TIMER_DRIVEN.name(), 
joltTransformDef.getDefaultSchedulingStrategy());
+
+        final List<String> joltTransformSchedulingStrategies = 
joltTransformDef.getSupportedSchedulingStrategies();
+        assertNotNull(joltTransformSchedulingStrategies);
+        assertEquals(3, joltTransformSchedulingStrategies.size());
+        
assertTrue(joltTransformSchedulingStrategies.contains(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertTrue(joltTransformSchedulingStrategies.contains(SchedulingStrategy.CRON_DRIVEN.name()));
+        
assertTrue(joltTransformSchedulingStrategies.contains(SchedulingStrategy.EVENT_DRIVEN.name()));
+
+        final Map<String, Integer> joltTransformDefaultConcurrentTasks = 
joltTransformDef.getDefaultConcurrentTasksBySchedulingStrategy();
+        assertNotNull(joltTransformDefaultConcurrentTasks);
+        assertEquals(3, joltTransformDefaultConcurrentTasks.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultConcurrentTasks(), 
joltTransformDefaultConcurrentTasks.get(SchedulingStrategy.TIMER_DRIVEN.name()).intValue());
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultConcurrentTasks(), 
joltTransformDefaultConcurrentTasks.get(SchedulingStrategy.CRON_DRIVEN.name()).intValue());
+        
assertEquals(SchedulingStrategy.EVENT_DRIVEN.getDefaultConcurrentTasks(), 
joltTransformDefaultConcurrentTasks.get(SchedulingStrategy.EVENT_DRIVEN.name()).intValue());
+
+        final Map<String, String> joltTransformDefaultSchedulingPeriods = 
listHdfsDefinition.getDefaultSchedulingPeriodBySchedulingStrategy();
+        assertNotNull(joltTransformDefaultSchedulingPeriods);
+        assertEquals(2, joltTransformDefaultSchedulingPeriods.size());
+        
assertEquals(SchedulingStrategy.TIMER_DRIVEN.getDefaultSchedulingPeriod(), 
joltTransformDefaultSchedulingPeriods.get(SchedulingStrategy.TIMER_DRIVEN.name()));
+        
assertEquals(SchedulingStrategy.CRON_DRIVEN.getDefaultSchedulingPeriod(), 
joltTransformDefaultSchedulingPeriods.get(SchedulingStrategy.CRON_DRIVEN.name()));
+    }
+
+    private PropertyDescriptor getPropertyDescriptor(final ProcessorDefinition 
processorDefinition, final String propName) {
+        final Map<String, PropertyDescriptor> propertyDescriptors = 
processorDefinition.getPropertyDescriptors();
+        assertNotNull(propertyDescriptors);
+
+        final PropertyDescriptor propertyDescriptor = 
propertyDescriptors.values().stream()
+                .filter(p -> p.getName().equals(propName))
+                .findFirst()
+                .orElse(null);
+        assertNotNull(propertyDescriptor);
+        return propertyDescriptor;
+    }
+
+    private ProcessorDefinition getProcessorDefinition(final List<Bundle> 
bundles, final String artifactId, final String type) {
+        final ComponentManifest componentManifest = 
getComponentManifest(bundles, artifactId);
+
+        final List<ProcessorDefinition> processors = 
componentManifest.getProcessors();
+        assertNotNull(processors);
+
+        final ProcessorDefinition processorDefinition = processors.stream()
+                .filter(p -> p.getType().equals(type))
+                .findFirst()
+                .orElse(null);
+        assertNotNull(processorDefinition);
+        return processorDefinition;
+    }
+
+    private ReportingTaskDefinition getReportingTaskDefinition(final 
List<Bundle> bundles, final String artifactId, final String type) {
+        final ComponentManifest componentManifest = 
getComponentManifest(bundles, artifactId);
+
+        final List<ReportingTaskDefinition> reportingTasks = 
componentManifest.getReportingTasks();
+        assertNotNull(reportingTasks);
+
+        final ReportingTaskDefinition reportingTaskDefinition = 
reportingTasks.stream()
+                .filter(p -> p.getType().equals(type))
+                .findFirst()
+                .orElse(null);
+        assertNotNull(reportingTaskDefinition);
+        return reportingTaskDefinition;
+    }
+
+    private ComponentManifest getComponentManifest(final List<Bundle> bundles, 
final String artifactId) {
+        final Bundle bundle = bundles.stream().filter(b -> 
b.getArtifact().equals(artifactId)).findFirst().orElse(null);
+        assertNotNull(bundle);
+
+        final ComponentManifest componentManifest = 
bundle.getComponentManifest();
+        assertNotNull(componentManifest);
+        return componentManifest;
+    }
+}
diff --git a/nifi-manifest/nifi-runtime-manifest/pom.xml 
b/nifi-manifest/nifi-runtime-manifest/pom.xml
new file mode 100644
index 0000000..4ef197b
--- /dev/null
+++ b/nifi-manifest/nifi-runtime-manifest/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi-manifest</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-runtime-manifest</artifactId>
+    <packaging>jar</packaging>
+
+    <properties>
+        
<extension.manifest.unpack.dir>${project.build.directory}</extension.manifest.unpack.dir>
+        
<build.properties.file>${project.build.directory}/classes/build.properties</build.properties.file>
+        
<runtime.manifest.file>${project.build.directory}/classes/nifi-runtime-manifest.json</runtime.manifest.file>
+        <runtime.manifest.id>apache-nifi</runtime.manifest.id>
+    </properties>
+
+    <dependencies>
+        <!-- Needed to unpack the extension-manifest.xml files during the 
build, marked as optional
+             because it is not a real dependency for anyone depending on this 
module -->
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-assembly</artifactId>
+            <version>1.16.0-SNAPSHOT</version>
+            <classifier>manifests</classifier>
+            <type>zip</type>
+            <optional>true</optional>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <!-- Replace variables in src/main/resource/build.properties when 
processing resources -->
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>build.properties</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <!-- Populate buildBranch, buildRevision, and timestamp so variables 
are available to build.properties -->
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>buildnumber-maven-plugin</artifactId>
+                <inherited>true</inherited>
+                <executions>
+                    <execution>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>create</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <doCheck>false</doCheck>
+                    <doUpdate>false</doUpdate>
+                    <shortRevisionLength>7</shortRevisionLength>
+                    <getRevisionOnlyOnce>true</getRevisionOnlyOnce>
+                    <revisionOnScmFailure />
+                    
<buildNumberPropertyName>buildRevision</buildNumberPropertyName>
+                    <scmBranchPropertyName>buildBranch</scmBranchPropertyName>
+                </configuration>
+            </plugin>
+            <!-- Unpack all of NiFi's extension manifests -->
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>extract-extension-manifests</id>
+                        <goals>
+                            <goal>unpack-dependencies</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                        <configuration>
+                            
<includeArtifactIds>nifi-assembly</includeArtifactIds>
+                            <includeClassifiers>manifests</includeClassifiers>
+                            <includes>**/extension-manifest.xml</includes>
+                            <excludeTransitive>true</excludeTransitive>
+                            
<outputDirectory>${extension.manifest.unpack.dir}</outputDirectory>
+                            <silent>true</silent>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Execute the runtime manifest generator -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.6.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-runtime-manifest</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                        <configuration>
+                            
<mainClass>org.apache.nifi.runtime.manifest.impl.RuntimeManifestGenerator</mainClass>
+                            <arguments>
+                                
<argument>${extension.manifest.unpack.dir}/nifi-manifests</argument>
+                                <argument>${build.properties.file}</argument>
+                                <argument>${runtime.manifest.file}</argument>
+                                <argument>${runtime.manifest.id}</argument>
+                            </arguments>
+                            
<includePluginDependencies>true</includePluginDependencies>
+                        </configuration>
+                    </execution>
+                </executions>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.nifi</groupId>
+                        <artifactId>nifi-runtime-manifest-core</artifactId>
+                        <version>1.16.0-SNAPSHOT</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <!-- Specifies an empty buildRevision and buildBranch when building 
outside of a git repo -->
+        <profile>
+            <id>build-info-no-git</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+                <file>
+                    <missing>../../.git/HEAD</missing>
+                </file>
+            </activation>
+            <properties>
+                <buildRevision />
+                <buildBranch />
+            </properties>
+        </profile>
+    </profiles>
+
+</project>
diff --git 
a/nifi-manifest/nifi-runtime-manifest/src/main/resources/build.properties 
b/nifi-manifest/nifi-runtime-manifest/src/main/resources/build.properties
new file mode 100644
index 0000000..aaf21ca
--- /dev/null
+++ b/nifi-manifest/nifi-runtime-manifest/src/main/resources/build.properties
@@ -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.
+
+Project-Version:${project.version}
+Build-Branch:${buildBranch}
+Build-Revision:${buildRevision}
+Build-Timestamp:${timestamp}
+Built-By:${user.name}
+Maven-Home:${maven.home}
+Maven-Version:${maven.version}
+Created-By:${maven.build.version}
+Build-Java-Home:${java.home}
+Build-Jdk:${java.version}
+Build-Jdk-Vendor:${java.vendor}
+Build-Arch:${os.arch}
+Build-Os:${os.name}
+Build-Os-Version:${os.version}
\ No newline at end of file
diff --git a/nifi-manifest/pom.xml b/nifi-manifest/pom.xml
new file mode 100644
index 0000000..4ee8692
--- /dev/null
+++ b/nifi-manifest/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>nifi</artifactId>
+        <version>1.16.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>nifi-manifest</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>nifi-runtime-manifest-core</module>
+        <module>nifi-runtime-manifest</module>
+        <module>nifi-runtime-manifest-test</module>
+    </modules>
+
+</project>
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/docs/TestJacksonExtensionManifestParser.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/docs/TestJacksonExtensionManifestParser.java
index 29e9648..6d0fbbc 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/docs/TestJacksonExtensionManifestParser.java
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/java/org/apache/nifi/registry/bundle/extract/nar/docs/TestJacksonExtensionManifestParser.java
@@ -17,6 +17,8 @@
 package org.apache.nifi.registry.bundle.extract.nar.docs;
 
 import org.apache.nifi.registry.extension.component.manifest.Cardinality;
+import org.apache.nifi.registry.extension.component.manifest.DefaultSchedule;
+import org.apache.nifi.registry.extension.component.manifest.DefaultSettings;
 import org.apache.nifi.registry.extension.component.manifest.Dependency;
 import org.apache.nifi.registry.extension.component.manifest.DependentValues;
 import org.apache.nifi.registry.extension.component.manifest.Extension;
@@ -36,6 +38,7 @@ import java.io.InputStream;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -151,6 +154,46 @@ public class TestJacksonExtensionManifestParser {
         final List<Extension> extensionDetails = 
extensionManifest.getExtensions();
         assertEquals(4, extensionDetails.size());
 
+        final Extension processor1 = extensionDetails.stream()
+                .filter(extension -> 
extension.getName().equals("org.apache.nifi.processors.TestProcessor1"))
+                .findFirst()
+                .orElse(null);
+        assertNotNull(processor1);
+        assertTrue(processor1.getTriggerSerially());
+        assertTrue(processor1.getTriggerWhenEmpty());
+        assertTrue(processor1.getTriggerWhenAnyDestinationAvailable());
+        assertTrue(processor1.getPrimaryNodeOnly());
+        assertTrue(processor1.getEventDriven());
+        assertTrue(processor1.getSupportsBatching());
+        assertTrue(processor1.getSideEffectFree());
+
+        final DefaultSettings defaultSettingsProc1 = 
processor1.getDefaultSettings();
+        assertNotNull(defaultSettingsProc1);
+        assertEquals("10 secs", defaultSettingsProc1.getYieldDuration());
+        assertEquals("20 secs", defaultSettingsProc1.getPenaltyDuration());
+        assertEquals("DEBUG", defaultSettingsProc1.getBulletinLevel());
+
+        final DefaultSchedule defaultScheduleProc1 = 
processor1.getDefaultSchedule();
+        assertNotNull(defaultScheduleProc1);
+        assertEquals("CRON_DRIVEN", defaultScheduleProc1.getStrategy());
+        assertEquals("* 1 * * *", defaultScheduleProc1.getPeriod());
+        assertEquals("5", defaultScheduleProc1.getConcurrentTasks());
+
+        final Extension processor2 = extensionDetails.stream()
+                .filter(extension -> 
extension.getName().equals("org.apache.nifi.processors.TestProcessor2"))
+                .findFirst()
+                .orElse(null);
+        assertNotNull(processor2);
+        assertFalse(processor2.getTriggerSerially());
+        assertFalse(processor2.getTriggerWhenEmpty());
+        assertFalse(processor2.getTriggerWhenAnyDestinationAvailable());
+        assertFalse(processor2.getPrimaryNodeOnly());
+        assertFalse(processor2.getEventDriven());
+        assertFalse(processor2.getSupportsBatching());
+        assertFalse(processor2.getSideEffectFree());
+
+        assertNull(processor2.getDefaultSchedule());
+        assertNull(processor2.getDefaultSettings());
     }
 
     @Test
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/resources/descriptors/extension-manifest-test-components.xml
 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/resources/descriptors/extension-manifest-test-components.xml
index 7783e1d..01de496 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/resources/descriptors/extension-manifest-test-components.xml
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-bundle-utils/src/test/resources/descriptors/extension-manifest-test-components.xml
@@ -9,6 +9,23 @@
                 <tag>test</tag>
                 <tag>processor</tag>
             </tags>
+            <triggerSerially>true</triggerSerially>
+            <triggerWhenEmpty>true</triggerWhenEmpty>
+            
<triggerWhenAnyDestinationAvailable>true</triggerWhenAnyDestinationAvailable>
+            <primaryNodeOnly>true</primaryNodeOnly>
+            <supportsBatching>true</supportsBatching>
+            <eventDriven>true</eventDriven>
+            <sideEffectFree>true</sideEffectFree>
+            <defaultSettings>
+                <yieldDuration>10 secs</yieldDuration>
+                <penaltyDuration>20 secs</penaltyDuration>
+                <bulletinLevel>DEBUG</bulletinLevel>
+            </defaultSettings>
+            <defaultSchedule>
+                <strategy>CRON_DRIVEN</strategy>
+                <period>* 1 * * *</period>
+                <concurrentTasks>5</concurrentTasks>
+            </defaultSchedule>
         </extension>
         <extension>
             <name>org.apache.nifi.processors.TestProcessor2</name>
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSchedule.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSchedule.java
new file mode 100644
index 0000000..77e7417
--- /dev/null
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSchedule.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.registry.extension.component.manifest;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+@ApiModel
+@XmlAccessorType(XmlAccessType.FIELD)
+public class DefaultSchedule {
+
+    private String strategy;
+    private String period;
+    private String concurrentTasks;
+
+    @ApiModelProperty("The default scheduling strategy")
+    public String getStrategy() {
+        return strategy;
+    }
+
+    public void setStrategy(String strategy) {
+        this.strategy = strategy;
+    }
+
+    @ApiModelProperty("The default scheduling period")
+    public String getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(String period) {
+        this.period = period;
+    }
+
+    @ApiModelProperty("The default concurrent tasks")
+    public String getConcurrentTasks() {
+        return concurrentTasks;
+    }
+
+    public void setConcurrentTasks(String concurrentTasks) {
+        this.concurrentTasks = concurrentTasks;
+    }
+
+}
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSettings.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSettings.java
new file mode 100644
index 0000000..fcfb623
--- /dev/null
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/DefaultSettings.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.registry.extension.component.manifest;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+@ApiModel
+@XmlAccessorType(XmlAccessType.FIELD)
+public class DefaultSettings {
+
+    private String yieldDuration;
+    private String penaltyDuration;
+    private String bulletinLevel;
+
+    @ApiModelProperty("The default yield duration")
+    public String getYieldDuration() {
+        return yieldDuration;
+    }
+
+    public void setYieldDuration(String yieldDuration) {
+        this.yieldDuration = yieldDuration;
+    }
+
+    @ApiModelProperty("The default penalty duration")
+    public String getPenaltyDuration() {
+        return penaltyDuration;
+    }
+
+    public void setPenaltyDuration(String penaltyDuration) {
+        this.penaltyDuration = penaltyDuration;
+    }
+
+    @ApiModelProperty("The default bulletin level")
+    public String getBulletinLevel() {
+        return bulletinLevel;
+    }
+
+    public void setBulletinLevel(String bulletinLevel) {
+        this.bulletinLevel = bulletinLevel;
+    }
+
+}
diff --git 
a/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/Extension.java
 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/Extension.java
index 143507f..5746ebb 100644
--- 
a/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/Extension.java
+++ 
b/nifi-registry/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/extension/component/manifest/Extension.java
@@ -89,6 +89,16 @@ public class Extension {
     @XmlElement(name = "providedServiceAPI")
     private List<ProvidedServiceAPI> providedServiceAPIs;
 
+    private DefaultSettings defaultSettings;
+    private DefaultSchedule defaultSchedule;
+
+    private boolean triggerSerially;
+    private boolean triggerWhenEmpty;
+    private boolean triggerWhenAnyDestinationAvailable;
+    private boolean supportsBatching;
+    private boolean eventDriven;
+    private boolean primaryNodeOnly;
+    private boolean sideEffectFree;
 
     @ApiModelProperty(value = "The name of the extension")
     public String getName() {
@@ -243,6 +253,87 @@ public class Extension {
         this.providedServiceAPIs = providedServiceAPIs;
     }
 
+    @ApiModelProperty(value = "The default settings for a processor")
+    public DefaultSettings getDefaultSettings() {
+        return defaultSettings;
+    }
+
+    public void setDefaultSettings(DefaultSettings defaultSettings) {
+        this.defaultSettings = defaultSettings;
+    }
+
+    @ApiModelProperty(value = "The default schedule for a processor reporting 
task")
+    public DefaultSchedule getDefaultSchedule() {
+        return defaultSchedule;
+    }
+
+    public void setDefaultSchedule(DefaultSchedule defaultSchedule) {
+        this.defaultSchedule = defaultSchedule;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor should be triggered 
serially")
+    public boolean getTriggerSerially() {
+        return triggerSerially;
+    }
+
+    public void setTriggerSerially(boolean triggerSerially) {
+        this.triggerSerially = triggerSerially;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor should be triggered 
when the incoming queues are empty")
+    public boolean getTriggerWhenEmpty() {
+        return triggerWhenEmpty;
+    }
+
+    public void setTriggerWhenEmpty(boolean triggerWhenEmpty) {
+        this.triggerWhenEmpty = triggerWhenEmpty;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor should be triggered 
when any destinations have space for flow files")
+    public boolean getTriggerWhenAnyDestinationAvailable() {
+        return triggerWhenAnyDestinationAvailable;
+    }
+
+    public void setTriggerWhenAnyDestinationAvailable(boolean 
triggerWhenAnyDestinationAvailable) {
+        this.triggerWhenAnyDestinationAvailable = 
triggerWhenAnyDestinationAvailable;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor supports batching")
+    public boolean getSupportsBatching() {
+        return supportsBatching;
+    }
+
+    public void setSupportsBatching(boolean supportsBatching) {
+        this.supportsBatching = supportsBatching;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor supports event 
driven scheduling")
+    public boolean getEventDriven() {
+        return eventDriven;
+    }
+
+    public void setEventDriven(boolean eventDriven) {
+        this.eventDriven = eventDriven;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor should be scheduled 
only on the primary node")
+    public boolean getPrimaryNodeOnly() {
+        return primaryNodeOnly;
+    }
+
+    public void setPrimaryNodeOnly(boolean primaryNodeOnly) {
+        this.primaryNodeOnly = primaryNodeOnly;
+    }
+
+    @ApiModelProperty(value = "Indicates that a processor is side effect free")
+    public boolean getSideEffectFree() {
+        return sideEffectFree;
+    }
+
+    public void setSideEffectFree(boolean sideEffectFree) {
+        this.sideEffectFree = sideEffectFree;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/pom.xml b/pom.xml
index c3048c5..1f45810 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@
         <module>nifi-stateless</module>
         <module>nifi-registry</module>
         <module>nifi-toolkit</module>
+        <module>nifi-manifest</module>
         <module>c2</module>
     </modules>
     <url>https://nifi.apache.org</url>

Reply via email to