MINIFI-104 - Making connection ids filesystem friendly, unique

 - Introducing configuration versioning, adding id to connection

MINIFI-108 - Using ids for processors, source, destination

 - Making ids optional

This closes #33

Signed-off-by: Joseph Percivall <joeperciv...@yahoo.com>


Project: http://git-wip-us.apache.org/repos/asf/nifi-minifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi-minifi/commit/93af87dd
Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi/tree/93af87dd
Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi/diff/93af87dd

Branch: refs/heads/master
Commit: 93af87dd841c7886997e2b7d7c9f62334c366e74
Parents: 8431750
Author: Bryan Rosander <bryanrosan...@gmail.com>
Authored: Wed Sep 7 17:36:32 2016 -0400
Committer: Joseph Percivall <joeperciv...@yahoo.com>
Committed: Tue Sep 20 17:32:43 2016 -0400

----------------------------------------------------------------------
 .../bootstrap/util/ConfigTransformer.java       |  10 +-
 .../bootstrap/util/TestConfigTransformer.java   |   9 +-
 .../src/test/resources/config-minimal.yml       |   2 +-
 .../src/test/resources/config-multiple-RPGs.yml |   4 +-
 .../resources/config-multiple-input-ports.yml   |   4 +-
 .../minifi/commons/schema/ConfigSchema.java     | 204 ++++++++++++++++---
 .../minifi/commons/schema/ConnectionSchema.java |  84 +++++---
 .../minifi/commons/schema/ProcessorSchema.java  |  22 +-
 .../commons/schema/RemoteInputPortSchema.java   |  24 +--
 .../schema/RemoteProcessingGroupSchema.java     |   4 +-
 .../schema/SecurityPropertiesSchema.java        |   6 +-
 .../commons/schema/common/BaseSchema.java       |  32 ++-
 .../schema/common/BaseSchemaWithIdAndName.java  |  85 ++++++++
 .../schema/serialization/SchemaLoader.java      |  28 ++-
 .../minifi/commons/schema/ConfigSchemaTest.java | 101 +++++++--
 .../schema/serialization/SchemaLoaderTest.java  |  72 +++++++
 .../src/test/resources/config-minimal.yml       |  36 ++++
 .../src/main/markdown/System_Admin_Guide.md     |  11 +-
 .../dto/ConnectionSchemaFunction.java           |   8 +-
 .../dto/ProcessorSchemaFunction.java            |   2 +
 .../configuration/dto/BaseSchemaTester.java     |   4 +-
 .../configuration/dto/ConnectionSchemaTest.java |  53 +++--
 .../configuration/dto/ProcessorSchemaTest.java  |  32 ++-
 .../dto/RemoteInputPortSchemaTest.java          |   2 +-
 .../src/test/resources/CsvToJson.yml            |  26 ++-
 .../resources/DecompressionCircularFlow.yml     |  80 +++++---
 .../resources/InvokeHttpMiNiFiTemplateTest.yml  |  66 +++---
 ...aceTextExpressionLanguageCSVReformatting.yml |  20 +-
 .../src/test/resources/StressTestFramework.yml  |  14 +-
 29 files changed, 799 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-bootstrap/src/main/java/org/apache/nifi/minifi/bootstrap/util/ConfigTransformer.java
----------------------------------------------------------------------
diff --git 
a/minifi-bootstrap/src/main/java/org/apache/nifi/minifi/bootstrap/util/ConfigTransformer.java
 
b/minifi-bootstrap/src/main/java/org/apache/nifi/minifi/bootstrap/util/ConfigTransformer.java
index f799782..00e5ab8 100644
--- 
a/minifi-bootstrap/src/main/java/org/apache/nifi/minifi/bootstrap/util/ConfigTransformer.java
+++ 
b/minifi-bootstrap/src/main/java/org/apache/nifi/minifi/bootstrap/util/ConfigTransformer.java
@@ -373,7 +373,8 @@ public final class ConfigTransformer {
             final Document doc = parentElement.getOwnerDocument();
             final Element element = doc.createElement("processor");
             parentElement.appendChild(element);
-            addTextElement(element, "id", processorConfig.getName());
+
+            addTextElement(element, "id", processorConfig.getId());
             addTextElement(element, "name", processorConfig.getName());
 
             addPosition(element);
@@ -508,7 +509,8 @@ public final class ConfigTransformer {
             final Document doc = parentElement.getOwnerDocument();
             final Element element = doc.createElement("connection");
             parentElement.appendChild(element);
-            addTextElement(element, "id", connectionProperties.getName());
+
+            addTextElement(element, "id", connectionProperties.getId());
             addTextElement(element, "name", connectionProperties.getName());
 
             final Element bendPointsElement = doc.createElement("bendPoints");
@@ -517,11 +519,11 @@ public final class ConfigTransformer {
             addTextElement(element, "labelIndex", "1");
             addTextElement(element, "zIndex", "0");
 
-            addTextElement(element, "sourceId", 
connectionProperties.getSourceName());
+            addTextElement(element, "sourceId", 
connectionProperties.getSourceId());
             addTextElement(element, "sourceGroupId", "Root-Group");
             addTextElement(element, "sourceType", "PROCESSOR");
 
-            final String connectionDestinationId = 
connectionProperties.getDestinationName();
+            final String connectionDestinationId = 
connectionProperties.getDestinationId();
             addTextElement(element, "destinationId", connectionDestinationId);
             final Optional<String> parentGroup = 
findInputPortParentGroup(connectionDestinationId, configSchema);
             if (parentGroup.isPresent()) {

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/util/TestConfigTransformer.java
----------------------------------------------------------------------
diff --git 
a/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/util/TestConfigTransformer.java
 
b/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/util/TestConfigTransformer.java
index 51072a5..8170d54 100644
--- 
a/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/util/TestConfigTransformer.java
+++ 
b/minifi-bootstrap/src/test/java/org/apache/nifi/minifi/bootstrap/util/TestConfigTransformer.java
@@ -18,6 +18,9 @@
 package org.apache.nifi.minifi.bootstrap.util;
 
 import 
org.apache.nifi.minifi.bootstrap.exception.InvalidConfigurationException;
+import org.apache.nifi.minifi.commons.schema.ConfigSchema;
+import org.apache.nifi.minifi.commons.schema.ConnectionSchema;
+import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
 import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
 import org.junit.Test;
 
@@ -196,8 +199,10 @@ public class TestConfigTransformer {
             
ConfigTransformer.transformConfigFile("./src/test/resources/config-multiple-problems.yml",
 "./target/");
             fail("Invalid configuration file was not detected.");
         } catch (InvalidConfigurationException e){
-            assertEquals("Failed to transform config file due to:['scheduling 
strategy' in section 'Provenance Reporting' because it is not a valid 
scheduling strategy], ['class' in section " +
-                    "'Processors' because it was not found and it is 
required], ['source name' in section 'Connections' because it was not found and 
it is required]", e.getMessage());
+            assertEquals("Failed to transform config file due to:[" + 
ConfigSchema.CONNECTIONS_REFER_TO_PROCESSOR_NAMES_THAT_DONT_EXIST
+                    + "null], ['scheduling strategy' in section 'Provenance 
Reporting' because it is not a valid scheduling strategy], ['class' in section "
+                    + "'Processors' because it was not found and it is 
required], ['source name' in section 'Connections' because it was not found and 
it is required], ["
+                    + BaseSchema.getIssueText(ConnectionSchema.SOURCE_ID_KEY, 
"Connections", BaseSchema.IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED) + "]", 
e.getMessage());
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-bootstrap/src/test/resources/config-minimal.yml
----------------------------------------------------------------------
diff --git a/minifi-bootstrap/src/test/resources/config-minimal.yml 
b/minifi-bootstrap/src/test/resources/config-minimal.yml
index 4a5f885..e50a87c 100644
--- a/minifi-bootstrap/src/test/resources/config-minimal.yml
+++ b/minifi-bootstrap/src/test/resources/config-minimal.yml
@@ -30,6 +30,6 @@ Processors:
 
 Connections:
     - name: TailToSplit
-      source name: RouteErrors
+      source name: TailAppLog
       source relationship name: success
       destination name: PutFile

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-bootstrap/src/test/resources/config-multiple-RPGs.yml
----------------------------------------------------------------------
diff --git a/minifi-bootstrap/src/test/resources/config-multiple-RPGs.yml 
b/minifi-bootstrap/src/test/resources/config-multiple-RPGs.yml
index 48ce749..a779a42 100644
--- a/minifi-bootstrap/src/test/resources/config-multiple-RPGs.yml
+++ b/minifi-bootstrap/src/test/resources/config-multiple-RPGs.yml
@@ -75,7 +75,7 @@ Processors:
 
 Connections:
     - name: TailToS2S
-      source name: TailFile
+      source name: TailAppLog
       source relationship name: success
       destination name: dgs809qe-9qw7-q985-jl1u-y789ohi87g78
       max work queue size: 0
@@ -83,7 +83,7 @@ Connections:
       flowfile expiration: 60 sec
       queue prioritizer class: 
org.apache.nifi.prioritizer.NewestFlowFileFirstPrioritizer
     - name: TailToS2S-2
-      source name: TailFile
+      source name: TailAppLog
       source relationship name: success
       destination name: 8644cbcc-a45c-40e0-964d-5e536e2ada61
       max work queue size: 0

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-bootstrap/src/test/resources/config-multiple-input-ports.yml
----------------------------------------------------------------------
diff --git 
a/minifi-bootstrap/src/test/resources/config-multiple-input-ports.yml 
b/minifi-bootstrap/src/test/resources/config-multiple-input-ports.yml
index 9498eae..9654edf 100644
--- a/minifi-bootstrap/src/test/resources/config-multiple-input-ports.yml
+++ b/minifi-bootstrap/src/test/resources/config-multiple-input-ports.yml
@@ -75,7 +75,7 @@ Processors:
 
 Connections:
     - name: TailToS2S
-      source name: TailFile
+      source name: TailAppLog
       source relationship name: success
       destination name: dgs809qe-9qw7-q985-jl1u-y789ohi87g78
       max work queue size: 0
@@ -83,7 +83,7 @@ Connections:
       flowfile expiration: 60 sec
       queue prioritizer class: 
org.apache.nifi.prioritizer.NewestFlowFileFirstPrioritizer
     - name: TailToS2S-2
-      source name: TailFile
+      source name: TailAppLog
       source relationship name: success
       destination name: 8644cbcc-a45c-40e0-964d-5e536e2ada61
       max work queue size: 0

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConfigSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConfigSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConfigSchema.java
index 4b4757a..11119b4 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConfigSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConfigSchema.java
@@ -18,12 +18,17 @@
 package org.apache.nifi.minifi.commons.schema;
 
 import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
+import org.apache.nifi.minifi.commons.schema.common.StringUtil;
 
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.COMPONENT_STATUS_REPO_KEY;
@@ -42,10 +47,18 @@ import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.SE
  *
  */
 public class ConfigSchema extends BaseSchema {
-    public static final String FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_NAMES = 
"Found the following duplicate processor names: ";
-    public static final String FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_NAMES 
= "Found the following duplicate connection names: ";
+    public static final String FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_IDS = 
"Found the following duplicate processor ids: ";
+    public static final String FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_IDS = 
"Found the following duplicate connection ids: ";
     public static final String 
FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_PROCESSING_GROUP_NAMES = "Found the 
following duplicate remote processing group names: ";
+    public static final String 
FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_INPUT_PORT_IDS = "Found the following 
duplicate remote input port ids: ";
+    public static final String FOUND_THE_FOLLOWING_DUPLICATE_IDS = "Found the 
following ids that occur both in Processors and Remote Input Ports: ";
+    public static final String 
CANNOT_LOOK_UP_PROCESSOR_ID_FROM_PROCESSOR_NAME_DUE_TO_DUPLICATE_PROCESSOR_NAMES
 = "Cannot look up Processor id from Processor name due to duplicate Processor 
names: ";
+    public static final int CONFIG_VERSION = 1;
+    public static final String 
CONNECTIONS_REFER_TO_PROCESSOR_NAMES_THAT_DONT_EXIST = "Connection(s) refer to 
Processor names that don't exist: ";
     public static String TOP_LEVEL_NAME = "top level";
+    public static final String VERSION = "MiNiFi Config Version";
+    public static final String EMPTY_NAME = "empty_name";
+    public static final Pattern ID_REPLACE_PATTERN = 
Pattern.compile("[^A-Za-z0-9_-]");
 
     private FlowControllerSchema flowControllerProperties;
     private CorePropertiesSchema coreProperties;
@@ -70,20 +83,12 @@ public class ConfigSchema extends BaseSchema {
         componentStatusRepositoryProperties = getMapAsType(map, 
COMPONENT_STATUS_REPO_KEY, ComponentStatusRepositorySchema.class, 
TOP_LEVEL_NAME, false);
         securityProperties = getMapAsType(map, SECURITY_PROPS_KEY, 
SecurityPropertiesSchema.class, TOP_LEVEL_NAME, false);
 
-        processors = getOptionalKeyAsType(map, PROCESSORS_KEY, List.class, 
TOP_LEVEL_NAME, null);
-        if (processors != null) {
-            transformListToType(processors, "processor", 
ProcessorSchema.class, PROCESSORS_KEY);
-        }
+        processors = getProcessorSchemas(getOptionalKeyAsType(map, 
PROCESSORS_KEY, List.class, TOP_LEVEL_NAME, null));
 
-        connections = getOptionalKeyAsType(map, CONNECTIONS_KEY, List.class, 
TOP_LEVEL_NAME, null);
-        if (connections != null) {
-            transformListToType(connections, "connection", 
ConnectionSchema.class, CONNECTIONS_KEY);
-        }
+        remoteProcessingGroups = convertListToType(getOptionalKeyAsType(map, 
REMOTE_PROCESSING_GROUPS_KEY, List.class, TOP_LEVEL_NAME, null), "remote 
processing group",
+                RemoteProcessingGroupSchema.class, 
REMOTE_PROCESSING_GROUPS_KEY);
 
-        remoteProcessingGroups = getOptionalKeyAsType(map, 
REMOTE_PROCESSING_GROUPS_KEY, List.class, TOP_LEVEL_NAME, null);
-        if (remoteProcessingGroups != null) {
-            transformListToType(remoteProcessingGroups, "remote processing 
group", RemoteProcessingGroupSchema.class, REMOTE_PROCESSING_GROUPS_KEY);
-        }
+        connections = getConnectionSchemas(getOptionalKeyAsType(map, 
CONNECTIONS_KEY, List.class, TOP_LEVEL_NAME, null));
 
         provenanceReportingProperties = getMapAsType(map, 
PROVENANCE_REPORTING_KEY, ProvenanceReportingSchema.class, TOP_LEVEL_NAME, 
false, false);
 
@@ -96,51 +101,168 @@ public class ConfigSchema extends BaseSchema {
         addIssuesIfNotNull(provenanceReportingProperties);
         addIssuesIfNotNull(provenanceRepositorySchema);
 
+        Set<String> processorIds = new HashSet<>();
         if (processors != null) {
-            
checkForDuplicateNames(FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_NAMES, 
processors.stream().map(ProcessorSchema::getName).collect(Collectors.toList()));
+            List<String> processorIdList = 
processors.stream().map(ProcessorSchema::getId).collect(Collectors.toList());
+            checkForDuplicates(this::addValidationIssue, 
FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_IDS, processorIdList);
             for (ProcessorSchema processorSchema : processors) {
                 addIssuesIfNotNull(processorSchema);
             }
+            processorIds.addAll(processorIdList);
         }
 
         if (connections != null) {
-            
checkForDuplicateNames(FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_NAMES, 
connections.stream().map(ConnectionSchema::getName).collect(Collectors.toList()));
+            List<String> idList = 
connections.stream().map(ConnectionSchema::getId).filter(s -> 
!StringUtil.isNullOrEmpty(s)).collect(Collectors.toList());
+            checkForDuplicates(this::addValidationIssue, 
FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_IDS, idList);
             for (ConnectionSchema connectionSchema : connections) {
                 addIssuesIfNotNull(connectionSchema);
             }
         }
 
+        Set<String> remoteInputPortIds = new HashSet<>();
         if (remoteProcessingGroups != null) {
-            
checkForDuplicateNames(FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_PROCESSING_GROUP_NAMES,
 
remoteProcessingGroups.stream().map(RemoteProcessingGroupSchema::getName).collect(Collectors.toList()));
+            checkForDuplicates(this::addValidationIssue, 
FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_PROCESSING_GROUP_NAMES,
+                    
remoteProcessingGroups.stream().map(RemoteProcessingGroupSchema::getName).collect(Collectors.toList()));
             for (RemoteProcessingGroupSchema remoteProcessingGroupSchema : 
remoteProcessingGroups) {
                 addIssuesIfNotNull(remoteProcessingGroupSchema);
             }
+            List<RemoteProcessingGroupSchema> remoteProcessingGroups = 
getRemoteProcessingGroups();
+            if (remoteProcessingGroups != null) {
+                List<String> remoteInputPortIdList = 
remoteProcessingGroups.stream().filter(r -> r.getInputPorts() != null)
+                        .flatMap(r -> 
r.getInputPorts().stream()).map(RemoteInputPortSchema::getId).collect(Collectors.toList());
+                checkForDuplicates(this::addValidationIssue, 
FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_INPUT_PORT_IDS, remoteInputPortIdList);
+                remoteInputPortIds.addAll(remoteInputPortIdList);
+            }
+        }
+
+        Set<String> duplicateIds = new HashSet<>(processorIds);
+        duplicateIds.retainAll(remoteInputPortIds);
+        if (duplicateIds.size() > 0) {
+            addValidationIssue(FOUND_THE_FOLLOWING_DUPLICATE_IDS + 
duplicateIds.stream().sorted().collect(Collectors.joining(", ")));
         }
     }
 
-    private void checkForDuplicateNames(String errorMessagePrefix, 
List<String> names) {
-        if (names != null) {
-            Set<String> seenNames = new HashSet<>();
-            Set<String> duplicateNames = new TreeSet<>();
-            for (String name : names) {
-                if (!seenNames.add(name)) {
-                    duplicateNames.add(name);
+    protected List<ProcessorSchema> getProcessorSchemas(List<Map> 
processorMaps) {
+        if (processorMaps == null) {
+            return null;
+        }
+        List<ProcessorSchema> processors = convertListToType(processorMaps, 
"processor", ProcessorSchema.class, PROCESSORS_KEY);
+
+        Map<String, Integer> idMap = 
processors.stream().map(ProcessorSchema::getId).filter(
+                s -> 
!StringUtil.isNullOrEmpty(s)).collect(Collectors.toMap(Function.identity(), s 
-> 2, Integer::compareTo));
+
+        // Set unset ids
+        processors.stream().filter(connection -> 
StringUtil.isNullOrEmpty(connection.getId())).forEachOrdered(processor -> 
processor.setId(getUniqueId(idMap, processor.getName())));
+
+        return processors;
+    }
+
+    protected List<ConnectionSchema> getConnectionSchemas(List<Map> 
connectionMaps) {
+        if (connectionMaps == null) {
+            return null;
+        }
+        List<ConnectionSchema> connections = convertListToType(connectionMaps, 
"connection", ConnectionSchema.class, CONNECTIONS_KEY);
+        Map<String, Integer> idMap = 
connections.stream().map(ConnectionSchema::getId).filter(
+                s -> 
!StringUtil.isNullOrEmpty(s)).collect(Collectors.toMap(Function.identity(), s 
-> 2, Integer::compareTo));
+
+        Map<String, String> processorNameToIdMap = new HashMap<>();
+
+        // We can't look up id by name for names that appear more than once
+        Set<String> duplicateProcessorNames = new HashSet<>();
+
+        List<ProcessorSchema> processors = getProcessors();
+        if (processors != null) {
+            processors.stream().forEachOrdered(p -> 
processorNameToIdMap.put(p.getName(), p.getId()));
+
+            Set<String> processorNames = new HashSet<>();
+            processors.stream().map(ProcessorSchema::getName).forEachOrdered(n 
-> {
+                if (!processorNames.add(n)) {
+                    duplicateProcessorNames.add(n);
+                }
+            });
+        }
+
+        Set<String> remoteInputPortIds = new HashSet<>();
+        List<RemoteProcessingGroupSchema> remoteProcessingGroups = 
getRemoteProcessingGroups();
+        if (remoteProcessingGroups != null) {
+            remoteInputPortIds.addAll(remoteProcessingGroups.stream().filter(r 
-> r.getInputPorts() != null)
+                    .flatMap(r -> 
r.getInputPorts().stream()).map(RemoteInputPortSchema::getId).collect(Collectors.toSet()));
+        }
+
+        Set<String> problematicDuplicateNames = new HashSet<>();
+        Set<String> missingProcessorNames = new HashSet<>();
+        // Set unset ids
+        connections.stream().filter(connection -> 
StringUtil.isNullOrEmpty(connection.getId())).forEachOrdered(connection -> 
connection.setId(getUniqueId(idMap, connection.getName())));
+
+        connections.stream().filter(connection -> 
StringUtil.isNullOrEmpty(connection.getSourceId())).forEach(connection -> {
+            String sourceName = connection.getSourceName();
+            if (remoteInputPortIds.contains(sourceName)) {
+                connection.setSourceId(sourceName);
+            } else {
+                if (duplicateProcessorNames.contains(sourceName)) {
+                    problematicDuplicateNames.add(sourceName);
+                }
+                String sourceId = processorNameToIdMap.get(sourceName);
+                if (StringUtil.isNullOrEmpty(sourceId)) {
+                    missingProcessorNames.add(sourceName);
+                } else {
+                    connection.setSourceId(sourceId);
+                }
+            }
+        });
+
+        connections.stream().filter(connection -> 
StringUtil.isNullOrEmpty(connection.getDestinationId()))
+                .forEach(connection -> {
+                    String destinationName = connection.getDestinationName();
+                    if (remoteInputPortIds.contains(destinationName)) {
+                        connection.setDestinationId(destinationName);
+                    } else {
+                        if (duplicateProcessorNames.contains(destinationName)) 
{
+                            problematicDuplicateNames.add(destinationName);
+                        }
+                        String destinationId = 
processorNameToIdMap.get(destinationName);
+                        if (StringUtil.isNullOrEmpty(destinationId)) {
+                            missingProcessorNames.add(destinationName);
+                        } else {
+                            connection.setDestinationId(destinationId);
+                        }
+                    }
+                });
+
+        if (problematicDuplicateNames.size() > 0) {
+            
addValidationIssue(CANNOT_LOOK_UP_PROCESSOR_ID_FROM_PROCESSOR_NAME_DUE_TO_DUPLICATE_PROCESSOR_NAMES
+                    + 
problematicDuplicateNames.stream().collect(Collectors.joining(", ")));
+        }
+        if (missingProcessorNames.size() > 0) {
+            
addValidationIssue(CONNECTIONS_REFER_TO_PROCESSOR_NAMES_THAT_DONT_EXIST + 
missingProcessorNames.stream().sorted().collect(Collectors.joining(", ")));
+        }
+        return connections;
+    }
+
+    protected static void checkForDuplicates(Consumer<String> 
duplicateMessageConsumer, String errorMessagePrefix, List<String> strings) {
+        if (strings != null) {
+            Set<String> seen = new HashSet<>();
+            Set<String> duplicates = new TreeSet<>();
+            for (String string : strings) {
+                if (!seen.add(string)) {
+                    duplicates.add(String.valueOf(string));
                 }
             }
-            if (duplicateNames.size() > 0) {
+            if (duplicates.size() > 0) {
                 StringBuilder errorMessage = new 
StringBuilder(errorMessagePrefix);
-                for (String duplicateName : duplicateNames) {
+                for (String duplicateName : duplicates) {
                     errorMessage.append(duplicateName);
                     errorMessage.append(", ");
                 }
                 errorMessage.setLength(errorMessage.length() - 2);
-                validationIssues.add(errorMessage.toString());
+                duplicateMessageConsumer.accept(errorMessage.toString());
             }
         }
     }
 
     public Map<String, Object> toMap() {
         Map<String, Object> result = mapSupplier.get();
+        result.put(VERSION, getVersion());
         result.put(FLOW_CONTROLLER_PROPS_KEY, 
flowControllerProperties.toMap());
         putIfNotNull(result, CORE_PROPS_KEY, coreProperties);
         putIfNotNull(result, FLOWFILE_REPO_KEY, flowfileRepositoryProperties);
@@ -198,4 +320,32 @@ public class ConfigSchema extends BaseSchema {
     public ProvenanceRepositorySchema getProvenanceRepositorySchema() {
         return provenanceRepositorySchema;
     }
+
+    public int getVersion() {
+        return CONFIG_VERSION;
+    }
+
+    /**
+     * Will replace all characters not in [A-Za-z0-9_] with _
+     * <p>
+     * This has potential for collisions so it will also append numbers as 
necessary to prevent that
+     *
+     * @param ids  id map of already incremented numbers
+     * @param name the name
+     * @return a unique filesystem-friendly id
+     */
+    protected static String getUniqueId(Map<String, Integer> ids, String name) 
{
+        String baseId = StringUtil.isNullOrEmpty(name) ? EMPTY_NAME : 
ID_REPLACE_PATTERN.matcher(name).replaceAll("_");
+        String id = baseId;
+        Integer idNum = ids.get(baseId);
+        while (ids.containsKey(id)) {
+            id = baseId + "_" + idNum++;
+        }
+        // Using != on a string comparison here is intentional.  The two will 
be reference equal iff the body of the while loop was never executed.
+        if (id != baseId) {
+            ids.put(baseId, idNum);
+        }
+        ids.put(id, 2);
+        return id;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConnectionSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConnectionSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConnectionSchema.java
index 34bd61f..ebf166d 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConnectionSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ConnectionSchema.java
@@ -17,32 +17,35 @@
 
 package org.apache.nifi.minifi.commons.schema;
 
-import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
+import org.apache.nifi.minifi.commons.schema.common.BaseSchemaWithIdAndName;
+import org.apache.nifi.minifi.commons.schema.common.StringUtil;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.CONNECTIONS_KEY;
-import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
 
-/**
- *
- */
-public class ConnectionSchema extends BaseSchema {
-    public static final String SOURCE_NAME_KEY = "source name";
+public class ConnectionSchema extends BaseSchemaWithIdAndName {
+    public static final String SOURCE_ID_KEY = "source id";
     public static final String SOURCE_RELATIONSHIP_NAME_KEY = "source 
relationship name";
-    public static final String DESTINATION_NAME_KEY = "destination name";
+    public static final String DESTINATION_ID_KEY = "destination id";
     public static final String MAX_WORK_QUEUE_SIZE_KEY = "max work queue size";
     public static final String MAX_WORK_QUEUE_DATA_SIZE_KEY = "max work queue 
data size";
     public static final String FLOWFILE_EXPIRATION__KEY = "flowfile 
expiration";
     public static final String QUEUE_PRIORITIZER_CLASS_KEY = "queue 
prioritizer class";
+    public static final String SOURCE_NAME_KEY = "source name";
+    public static final String DESTINATION_NAME_KEY = "destination name";
 
     public static final long DEFAULT_MAX_WORK_QUEUE_SIZE = 0;
     public static final String DEFAULT_MAX_QUEUE_DATA_SIZE = "0 MB";
     public static final String DEFAULT_FLOWFILE_EXPIRATION = "0 sec";
 
-    private String name;
-    private String sourceName;
+    private String sourceId;
     private String sourceRelationshipName;
+    private String destinationId;
+
+    private String sourceName;
     private String destinationName;
 
     private Number maxWorkQueueSize = DEFAULT_MAX_WORK_QUEUE_SIZE;
@@ -51,10 +54,18 @@ public class ConnectionSchema extends BaseSchema {
     private String queuePrioritizerClass;
 
     public ConnectionSchema(Map map) {
-        name = getRequiredKeyAsType(map, NAME_KEY, String.class, 
CONNECTIONS_KEY);
-        sourceName = getRequiredKeyAsType(map, SOURCE_NAME_KEY, String.class, 
CONNECTIONS_KEY);
+        super(map, CONNECTIONS_KEY);
+
+        sourceId = getOptionalKeyAsType(map, SOURCE_ID_KEY, String.class, 
CONNECTIONS_KEY, "");
+        if (StringUtil.isNullOrEmpty(sourceId)) {
+            sourceName = getRequiredKeyAsType(map, SOURCE_NAME_KEY, 
String.class, CONNECTIONS_KEY);
+        }
         sourceRelationshipName = getRequiredKeyAsType(map, 
SOURCE_RELATIONSHIP_NAME_KEY, String.class, CONNECTIONS_KEY);
-        destinationName = getRequiredKeyAsType(map, DESTINATION_NAME_KEY, 
String.class, CONNECTIONS_KEY);
+
+        destinationId = getOptionalKeyAsType(map, DESTINATION_ID_KEY, 
String.class, CONNECTIONS_KEY, "");
+        if (StringUtil.isNullOrEmpty(getDestinationId())) {
+            destinationName = getRequiredKeyAsType(map, DESTINATION_NAME_KEY, 
String.class, CONNECTIONS_KEY);
+        }
 
         maxWorkQueueSize = getOptionalKeyAsType(map, MAX_WORK_QUEUE_SIZE_KEY, 
Number.class, CONNECTIONS_KEY, DEFAULT_MAX_WORK_QUEUE_SIZE);
         maxWorkQueueDataSize = getOptionalKeyAsType(map, 
MAX_WORK_QUEUE_DATA_SIZE_KEY, String.class, CONNECTIONS_KEY, 
DEFAULT_MAX_QUEUE_DATA_SIZE);
@@ -64,11 +75,10 @@ public class ConnectionSchema extends BaseSchema {
 
     @Override
     public Map<String, Object> toMap() {
-        Map<String, Object> result = mapSupplier.get();
-        result.put(NAME_KEY, name);
-        result.put(SOURCE_NAME_KEY, sourceName);
+        Map<String, Object> result = super.toMap();
+        result.put(SOURCE_ID_KEY, sourceId);
         result.put(SOURCE_RELATIONSHIP_NAME_KEY, sourceRelationshipName);
-        result.put(DESTINATION_NAME_KEY, destinationName);
+        result.put(DESTINATION_ID_KEY, destinationId);
 
         result.put(MAX_WORK_QUEUE_SIZE_KEY, maxWorkQueueSize);
         result.put(MAX_WORK_QUEUE_DATA_SIZE_KEY, maxWorkQueueDataSize);
@@ -77,20 +87,24 @@ public class ConnectionSchema extends BaseSchema {
         return result;
     }
 
-    public String getName() {
-        return name;
+    public String getSourceId() {
+        return sourceId;
     }
 
-    public String getSourceName() {
-        return sourceName;
+    public void setSourceId(String sourceId) {
+        this.sourceId = sourceId;
     }
 
-    public String getSourceRelationshipName() {
-        return sourceRelationshipName;
+    public String getDestinationId() {
+        return destinationId;
     }
 
-    public String getDestinationName() {
-        return destinationName;
+    public void setDestinationId(String destinationId) {
+        this.destinationId = destinationId;
+    }
+
+    public String getSourceRelationshipName() {
+        return sourceRelationshipName;
     }
 
     public Number getMaxWorkQueueSize() {
@@ -108,4 +122,24 @@ public class ConnectionSchema extends BaseSchema {
     public String getQueuePrioritizerClass() {
         return queuePrioritizerClass;
     }
+
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    public String getDestinationName() {
+        return destinationName;
+    }
+
+    @Override
+    public List<String> getValidationIssues() {
+        List<String> validationIssues = super.getValidationIssues();
+        if (StringUtil.isNullOrEmpty(getSourceId())) {
+            validationIssues.add(getIssueText(SOURCE_ID_KEY, CONNECTIONS_KEY, 
IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED));
+        }
+        if (StringUtil.isNullOrEmpty(getDestinationId())) {
+            validationIssues.add(getIssueText(DESTINATION_ID_KEY, 
CONNECTIONS_KEY, IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED));
+        }
+        return Collections.unmodifiableList(validationIssues);
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ProcessorSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ProcessorSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ProcessorSchema.java
index d008df5..471762a 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ProcessorSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/ProcessorSchema.java
@@ -17,7 +17,7 @@
 
 package org.apache.nifi.minifi.commons.schema;
 
-import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
+import org.apache.nifi.minifi.commons.schema.common.BaseSchemaWithIdAndName;
 import org.apache.nifi.scheduling.SchedulingStrategy;
 
 import java.util.Collections;
@@ -26,16 +26,12 @@ import java.util.Map;
 import java.util.TreeMap;
 
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.MAX_CONCURRENT_TASKS_KEY;
-import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.PROCESSORS_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.SCHEDULING_PERIOD_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.SCHEDULING_STRATEGY_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.YIELD_PERIOD_KEY;
 
-/**
- *
- */
-public class ProcessorSchema extends BaseSchema {
+public class ProcessorSchema extends BaseSchemaWithIdAndName {
     public static final String CLASS_KEY = "class";
     public static final String PENALIZATION_PERIOD_KEY = "penalization period";
     public static final String RUN_DURATION_NANOS_KEY = "run duration nanos";
@@ -49,7 +45,6 @@ public class ProcessorSchema extends BaseSchema {
     public static final Map<String, Object> DEFAULT_PROPERTIES = 
Collections.emptyMap();
     public static final String IT_IS_NOT_A_VALID_SCHEDULING_STRATEGY = "it is 
not a valid scheduling strategy";
 
-    private String name;
     private String processorClass;
     private String schedulingStrategy;
     private String schedulingPeriod;
@@ -61,7 +56,7 @@ public class ProcessorSchema extends BaseSchema {
     private Map<String, Object> properties = DEFAULT_PROPERTIES;
 
     public ProcessorSchema(Map map) {
-        name = getRequiredKeyAsType(map, NAME_KEY, String.class, 
PROCESSORS_KEY);
+        super(map, PROCESSORS_KEY);
         processorClass = getRequiredKeyAsType(map, CLASS_KEY, String.class, 
PROCESSORS_KEY);
         schedulingStrategy = getRequiredKeyAsType(map, 
SCHEDULING_STRATEGY_KEY, String.class, PROCESSORS_KEY);
         if (schedulingStrategy != null && 
!isSchedulingStrategy(schedulingStrategy)) {
@@ -88,8 +83,7 @@ public class ProcessorSchema extends BaseSchema {
 
     @Override
     public Map<String, Object> toMap() {
-        Map<String, Object> result = mapSupplier.get();
-        result.put(NAME_KEY, name);
+        Map<String, Object> result = super.toMap();
         result.put(CLASS_KEY, processorClass);
         result.put(MAX_CONCURRENT_TASKS_KEY, maxConcurrentTasks);
         result.put(SCHEDULING_STRATEGY_KEY, schedulingStrategy);
@@ -102,14 +96,6 @@ public class ProcessorSchema extends BaseSchema {
         return result;
     }
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
     public String getProcessorClass() {
         return processorClass;
     }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteInputPortSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteInputPortSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteInputPortSchema.java
index f12f443..6ff8648 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteInputPortSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteInputPortSchema.java
@@ -17,35 +17,29 @@
 
 package org.apache.nifi.minifi.commons.schema;
 
-import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
+import org.apache.nifi.minifi.commons.schema.common.BaseSchemaWithIdAndName;
 
 import java.util.Map;
 
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.COMMENT_KEY;
-import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.ID_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.INPUT_PORTS_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.MAX_CONCURRENT_TASKS_KEY;
-import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.USE_COMPRESSION_KEY;
 
 /**
  *
  */
-public class RemoteInputPortSchema extends BaseSchema {
+public class RemoteInputPortSchema extends BaseSchemaWithIdAndName {
     public static final String DEFAULT_COMMENT = "";
     public static final int DEFAULT_MAX_CONCURRENT_TASKS = 1;
     public static final boolean DEFAULT_USE_COMPRESSION = true;
 
-    private String id;
-    private String name;
-
     private String comment = DEFAULT_COMMENT;
     private Number maxConcurrentTasks = DEFAULT_MAX_CONCURRENT_TASKS;
     private Boolean useCompression = DEFAULT_USE_COMPRESSION;
 
     public RemoteInputPortSchema(Map map) {
-        id = getRequiredKeyAsType(map, ID_KEY, String.class, INPUT_PORTS_KEY);
-        name = getRequiredKeyAsType(map, NAME_KEY, String.class, 
INPUT_PORTS_KEY);
+        super(map, INPUT_PORTS_KEY);
 
         comment = getOptionalKeyAsType(map, COMMENT_KEY, String.class, 
INPUT_PORTS_KEY, DEFAULT_COMMENT);
         maxConcurrentTasks = getOptionalKeyAsType(map, 
MAX_CONCURRENT_TASKS_KEY, Number.class, INPUT_PORTS_KEY, 
DEFAULT_MAX_CONCURRENT_TASKS);
@@ -54,23 +48,13 @@ public class RemoteInputPortSchema extends BaseSchema {
 
     @Override
     public Map<String, Object> toMap() {
-        Map<String, Object> result = mapSupplier.get();
-        result.put(ID_KEY, id);
-        result.put(NAME_KEY, name);
+        Map<String, Object> result = super.toMap();
         result.put(COMMENT_KEY, comment);
         result.put(MAX_CONCURRENT_TASKS_KEY, maxConcurrentTasks);
         result.put(USE_COMPRESSION_KEY, useCompression);
         return result;
     }
 
-    public String getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
     public String getComment() {
         return comment;
     }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteProcessingGroupSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteProcessingGroupSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteProcessingGroupSchema.java
index 3fb351e..86ed71e 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteProcessingGroupSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/RemoteProcessingGroupSchema.java
@@ -50,10 +50,8 @@ public class RemoteProcessingGroupSchema extends BaseSchema {
     public RemoteProcessingGroupSchema(Map map) {
         name = getRequiredKeyAsType(map, NAME_KEY, String.class, 
REMOTE_PROCESSING_GROUPS_KEY);
         url = getRequiredKeyAsType(map, URL_KEY, String.class, 
REMOTE_PROCESSING_GROUPS_KEY);
-        inputPorts = getRequiredKeyAsType(map, INPUT_PORTS_KEY, List.class, 
REMOTE_PROCESSING_GROUPS_KEY);
+        inputPorts = convertListToType(getRequiredKeyAsType(map, 
INPUT_PORTS_KEY, List.class, REMOTE_PROCESSING_GROUPS_KEY), "input port", 
RemoteInputPortSchema.class, INPUT_PORTS_KEY);
         if (inputPorts != null) {
-            transformListToType(inputPorts, "input port", 
RemoteInputPortSchema.class, INPUT_PORTS_KEY);
-
             for (RemoteInputPortSchema remoteInputPortSchema: inputPorts) {
                 addIssuesIfNotNull(remoteInputPortSchema);
             }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/SecurityPropertiesSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/SecurityPropertiesSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/SecurityPropertiesSchema.java
index be41810..6adfdfe 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/SecurityPropertiesSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/SecurityPropertiesSchema.java
@@ -100,14 +100,14 @@ public class SecurityPropertiesSchema extends BaseSchema {
                     break;
             }
             if (StringUtil.isNullOrEmpty(keystore)) {
-                validationIssues.add("When the '" + SSL_PROTOCOL_KEY + "' key 
of '" + SECURITY_PROPS_KEY + "' is set, the '" + KEYSTORE_KEY + "' must also be 
set");
+                addValidationIssue("When the '" + SSL_PROTOCOL_KEY + "' key of 
'" + SECURITY_PROPS_KEY + "' is set, the '" + KEYSTORE_KEY + "' must also be 
set");
             } else if (StringUtil.isNullOrEmpty(keystoreType) || 
StringUtil.isNullOrEmpty(keystorePassword) || 
StringUtil.isNullOrEmpty(keyPassword)) {
-                validationIssues.add("When the '" + KEYSTORE_KEY + "' key of 
'" + SECURITY_PROPS_KEY + "' is set, the '" + KEYSTORE_TYPE_KEY + "', '" + 
KEYSTORE_PASSWORD_KEY +
+                addValidationIssue("When the '" + KEYSTORE_KEY + "' key of '" 
+ SECURITY_PROPS_KEY + "' is set, the '" + KEYSTORE_TYPE_KEY + "', '" + 
KEYSTORE_PASSWORD_KEY +
                         "' and '" + KEY_PASSWORD_KEY + "' all must also be 
set");
             }
 
             if (!StringUtil.isNullOrEmpty(truststore) && 
(StringUtil.isNullOrEmpty(truststoreType) || 
StringUtil.isNullOrEmpty(truststorePassword))) {
-                validationIssues.add("When the '" + TRUSTSTORE_KEY + "' key of 
'" + SECURITY_PROPS_KEY + "' is set, the '" + TRUSTSTORE_TYPE_KEY + "' and '" +
+                addValidationIssue("When the '" + TRUSTSTORE_KEY + "' key of 
'" + SECURITY_PROPS_KEY + "' is set, the '" + TRUSTSTORE_TYPE_KEY + "' and '" +
                         TRUSTSTORE_PASSWORD_KEY + "' must also be set");
             }
         }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchema.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchema.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchema.java
index 22f45cf..0c269cc 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchema.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchema.java
@@ -19,6 +19,7 @@ package org.apache.nifi.minifi.commons.schema.common;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
@@ -42,20 +43,20 @@ public abstract class BaseSchema {
     }
 
     /******* Validation Issue helper methods *******/
-    public List<String> validationIssues = new LinkedList<>();
+    private List<String> validationIssues = new LinkedList<>();
 
     public boolean isValid() {
-        return validationIssues.isEmpty();
+        return getValidationIssues().isEmpty();
     }
 
     public List<String> getValidationIssues() {
-        return validationIssues;
+        return new ArrayList<>(validationIssues);
     }
 
     public String getValidationIssuesAsString() {
         StringBuilder stringBuilder = new StringBuilder();
         boolean first = true;
-        for (String validationIssue : validationIssues) {
+        for (String validationIssue : getValidationIssues()) {
             if (!first) {
                 stringBuilder.append(", ");
             }
@@ -79,8 +80,16 @@ public abstract class BaseSchema {
         return result;
     }
 
+    public void addValidationIssue(String issue) {
+        validationIssues.add(issue);
+    }
+
     public void addValidationIssue(String keyName, String wrapperName, String 
reason) {
-        validationIssues.add("'" + keyName + "' in section '" + wrapperName + 
"' because " + reason);
+        addValidationIssue(getIssueText(keyName, wrapperName, reason));
+    }
+
+    public static String getIssueText(String keyName, String wrapperName, 
String reason) {
+        return "'" + keyName + "' in section '" + wrapperName + "' because " + 
reason;
     }
 
     public void addIssuesIfNotNull(BaseSchema baseSchema) {
@@ -127,13 +136,18 @@ public abstract class BaseSchema {
         return interpretValueAsType(obj, key, targetClass, wrapperName, 
required, instantiateIfNull);
     }
 
-    public <T> void transformListToType(List<T> list, String simpleListType, 
Class<T> targetClass, String wrapperName){
+    public <InputT, OutputT> List<OutputT> convertListToType(List<InputT> 
list, String simpleListType, Class<? extends OutputT> targetClass, String 
wrapperName){
+        if (list == null) {
+            return null;
+        }
+        List<OutputT> result = new ArrayList<>(list.size());
         for (int i = 0; i < list.size(); i++) {
-            T obj = interpretValueAsType(list.get(i), simpleListType + " 
number " + i, targetClass, wrapperName, false, false);
-            if (obj != null) {
-                list.set(i, obj);
+            OutputT val = interpretValueAsType(list.get(i), simpleListType + " 
number " + i, targetClass, wrapperName, false, false);
+            if (val != null) {
+                result.add(val);
             }
         }
+        return result;
     }
 
     private <T> T interpretValueAsType(Object obj, String key, Class 
targetClass, String wrapperName, boolean required, boolean instantiateIfNull) {

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchemaWithIdAndName.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchemaWithIdAndName.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchemaWithIdAndName.java
new file mode 100644
index 0000000..8acb167
--- /dev/null
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/common/BaseSchemaWithIdAndName.java
@@ -0,0 +1,85 @@
+/*
+ *
+ *  * 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.minifi.commons.schema.common;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.ID_KEY;
+import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
+
+public abstract class BaseSchemaWithIdAndName extends BaseSchema {
+    public static final Pattern VALID_ID_PATTERN = 
Pattern.compile("[A-Za-z0-9_-]+");
+    public static final String ID_DOES_NOT_MATCH_VALID_ID_PATTERN = "Id does 
not match valid pattern (" + VALID_ID_PATTERN + "): ";
+
+    private final String wrapperName;
+    private String id;
+    private String name;
+
+    public BaseSchemaWithIdAndName(Map map, String wrapperName) {
+        id = getId(map, wrapperName);
+        name = getName(map, wrapperName);
+        this.wrapperName = wrapperName;
+    }
+
+    protected String getName(Map map, String wrapperName) {
+        return getOptionalKeyAsType(map, NAME_KEY, String.class, wrapperName, 
"");
+    }
+
+    protected String getId(Map map, String wrapperName) {
+        return getOptionalKeyAsType(map, ID_KEY, String.class, wrapperName, 
"");
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    protected void setName(String name) {
+        this.name = name;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Map<String, Object> toMap() {
+        Map<String, Object> map = mapSupplier.get();
+        map.put(NAME_KEY, name);
+        map.put(ID_KEY, id);
+        return map;
+    }
+
+    @Override
+    public List<String> getValidationIssues() {
+        List<String> validationIssues = super.getValidationIssues();
+        if (StringUtil.isNullOrEmpty(id)) {
+            validationIssues.add(getIssueText(CommonPropertyKeys.ID_KEY, 
wrapperName, IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED));
+        } else if (!VALID_ID_PATTERN.matcher(id).matches()) {
+            validationIssues.add(ID_DOES_NOT_MATCH_VALID_ID_PATTERN + id);
+        }
+        return validationIssues;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoader.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoader.java
 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoader.java
index 88d501c..a5bf14c 100644
--- 
a/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoader.java
+++ 
b/minifi-commons/minifi-commons-schema/src/main/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoader.java
@@ -24,9 +24,23 @@ import org.yaml.snakeyaml.error.YAMLException;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 public class SchemaLoader {
+    private static final Map<String, Function<Map, ConfigSchema>> 
configSchemaFactories = initConfigSchemaFactories();
+
+    private static Map<String, Function<Map, ConfigSchema>> 
initConfigSchemaFactories() {
+        Map<String, Function<Map, ConfigSchema>> result = new HashMap<>();
+        result.put(String.valueOf((Object)null), ConfigSchema::new);
+        result.put("", ConfigSchema::new);
+        result.put(Integer.toString(ConfigSchema.CONFIG_VERSION), 
ConfigSchema::new);
+        return result;
+    }
+
+
     public static Map<String, Object> loadYamlAsMap(InputStream sourceStream) 
throws IOException, SchemaLoaderException {
         try {
             Yaml yaml = new Yaml();
@@ -40,7 +54,7 @@ public class SchemaLoader {
             } else {
                 throw new SchemaLoaderException("Provided YAML configuration 
is not a Map");
             }
-        } catch (YAMLException e ) {
+        } catch (YAMLException e) {
             throw new IOException(e);
         } finally {
             sourceStream.close();
@@ -48,6 +62,16 @@ public class SchemaLoader {
     }
 
     public static ConfigSchema loadConfigSchemaFromYaml(InputStream 
sourceStream) throws IOException, SchemaLoaderException {
-        return new ConfigSchema(loadYamlAsMap(sourceStream));
+        return loadConfigSchemaFromYaml(loadYamlAsMap(sourceStream));
+    }
+
+    public static ConfigSchema loadConfigSchemaFromYaml(Map<String, Object> 
yamlAsMap) throws SchemaLoaderException {
+        String version = String.valueOf(yamlAsMap.get(ConfigSchema.VERSION));
+        Function<Map, ConfigSchema> schemaFactory = 
configSchemaFactories.get(version);
+        if (schemaFactory == null) {
+            throw new SchemaLoaderException("YAML configuration version " + 
version + " not supported.  Supported versions: "
+                    + 
configSchemaFactories.keySet().stream().sorted().collect(Collectors.joining(", 
")));
+        }
+        return schemaFactory.apply(yamlAsMap);
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/ConfigSchemaTest.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/ConfigSchemaTest.java
 
b/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/ConfigSchemaTest.java
index e1575c3..2713d15 100644
--- 
a/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/ConfigSchemaTest.java
+++ 
b/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/ConfigSchemaTest.java
@@ -17,41 +17,85 @@
 
 package org.apache.nifi.minifi.commons.schema;
 
+import org.apache.nifi.minifi.commons.schema.common.BaseSchema;
 import org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys;
+import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
+import org.apache.nifi.minifi.commons.schema.serialization.SchemaLoader;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 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.fail;
 
 public class ConfigSchemaTest {
+    @Test
+    public void testGetUniqueIdEmptySet() {
+        String testId = "testId";
+        assertEquals(testId + "___", ConfigSchema.getUniqueId(new HashMap<>(), 
testId + "/ $"));
+    }
+
+    @Test
+    public void testConnectionGeneratedIds() {
+        List<Map<String, Object>> listWithKeyValues = 
getListWithKeyValues(CommonPropertyKeys.NAME_KEY, "test", "test", "test_2");
+
+        // These ids should be honored even though they're last
+        
listWithKeyValues.addAll(getListWithKeyValues(CommonPropertyKeys.ID_KEY, 
"test", "test_2"));
+
+        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.CONNECTIONS_KEY, 
listWithKeyValues));
+        assertMessageDoesNotExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_IDS);
+        List<ConnectionSchema> connections = configSchema.getConnections();
+        assertEquals(5, connections.size());
+
+        // Generated unique ids
+        assertEquals("test_3", connections.get(0).getId());
+        assertEquals("test_4", connections.get(1).getId());
+        assertEquals("test_2_2", connections.get(2).getId());
+
+        // Specified ids
+        assertEquals("test", connections.get(3).getId());
+        assertEquals("test_2", connections.get(4).getId());
+    }
+
+    @Test
+    public void testGetUniqueIdConflicts() {
+        Map<String, Integer> ids = new HashMap<>();
+        assertEquals("test_id", ConfigSchema.getUniqueId(ids, "test/id"));
+        assertEquals("test_id_2", ConfigSchema.getUniqueId(ids, "test$id"));
+        assertEquals("test_id_3", ConfigSchema.getUniqueId(ids, "test$id"));
+        assertEquals("test_id_4", ConfigSchema.getUniqueId(ids, "test$id"));
+        assertEquals("test_id_5", ConfigSchema.getUniqueId(ids, "test$id"));
+        assertEquals("test_id_2_2", ConfigSchema.getUniqueId(ids, 
"test_id_2"));
+    }
 
     @Test
     public void testProcessorDuplicateValidationNegativeCase() {
-        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.PROCESSORS_KEY, 
getListWithNames("testName1", "testName2")));
-        assertMessageDoesNotExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_NAMES);
+        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.PROCESSORS_KEY, 
getListWithKeyValues(CommonPropertyKeys.ID_KEY, "testId1", "testId2")));
+        assertMessageDoesNotExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_IDS);
     }
 
     @Test
     public void testProcessorDuplicateValidationPositiveCase() {
-        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.PROCESSORS_KEY, 
getListWithNames("testName1", "testName1")));
-        assertMessageDoesExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_NAMES);
+        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.PROCESSORS_KEY, 
getListWithKeyValues(CommonPropertyKeys.ID_KEY, "testId1", "testId1")));
+        assertMessageDoesExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_PROCESSOR_IDS);
     }
 
     @Test
     public void testConnectionDuplicateValidationNegativeCase() {
-        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.CONNECTIONS_KEY, 
getListWithNames("testName1", "testName2")));
-        assertMessageDoesNotExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_NAMES);
+        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.CONNECTIONS_KEY, 
getListWithKeyValues(CommonPropertyKeys.ID_KEY, "testId1", "testId2")));
+        assertMessageDoesNotExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_IDS);
     }
 
     @Test
     public void testConnectionDuplicateValidationPositiveCase() {
-        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.CONNECTIONS_KEY, 
getListWithNames("testName1", "testName1")));
-        assertMessageDoesExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_NAMES);
+        ConfigSchema configSchema = new 
ConfigSchema(Collections.singletonMap(CommonPropertyKeys.CONNECTIONS_KEY, 
getListWithKeyValues(CommonPropertyKeys.ID_KEY, "testId1", "testId1")));
+        assertMessageDoesExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_CONNECTION_IDS);
     }
 
     @Test
@@ -66,22 +110,47 @@ public class ConfigSchemaTest {
         assertMessageDoesExist(configSchema, 
ConfigSchema.FOUND_THE_FOLLOWING_DUPLICATE_REMOTE_PROCESSING_GROUP_NAMES);
     }
 
-    private List<Map<String, Object>> getListWithNames(String... names) {
-        List<Map<String, Object>> result = new ArrayList<>(names.length);
-        for (String name : names) {
-            result.add(Collections.singletonMap(CommonPropertyKeys.NAME_KEY, 
name));
+    @Test
+    public void testInvalidSourceAndDestinationNames() throws IOException, 
SchemaLoaderException {
+        Map<String, Object> yamlAsMap = 
SchemaLoader.loadYamlAsMap(ConfigSchemaTest.class.getClassLoader().getResourceAsStream("config-minimal.yml"));
+        List<Map<String, Object>> connections = (List<Map<String, Object>>) 
yamlAsMap.get(CommonPropertyKeys.CONNECTIONS_KEY);
+        assertEquals(1, connections.size());
+
+        String fakeSource = "fakeSource";
+        String fakeDestination = "fakeDestination";
+
+        Map<String, Object> connection = connections.get(0);
+        connection.put(ConnectionSchema.SOURCE_NAME_KEY, fakeSource);
+        connection.put(ConnectionSchema.DESTINATION_NAME_KEY, fakeDestination);
+
+        ConfigSchema configSchema = new ConfigSchema(yamlAsMap);
+        List<String> validationIssues = configSchema.getValidationIssues();
+        assertEquals(3, validationIssues.size());
+        
assertEquals(ConfigSchema.CONNECTIONS_REFER_TO_PROCESSOR_NAMES_THAT_DONT_EXIST 
+ fakeDestination + ", " + fakeSource, validationIssues.get(0));
+        assertEquals(BaseSchema.getIssueText(ConnectionSchema.SOURCE_ID_KEY, 
CommonPropertyKeys.CONNECTIONS_KEY, 
BaseSchema.IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED), validationIssues.get(1));
+        
assertEquals(BaseSchema.getIssueText(ConnectionSchema.DESTINATION_ID_KEY, 
CommonPropertyKeys.CONNECTIONS_KEY, 
BaseSchema.IT_WAS_NOT_FOUND_AND_IT_IS_REQUIRED), validationIssues.get(2));
+    }
+
+    public static List<Map<String, Object>> getListWithNames(String... names) {
+        return getListWithKeyValues(CommonPropertyKeys.NAME_KEY, names);
+    }
+
+    public static List<Map<String, Object>> getListWithKeyValues(String key, 
String... values) {
+        List<Map<String, Object>> result = new ArrayList<>(values.length);
+        for (String value : values) {
+            result.add(Collections.singletonMap(key, value));
         }
         return result;
     }
 
-    private void assertMessageDoesNotExist(ConfigSchema configSchema, String 
message) {
-        for (String validationIssue : configSchema.validationIssues) {
+    public static void assertMessageDoesNotExist(ConfigSchema configSchema, 
String message) {
+        for (String validationIssue : configSchema.getValidationIssues()) {
             assertFalse("Did not expect to find message: " + validationIssue, 
validationIssue.startsWith(message));
         }
     }
 
-    private void assertMessageDoesExist(ConfigSchema configSchema, String 
message) {
-        for (String validationIssue : configSchema.validationIssues) {
+    public static void assertMessageDoesExist(ConfigSchema configSchema, 
String message) {
+        for (String validationIssue : configSchema.getValidationIssues()) {
             if (validationIssue.startsWith(message)) {
                 return;
             }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoaderTest.java
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoaderTest.java
 
b/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoaderTest.java
new file mode 100644
index 0000000..8d64afa
--- /dev/null
+++ 
b/minifi-commons/minifi-commons-schema/src/test/java/org/apache/nifi/minifi/commons/schema/serialization/SchemaLoaderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.minifi.commons.schema.serialization;
+
+import org.apache.nifi.minifi.commons.schema.ConfigSchema;
+import org.apache.nifi.minifi.commons.schema.ConnectionSchema;
+import org.apache.nifi.minifi.commons.schema.ProcessorSchema;
+import org.apache.nifi.minifi.commons.schema.exception.SchemaLoaderException;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class SchemaLoaderTest {
+    @Test
+    public void testMinimalConfigNoVersion() throws IOException, 
SchemaLoaderException {
+        ConfigSchema configSchema = 
SchemaLoader.loadConfigSchemaFromYaml(SchemaLoaderTest.class.getClassLoader().getResourceAsStream("config-minimal.yml"));
+        validateMinimalConfigVersion1Parse(configSchema);
+    }
+
+    @Test
+    public void testMinimalConfigEmptyVersion() throws IOException, 
SchemaLoaderException {
+        Map<String, Object> yamlAsMap = 
SchemaLoader.loadYamlAsMap(SchemaLoaderTest.class.getClassLoader().getResourceAsStream("config-minimal.yml"));
+        yamlAsMap.put(ConfigSchema.VERSION, "");
+        ConfigSchema configSchema = 
SchemaLoader.loadConfigSchemaFromYaml(yamlAsMap);
+        validateMinimalConfigVersion1Parse(configSchema);
+    }
+
+    @Test
+    public void testMinimalConfigV1Version() throws IOException, 
SchemaLoaderException {
+        Map<String, Object> yamlAsMap = 
SchemaLoader.loadYamlAsMap(SchemaLoaderTest.class.getClassLoader().getResourceAsStream("config-minimal.yml"));
+        yamlAsMap.put(ConfigSchema.VERSION, ConfigSchema.CONFIG_VERSION);
+        ConfigSchema configSchema = 
SchemaLoader.loadConfigSchemaFromYaml(yamlAsMap);
+        validateMinimalConfigVersion1Parse(configSchema);
+    }
+
+    private void validateMinimalConfigVersion1Parse(ConfigSchema configSchema) 
{
+        assertTrue(configSchema instanceof ConfigSchema);
+
+        List<ConnectionSchema> connections = configSchema.getConnections();
+        assertNotNull(connections);
+        assertEquals(1, connections.size());
+        assertNotNull(connections.get(0).getId());
+
+        List<ProcessorSchema> processors = configSchema.getProcessors();
+        assertNotNull(processors);
+        assertEquals(2, processors.size());
+        processors.forEach(p -> assertNotNull(p.getId()));
+
+        assertEquals("Expected no errors, got: " + 
configSchema.getValidationIssues(), 0, 
configSchema.getValidationIssues().size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-commons/minifi-commons-schema/src/test/resources/config-minimal.yml
----------------------------------------------------------------------
diff --git 
a/minifi-commons/minifi-commons-schema/src/test/resources/config-minimal.yml 
b/minifi-commons/minifi-commons-schema/src/test/resources/config-minimal.yml
new file mode 100644
index 0000000..2612ad1
--- /dev/null
+++ b/minifi-commons/minifi-commons-schema/src/test/resources/config-minimal.yml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the \"License\"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an \"AS IS\" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a minimal V1 config.  It is missing the version number as well as an 
id for the connection.
+Flow Controller:
+    name: MiNiFi Flow
+
+# When running the Flow (not just doing the transform) these processors will 
be invalid due to not having the necesary properties/auto-terminated 
relationships
+Processors:
+    - name: TailAppLog
+      class: org.apache.nifi.processors.standard.TailFile
+      scheduling strategy: TIMER_DRIVEN
+      scheduling period: 10 sec
+    - name: PutFile
+      class: org.apache.nifi.processors.standard.PutFile
+      max concurrent tasks: 1
+      scheduling strategy: TIMER_DRIVEN
+      scheduling period: 0 sec
+
+Connections:
+    - name: TailToSplit
+      source name: TailAppLog
+      source relationship name: success
+      destination name: PutFile

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-docs/src/main/markdown/System_Admin_Guide.md
----------------------------------------------------------------------
diff --git a/minifi-docs/src/main/markdown/System_Admin_Guide.md 
b/minifi-docs/src/main/markdown/System_Admin_Guide.md
index 8a249ad..f0e8e9e 100644
--- a/minifi-docs/src/main/markdown/System_Admin_Guide.md
+++ b/minifi-docs/src/main/markdown/System_Admin_Guide.md
@@ -174,10 +174,11 @@ for example "10 sec" or "10 MB", not simply "10".
 
 The first section of config.yml is for naming and commenting on the file.
 
- Property | Description
- -------- | ---
-name      | The name of the file.
-comment   | A comment describing the usage of this config file.
+ Property             | Description
+--------------------- | -----------
+MiNiFi Config Version | The version of the configuration file.  The default 
value if this property is missing or empty is 1 which is the only version (and 
correct value) currently.
+name                  | The name of the file.
+comment               | A comment describing the usage of this config file.
 
 ## Core Properties
 
@@ -289,6 +290,7 @@ The current implementation of MiNiFi supports multiple 
processors. the "Processo
 *Property*                          | *Description*
 ----------------------------------- | -------------
 name                                | The name of what this processor will do. 
This is not used for any underlying implementation but solely for the users of 
this configuration and MiNiFi agent.
+id                                  | The id of this processor.  This can be 
omitted but in processors without this field, there should not be any duplicate 
names and connections will need to specify source and destination name instead 
of id.  If set it should be a filesystem-friendly value (regex: [A-Za-z0-9_-]+)
 class                               | The fully qualified java class name of 
the processor to run. For example for the standard TailFile processor it would 
be: org.apache.nifi.processors.standard.TailFile
 max concurrent tasks                | The maximum number of tasks that the 
processor will use.
 scheduling strategy                 | The strategy for executing the 
processor. Valid options are `CRON_DRIVEN` or `TIMER_DRIVEN`
@@ -315,6 +317,7 @@ There can be multiple connections in this version of 
MiNiFi. The "Connections" s
 *Property*               | *Description*
 --------------------     | -------------
 name                     | The name of what this connection will do. This is 
used for the id of the connection so it must be unique.
+id                       | The id of this connection.  This needs to be left 
empty or set to a filesystem-friendly value (regex: [A-Za-z0-9_-]+)
 source name              | The name of what of the processor that is the 
source for this connection.
 source relationship name | The name of the processors relationship to route to 
this connection
 destination name         | The name of the component to receive this 
connection.

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaFunction.java
----------------------------------------------------------------------
diff --git 
a/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaFunction.java
 
b/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaFunction.java
index e3b98a7..169d3d1 100644
--- 
a/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaFunction.java
+++ 
b/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaFunction.java
@@ -29,19 +29,21 @@ import java.util.Set;
 import java.util.function.Function;
 
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.CONNECTIONS_KEY;
+import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.ID_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
 
 public class ConnectionSchemaFunction implements Function<ConnectionDTO, 
ConnectionSchema> {
     @Override
     public ConnectionSchema apply(ConnectionDTO connectionDTO) {
         Map<String, Object> map = new HashMap<>();
+        map.put(ID_KEY, connectionDTO.getId());
         map.put(NAME_KEY, connectionDTO.getName());
-        map.put(ConnectionSchema.SOURCE_NAME_KEY, 
connectionDTO.getSource().getName());
+        map.put(ConnectionSchema.SOURCE_ID_KEY, 
connectionDTO.getSource().getId());
         Set<String> selectedRelationships = 
BaseSchema.nullToEmpty(connectionDTO.getSelectedRelationships());
         if (selectedRelationships.size() > 0) {
             map.put(ConnectionSchema.SOURCE_RELATIONSHIP_NAME_KEY, 
selectedRelationships.iterator().next());
         }
-        map.put(ConnectionSchema.DESTINATION_NAME_KEY, 
connectionDTO.getDestination().getName());
+        map.put(ConnectionSchema.DESTINATION_ID_KEY, 
connectionDTO.getDestination().getId());
 
         map.put(ConnectionSchema.MAX_WORK_QUEUE_SIZE_KEY, 
connectionDTO.getBackPressureObjectThreshold());
         map.put(ConnectionSchema.MAX_WORK_QUEUE_DATA_SIZE_KEY, 
connectionDTO.getBackPressureDataSizeThreshold());
@@ -52,7 +54,7 @@ public class ConnectionSchemaFunction implements 
Function<ConnectionDTO, Connect
         }
         ConnectionSchema connectionSchema = new ConnectionSchema(map);
         if 
(ConnectableType.FUNNEL.name().equals(connectionDTO.getSource().getType())) {
-            connectionSchema.validationIssues.add("Connection " + 
connectionDTO.getName() + " has type " + ConnectableType.FUNNEL.name() + " 
which is not supported by MiNiFi");
+            connectionSchema.addValidationIssue("Connection " + 
connectionDTO.getName() + " has type " + ConnectableType.FUNNEL.name() + " 
which is not supported by MiNiFi");
         }
         if (selectedRelationships.size() > 1) {
             
connectionSchema.addValidationIssue(ConnectionSchema.SOURCE_RELATIONSHIP_NAME_KEY,
 CONNECTIONS_KEY, " has more than one selected relationship");

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ProcessorSchemaFunction.java
----------------------------------------------------------------------
diff --git 
a/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ProcessorSchemaFunction.java
 
b/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ProcessorSchemaFunction.java
index f0a7be9..dde36bc 100644
--- 
a/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ProcessorSchemaFunction.java
+++ 
b/minifi-toolkit/minifi-toolkit-configuration/src/main/java/org/apache/nifi/minifi/toolkit/configuration/dto/ProcessorSchemaFunction.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.ID_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.NAME_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.SCHEDULING_PERIOD_KEY;
 import static 
org.apache.nifi.minifi.commons.schema.common.CommonPropertyKeys.SCHEDULING_STRATEGY_KEY;
@@ -40,6 +41,7 @@ public class ProcessorSchemaFunction implements 
Function<ProcessorDTO, Processor
 
         Map<String, Object> map = new HashMap<>();
         map.put(NAME_KEY, processorDTO.getName());
+        map.put(ID_KEY, processorDTO.getId());
         map.put(ProcessorSchema.CLASS_KEY, processorDTO.getType());
         map.put(SCHEDULING_STRATEGY_KEY, 
processorDTOConfig.getSchedulingStrategy());
         map.put(SCHEDULING_PERIOD_KEY, 
processorDTOConfig.getSchedulingPeriod());

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/BaseSchemaTester.java
----------------------------------------------------------------------
diff --git 
a/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/BaseSchemaTester.java
 
b/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/BaseSchemaTester.java
index 4dcea13..a8c1908 100644
--- 
a/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/BaseSchemaTester.java
+++ 
b/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/BaseSchemaTester.java
@@ -40,10 +40,10 @@ public abstract class BaseSchemaTester<Schema extends 
BaseSchema, DTO> {
         Schema dtoSchema = dtoSchemaFunction.apply(dto);
         Schema mapSchema = mapSchemaFunction.apply(map);
         assertSchemaEquals(dtoSchema, mapSchema);
-        assertEquals(dtoSchema.validationIssues, mapSchema.validationIssues);
+        assertEquals(dtoSchema.getValidationIssues(), 
mapSchema.getValidationIssues());
         assertSchemaEquals(dtoSchema, 
mapSchemaFunction.apply(dtoSchema.toMap()));
         assertSchemaEquals(mapSchema, 
mapSchemaFunction.apply(mapSchema.toMap()));
-        assertEquals(validationErrors, dtoSchema.validationIssues.size());
+        assertEquals(validationErrors, dtoSchema.getValidationIssues().size());
     }
 
     public abstract void assertSchemaEquals(Schema one, Schema two);

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/93af87dd/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaTest.java
----------------------------------------------------------------------
diff --git 
a/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaTest.java
 
b/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaTest.java
index ebd4b2f..d8e8e48 100644
--- 
a/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaTest.java
+++ 
b/minifi-toolkit/minifi-toolkit-configuration/src/test/java/org/apache/nifi/minifi/toolkit/configuration/dto/ConnectionSchemaTest.java
@@ -33,14 +33,15 @@ import java.util.stream.Collectors;
 import static org.junit.Assert.assertEquals;
 
 public class ConnectionSchemaTest extends BaseSchemaTester<ConnectionSchema, 
ConnectionDTO> {
-    private String testName = "testName";
-    private String testSourceName = "testSourceName";
-    private String testSelectedRelationship = "testSelectedRelationship";
-    private String testDestinationName = "testDestinationName";
-    private long testMaxWorkQueueSize = 101L;
-    private String testMaxWorkQueueDataSize = "120 GB";
-    private String testFlowfileExpiration = "1 day";
-    private String testQueuePrioritizerClass = "testQueuePrioritizerClass";
+    private final String testId = "testId";
+    private final String testName = "testName";
+    private final String testSourceId = "testSourceId";
+    private final String testSelectedRelationship = "testSelectedRelationship";
+    private final String testDestinationId = "testDestinationId";
+    private final long testMaxWorkQueueSize = 101L;
+    private final String testMaxWorkQueueDataSize = "120 GB";
+    private final String testFlowfileExpiration = "1 day";
+    private final String testQueuePrioritizerClass = 
"testQueuePrioritizerClass";
 
     public ConnectionSchemaTest() {
         super(new ConnectionSchemaFunction(), ConnectionSchema::new);
@@ -49,12 +50,13 @@ public class ConnectionSchemaTest extends 
BaseSchemaTester<ConnectionSchema, Con
     @Before
     public void setup() {
         ConnectableDTO source = new ConnectableDTO();
-        source.setName(testSourceName);
+        source.setId(testSourceId);
 
         ConnectableDTO destination = new ConnectableDTO();
-        destination.setName(testDestinationName);
+        destination.setId(testDestinationId);
 
         dto = new ConnectionDTO();
+        dto.setId(testId);
         dto.setName(testName);
         dto.setSource(source);
         
dto.setSelectedRelationships(Arrays.asList(testSelectedRelationship).stream().collect(Collectors.toSet()));
@@ -65,10 +67,11 @@ public class ConnectionSchemaTest extends 
BaseSchemaTester<ConnectionSchema, Con
         dto.setPrioritizers(Arrays.asList(testQueuePrioritizerClass));
 
         map = new HashMap<>();
+        map.put(CommonPropertyKeys.ID_KEY, testId);
         map.put(CommonPropertyKeys.NAME_KEY, testName);
-        map.put(ConnectionSchema.SOURCE_NAME_KEY, testSourceName);
+        map.put(ConnectionSchema.SOURCE_ID_KEY, testSourceId);
         map.put(ConnectionSchema.SOURCE_RELATIONSHIP_NAME_KEY, 
testSelectedRelationship);
-        map.put(ConnectionSchema.DESTINATION_NAME_KEY, testDestinationName);
+        map.put(ConnectionSchema.DESTINATION_ID_KEY, testDestinationId);
         map.put(ConnectionSchema.MAX_WORK_QUEUE_SIZE_KEY, 
testMaxWorkQueueSize);
         map.put(ConnectionSchema.MAX_WORK_QUEUE_DATA_SIZE_KEY, 
testMaxWorkQueueDataSize);
         map.put(ConnectionSchema.FLOWFILE_EXPIRATION__KEY, 
testFlowfileExpiration);
@@ -79,20 +82,27 @@ public class ConnectionSchemaTest extends 
BaseSchemaTester<ConnectionSchema, Con
     public void testNoName() {
         dto.setName(null);
         map.remove(CommonPropertyKeys.NAME_KEY);
+        assertDtoAndMapConstructorAreSame(0);
+    }
+
+    @Test
+    public void testNoId() {
+        dto.setId(null);
+        map.remove(CommonPropertyKeys.ID_KEY);
         assertDtoAndMapConstructorAreSame(1);
     }
 
     @Test
-    public void testNoSourceName() {
+    public void testNoSourceId() {
         dto.setSource(new ConnectableDTO());
-        map.remove(ConnectionSchema.SOURCE_NAME_KEY);
-        assertDtoAndMapConstructorAreSame(1);
+        map.remove(ConnectionSchema.SOURCE_ID_KEY);
+        assertDtoAndMapConstructorAreSame(2);
     }
 
     @Test
     public void testDtoMultipleSourceRelationships() {
         dto.setSelectedRelationships(Arrays.asList("one", 
"two").stream().collect(Collectors.toSet()));
-        assertEquals(1, dtoSchemaFunction.apply(dto).validationIssues.size());
+        assertEquals(1, 
dtoSchemaFunction.apply(dto).getValidationIssues().size());
     }
 
     @Test
@@ -108,8 +118,8 @@ public class ConnectionSchemaTest extends 
BaseSchemaTester<ConnectionSchema, Con
     @Test
     public void testNoDestinationName() {
         dto.setDestination(new ConnectableDTO());
-        map.remove(ConnectionSchema.DESTINATION_NAME_KEY);
-        assertDtoAndMapConstructorAreSame(1);
+        map.remove(ConnectionSchema.DESTINATION_ID_KEY);
+        assertDtoAndMapConstructorAreSame(2);
     }
 
     @Test
@@ -145,15 +155,16 @@ public class ConnectionSchemaTest extends 
BaseSchemaTester<ConnectionSchema, Con
     @Test
     public void testFunnelValidationMessage() {
         dto.getSource().setType(ConnectableType.FUNNEL.name());
-        assertEquals(1, dtoSchemaFunction.apply(dto).validationIssues.size());
+        assertEquals(1, 
dtoSchemaFunction.apply(dto).getValidationIssues().size());
     }
 
     @Override
     public void assertSchemaEquals(ConnectionSchema one, ConnectionSchema two) 
{
         assertEquals(one.getName(), two.getName());
-        assertEquals(one.getSourceName(), two.getSourceName());
+        assertEquals(one.getId(), two.getId());
+        assertEquals(one.getSourceId(), two.getSourceId());
         assertEquals(one.getSourceRelationshipName(), 
two.getSourceRelationshipName());
-        assertEquals(one.getDestinationName(), two.getDestinationName());
+        assertEquals(one.getDestinationId(), two.getDestinationId());
         assertEquals(one.getMaxWorkQueueSize(), two.getMaxWorkQueueSize());
         assertEquals(one.getMaxWorkQueueDataSize(), 
two.getMaxWorkQueueDataSize());
         assertEquals(one.getFlowfileExpiration(), two.getFlowfileExpiration());

Reply via email to