You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/04/02 18:42:12 UTC

[3/3] nifi-minifi git commit: MINIFI-448 Adds C2 Server service layer

MINIFI-448 Adds C2 Server service layer

The C2 service layer is the part of the C2 framework that backs the
REST API to provide the business logic.

This commit also defines a few peliminary provider interfaces for
persistence based on what is required by the service layer. A
reference, in-memory implementation of the provider interfaces is
included.


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

Branch: refs/heads/minifi-c2-server
Commit: 11bb8dbe72e4d7ee1491b4f44d571c0f52493969
Parents: 7ab8fe7
Author: Kevin Doran <kd...@apache.org>
Authored: Fri Mar 16 15:28:59 2018 -0400
Committer: Kevin Doran <kd...@apache.org>
Committed: Mon Apr 2 14:41:55 2018 -0400

----------------------------------------------------------------------
 minifi-c2/minifi-c2-assembly/pom.xml            |  22 +-
 .../org/apache/nifi/minifi/c2/model/Agent.java  |  54 ++
 .../apache/nifi/minifi/c2/model/AgentClass.java |  15 +
 .../apache/nifi/minifi/c2/model/AgentInfo.java  |  16 +-
 .../nifi/minifi/c2/model/AgentManifest.java     |  10 +
 .../apache/nifi/minifi/c2/model/BuildInfo.java  |  12 +
 .../nifi/minifi/c2/model/C2Heartbeat.java       |  57 +-
 .../nifi/minifi/c2/model/C2Operation.java       |   2 +-
 .../org/apache/nifi/minifi/c2/model/Device.java |   4 +-
 .../apache/nifi/minifi/c2/model/DeviceInfo.java |  15 +-
 .../nifi/minifi/c2/model/NetworkInfo.java       |   8 +
 .../nifi/minifi/c2/model/OperationRequest.java  |  19 +
 .../nifi/minifi/c2/model/OperationState.java    |  40 ++
 .../apache/nifi/minifi/c2/model/SystemInfo.java |   8 +
 .../apache/nifi/minifi/c2/model/TestObject.java |  51 --
 .../c2/model/extension/ControllerService.java   |   2 +-
 .../minifi/c2/model/extension/Processor.java    |   2 +-
 minifi-c2/minifi-c2-framework/pom.xml           |  74 +-
 .../c2/api/provider/PersistenceProvider.java    |  98 +++
 .../agent/AgentClassPersistenceProvider.java    |  27 +
 .../agent/AgentManifestPersistenceProvider.java |  37 +
 .../agent/AgentPersistenceProvider.java         |  36 +
 .../device/DevicePersistenceProvider.java       |  31 +
 .../heartbeat/HeartbeatPersistenceProvider.java |  40 ++
 .../OperationPersistenceProvider.java           |  38 ++
 .../exception/ResourceNotFoundException.java    |  30 +
 .../c2/core/persistence/C2Repository.java       |  35 -
 .../core/persistence/VolatileC2Repository.java  |  69 --
 .../c2/core/provider/ProviderFactory.java       |  29 +
 .../core/provider/ProviderFactoryException.java |  36 +
 .../StandardProviderConfigurationContext.java   |  39 ++
 .../VolatileAgentClassPersistenceProvider.java  | 108 +++
 ...olatileAgentManifestPersistenceProvider.java | 119 ++++
 .../VolatileAgentPersistenceProvider.java       | 104 +++
 .../VolatileDevicePersistenceProvider.java      | 102 +++
 .../VolatileHeartbeatPersistenceProvider.java   | 121 ++++
 .../VolatileOperationPersistenceProvider.java   | 114 ++++
 .../VolatilePersistenceProviderFactory.java     |  80 +++
 .../c2/core/service/C2ProtocolService.java      |  27 +
 .../nifi/minifi/c2/core/service/C2Service.java  | 113 +++-
 .../core/service/StandardC2ProtocolService.java | 243 +++++++
 .../c2/core/service/StandardC2Service.java      | 477 +++++++++++++
 .../StandardC2ProtocolServiceSpec.groovy        | 164 +++++
 .../core/service/StandardC2ServiceSpec.groovy   | 677 +++++++++++++++++++
 minifi-c2/minifi-c2-jetty/pom.xml               |   1 +
 minifi-c2/minifi-c2-provider-api/pom.xml        |  30 +
 .../nifi/minifi/c2/api/provider/Provider.java   |  47 ++
 .../provider/ProviderConfigurationContext.java  |  31 +
 .../api/provider/ProviderCreationException.java |  37 +
 minifi-c2/minifi-c2-web-api/pom.xml             |  12 +
 .../minifi/c2/web/MinifiC2ResourceConfig.java   |   4 +-
 .../minifi/c2/web/api/AgentClassResource.java   |  75 +-
 .../c2/web/api/AgentManifestResource.java       |  47 +-
 .../minifi/c2/web/api/ApplicationResource.java  | 122 +++-
 .../minifi/c2/web/api/C2ProtocolResource.java   |  26 +-
 .../nifi/minifi/c2/web/api/TestResource.java    | 150 ----
 .../web/mapper/BadRequestExceptionMapper.java   |  47 ++
 .../ConstraintViolationExceptionMapper.java     |  68 ++
 .../mapper/IllegalArgumentExceptionMapper.java  |  47 ++
 .../web/mapper/IllegalStateExceptionMapper.java |  44 ++
 .../mapper/ResourceNotFoundExceptionMapper.java |  45 ++
 minifi-c2/pom.xml                               |  21 +-
 pom.xml                                         |   5 +
 63 files changed, 3810 insertions(+), 454 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-assembly/pom.xml
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-assembly/pom.xml b/minifi-c2/minifi-c2-assembly/pom.xml
index 02f07f0..3b39be1 100644
--- a/minifi-c2/minifi-c2-assembly/pom.xml
+++ b/minifi-c2/minifi-c2-assembly/pom.xml
@@ -81,6 +81,22 @@ limitations under the License.
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-c2-commons</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-c2-provider-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-c2-web-api</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+        </dependency>
+        <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <scope>compile</scope>
@@ -130,11 +146,5 @@ limitations under the License.
             <artifactId>jetty-util</artifactId>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.nifi.minifi</groupId>
-            <artifactId>minifi-c2-web-api</artifactId>
-            <version>${project.version}</version>
-            <type>war</type>
-        </dependency>
     </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Agent.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Agent.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Agent.java
new file mode 100644
index 0000000..eae472c
--- /dev/null
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Agent.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.model;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class Agent extends AgentInfo {
+
+    private String name;
+    private Long firstSeen;
+    private Long lastSeen;
+
+    @ApiModelProperty
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the first time the agent was seen by this C2 server")
+    public Long getFirstSeen() {
+        return firstSeen;
+    }
+
+    public void setFirstSeen(Long firstSeen) {
+        this.firstSeen = firstSeen;
+    }
+
+    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the most recent time the was seen by this C2 server")
+    public Long getLastSeen() {
+        return lastSeen;
+    }
+
+    public void setLastSeen(Long lastSeen) {
+        this.lastSeen = lastSeen;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentClass.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentClass.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentClass.java
index 2567b69..013d420 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentClass.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentClass.java
@@ -17,12 +17,17 @@ package org.apache.nifi.minifi.c2.model;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import javax.validation.constraints.NotBlank;
+import java.util.Set;
+
 @ApiModel
 public class AgentClass {
 
+    @NotBlank
     private String name;
     private String description;
     private FlowUri trackedFlow;
+    private Set<String> agentManifests;
 
     @ApiModelProperty(value = "A unique class name for the agent", required = true)
     public String getName() {
@@ -50,4 +55,14 @@ public class AgentClass {
     public void setTrackedFlow(FlowUri trackedFlow) {
         this.trackedFlow = trackedFlow;
     }
+
+    @ApiModelProperty("A list of agent manifest ids belonging to this class")
+    public Set<String> getAgentManifests() {
+        return agentManifests;
+    }
+
+    public void setAgentManifests(Set<String> agentManifests) {
+        this.agentManifests = agentManifests;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentInfo.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentInfo.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentInfo.java
index 3e4c7ac..86f7599 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentInfo.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentInfo.java
@@ -17,9 +17,12 @@ package org.apache.nifi.minifi.c2.model;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import javax.validation.constraints.NotBlank;
+
 @ApiModel
 public class AgentInfo {
 
+    @NotBlank
     private String identifier;
     // TODO, do we also need identity. e.g., cert DN
     private String agentClass;
@@ -29,7 +32,8 @@ public class AgentInfo {
 
     @ApiModelProperty(
             value = "A unique identifier for the Agent",
-            notes = "Usually set when the agent is provisioned and deployed")
+            notes = "Usually set when the agent is provisioned and deployed",
+            required = true)
     public String getIdentifier() {
         return identifier;
     }
@@ -66,4 +70,14 @@ public class AgentInfo {
     public void setStatus(AgentStatus status) {
         this.status = status;
     }
+
+    @Override
+    public String toString() {
+        return "AgentInfo{" +
+                "identifier='" + identifier + '\'' +
+                ", agentClass='" + agentClass + '\'' +
+                ", agentManifest=" + agentManifest +
+                ", status=" + status +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentManifest.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentManifest.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentManifest.java
index d5a53d2..ba93667 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentManifest.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/AgentManifest.java
@@ -113,4 +113,14 @@ public class AgentManifest {
                 .append(identifier)
                 .toHashCode();
     }
+
+    @Override
+    public String toString() {
+        return "AgentManifest{" +
+                "identifier='" + identifier + '\'' +
+                ", agentType='" + agentType + '\'' +
+                ", version='" + version + '\'' +
+                ", buildInfo=" + buildInfo +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/BuildInfo.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/BuildInfo.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/BuildInfo.java
index 3f3876b..28a4e7e 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/BuildInfo.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/BuildInfo.java
@@ -80,4 +80,16 @@ public class BuildInfo {
     public void setCompilerFlags(String compilerFlags) {
         this.compilerFlags = compilerFlags;
     }
+
+    @Override
+    public String toString() {
+        return "BuildInfo{" +
+                "version='" + version + '\'' +
+                ", revision='" + revision + '\'' +
+                ", timestamp=" + timestamp +
+                ", targetArch='" + targetArch + '\'' +
+                ", compiler='" + compiler + '\'' +
+                ", compilerFlags='" + compilerFlags + '\'' +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Heartbeat.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Heartbeat.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Heartbeat.java
index b68680f..a39917a 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Heartbeat.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Heartbeat.java
@@ -16,15 +16,41 @@ package org.apache.nifi.minifi.c2.model;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 
+/**
+ * TODO add Builder interface
+ */
 @ApiModel
 public class C2Heartbeat {
 
-    // TODO, timestamp?
+    // Internal, not part of REST API
+    private String identifier;
+    private long timestamp;
+
     private DeviceInfo deviceInfo;
     private AgentInfo agentInfo;
     private FlowInfo flowInfo;
 
+    @ApiModelProperty(hidden = true)
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    @ApiModelProperty(hidden = true)
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
     @ApiModelProperty("Metadata for the device")
     public DeviceInfo getDeviceInfo() {
         return deviceInfo;
@@ -52,4 +78,33 @@ public class C2Heartbeat {
         this.flowInfo = flowInfo;
     }
 
+    @Override
+    public String toString() {
+        return "C2Heartbeat{" +
+                "identifier='" + identifier + '\'' +
+                ", timestamp=" + timestamp +
+                ", agent=" + (agentInfo != null ? agentInfo.getIdentifier() : "null") +
+                ", device=" + (deviceInfo != null ? deviceInfo.getIdentifier() : "null") +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || getClass() != o.getClass()) return false;
+
+        C2Heartbeat that = (C2Heartbeat) o;
+
+        return new EqualsBuilder()
+                .append(identifier, that.identifier)
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37)
+                .append(identifier)
+                .toHashCode();
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Operation.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Operation.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Operation.java
index 047ebc6..72f8215 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Operation.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/C2Operation.java
@@ -30,7 +30,6 @@ public class C2Operation {
     private OperationType operation;
     private String operand;
     private Map<String, String> args;
-
     private List<String> dependencies;
 
     @ApiModelProperty(
@@ -95,4 +94,5 @@ public class C2Operation {
     public void setDependencies(List<String> dependencies) {
         this.dependencies = dependencies;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Device.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Device.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Device.java
index 0505856..c7f6cdf 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Device.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/Device.java
@@ -33,7 +33,7 @@ public class Device extends DeviceInfo {
         this.name = name;
     }
 
-    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the first time a device was seen by this C2 server")
+    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the first time the device was seen by this C2 server")
     public Long getFirstSeen() {
         return firstSeen;
     }
@@ -42,7 +42,7 @@ public class Device extends DeviceInfo {
         this.firstSeen = firstSeen;
     }
 
-    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the most recent time time a device was seen by this C2 server")
+    @ApiModelProperty("A timestamp (milliseconds since Epoch) for the most recent time the device was seen by this C2 server")
     public Long getLastSeen() {
         return lastSeen;
     }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/DeviceInfo.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/DeviceInfo.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/DeviceInfo.java
index a72a347..0068e89 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/DeviceInfo.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/DeviceInfo.java
@@ -17,14 +17,19 @@ package org.apache.nifi.minifi.c2.model;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import javax.validation.constraints.NotBlank;
+
 @ApiModel
 public class DeviceInfo {
 
+    @NotBlank
     private String identifier;
     private SystemInfo systemInfo;
     private NetworkInfo networkInfo;
 
-    @ApiModelProperty("A unique, long-lived identifier for the MiNiFi-enabled device")
+    @ApiModelProperty(
+            value = "A unique, long-lived identifier for the MiNiFi-enabled device",
+            required = true)
     public String getIdentifier() {
         return identifier;
     }
@@ -51,4 +56,12 @@ public class DeviceInfo {
         this.networkInfo = networkInfo;
     }
 
+    @Override
+    public String toString() {
+        return "DeviceInfo{" +
+                "identifier='" + identifier + '\'' +
+                ", systemInfo=" + systemInfo +
+                ", networkInfo=" + networkInfo +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/NetworkInfo.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/NetworkInfo.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/NetworkInfo.java
index 8a77124..129d342 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/NetworkInfo.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/NetworkInfo.java
@@ -51,4 +51,12 @@ public class NetworkInfo {
         this.ipAddress = ipAddress;
     }
 
+    @Override
+    public String toString() {
+        return "NetworkInfo{" +
+                "deviceId='" + deviceId + '\'' +
+                ", hostname='" + hostname + '\'' +
+                ", ipAddress='" + ipAddress + '\'' +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationRequest.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationRequest.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationRequest.java
index f433831..2fa17aa 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationRequest.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationRequest.java
@@ -17,11 +17,19 @@ package org.apache.nifi.minifi.c2.model;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
 @ApiModel
 public class OperationRequest {
 
+    @NotNull
     private C2Operation operation;
+
+    @NotBlank
     private String targetAgentIdentifier;
+
+    private OperationState state = OperationState.NEW;
     private String operatorIdentity;
     private String operatorName;
 
@@ -43,6 +51,17 @@ public class OperationRequest {
         this.targetAgentIdentifier = targetAgentIdentifier;
     }
 
+    @ApiModelProperty(
+            value = "The current state of the operation",
+            readOnly = true)
+    public OperationState getState() {
+        return state;
+    }
+
+    public void setState(OperationState state) {
+        this.state = state;
+    }
+
     @ApiModelProperty(value = "The verified identity of the C2 server client that created the operation",
             readOnly = true,
             notes = "This field is set by the server when an operation request is submitted to identify the origin. " +

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationState.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationState.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationState.java
new file mode 100644
index 0000000..6f74d0d
--- /dev/null
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/OperationState.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.model;
+
+public enum OperationState {
+
+    /** Operation has been created (assigned an ID), but has not been scheduled for deployment. Newly created operations default to this state.  */
+    NEW,
+
+    /** Operator (human or automated) has submitted the operation to be deployed to the target at some point in the future */
+    READY,
+
+    /** Operation is marked to be sent to the target at the next available opportunity (i.e., in the next heartbeat response) */
+    QUEUED,
+
+    /** Operation has been conveyed to the the target agent. It is in progress and the C2 Server is waiting for the result */
+    DEPLOYED,
+
+    /** The target was able to execute the operation successfully */
+    DONE,
+
+    /** The target was unable to execute the operation successfully */
+    FAILED,
+
+    /** Operation was cancelled by an operator */
+    CANCELLED
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/SystemInfo.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/SystemInfo.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/SystemInfo.java
index 3478d1d..20fcd38 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/SystemInfo.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/SystemInfo.java
@@ -64,4 +64,12 @@ public class SystemInfo {
         this.vCores = vCores;
     }
 
+    @Override
+    public String toString() {
+        return "SystemInfo{" +
+                "machineArch='" + machineArch + '\'' +
+                ", physicalMem=" + physicalMem +
+                ", vCores=" + vCores +
+                '}';
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/TestObject.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/TestObject.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/TestObject.java
deleted file mode 100644
index 8c2284c..0000000
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/TestObject.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.minifi.c2.model;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-import javax.validation.constraints.NotBlank;
-
-@ApiModel
-public class TestObject {
-
-    @NotBlank
-    private String identifier;
-
-    @NotBlank
-    private String name;
-
-    @ApiModelProperty(value = "A unique identifier for this object generated by the server at creation time.", readOnly = true)
-    public String getIdentifier() {
-        return identifier;
-    }
-
-    public void setIdentifier(String identifier) {
-        this.identifier = identifier;
-    }
-
-    @ApiModelProperty(value = "The name of the object.", required = true)
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/ControllerService.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/ControllerService.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/ControllerService.java
index c785e52..33ff909 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/ControllerService.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/ControllerService.java
@@ -20,7 +20,7 @@ import io.swagger.annotations.ApiModelProperty;
 import java.util.Collections;
 import java.util.Map;
 
-@ApiModel  // TODO Swagger docs description
+@ApiModel
 public class ControllerService extends ExtensionComponent {
 
     private Map<String, PropertyDescriptor> propertyDescriptors;

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/Processor.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/Processor.java b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/Processor.java
index 3c2856f..a5dcdac 100644
--- a/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/Processor.java
+++ b/minifi-c2/minifi-c2-commons/src/main/java/org/apache/nifi/minifi/c2/model/extension/Processor.java
@@ -21,7 +21,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-@ApiModel  // TODO Swagger docs description
+@ApiModel
 public class Processor extends ExtensionComponent {
 
     private Map<String, PropertyDescriptor> propertyDescriptors;

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/pom.xml
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/pom.xml b/minifi-c2/minifi-c2-framework/pom.xml
index f0d7198..febcb3d 100644
--- a/minifi-c2/minifi-c2-framework/pom.xml
+++ b/minifi-c2/minifi-c2-framework/pom.xml
@@ -42,6 +42,13 @@ limitations under the License.
             <groupId>org.apache.nifi.minifi</groupId>
             <artifactId>minifi-c2-commons</artifactId>
             <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.minifi</groupId>
+            <artifactId>minifi-c2-provider-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi.registry</groupId>
@@ -61,55 +68,26 @@ limitations under the License.
         </dependency>
 
         <dependency>
-            <groupId>org.bouncycastle</groupId>
-            <artifactId>bcprov-jdk15on</artifactId>
-            <version>1.55</version>
-        </dependency>
-
-        <dependency>
             <groupId>org.flywaydb</groupId>
             <artifactId>flyway-core</artifactId>
             <version>${flyway.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>javax.el</artifactId>
+        </dependency>
+
         <!-- Spring dependencies -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-security</artifactId>
             <version>${spring.boot.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-ldap</artifactId>
-            <version>${spring.security.ldap.version}</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.springframework.security</groupId>
-                    <artifactId>spring-security-core</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.springframework</groupId>
-                    <artifactId>spring-beans</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.springframework</groupId>
-                    <artifactId>spring-context</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.springframework</groupId>
-                    <artifactId>spring-core</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.springframework</groupId>
-                    <artifactId>spring-tx</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>commons-logging</groupId>
-                    <artifactId>commons-logging</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-
         <!-- We're using spring-boot-starter-data-jpa to bring in spring-data-jdbc and a few other dependencies, but
             we aren't actually using any JPA dependencies, this also brings in the standard Spring dependencies for the backend -->
         <dependency>
@@ -136,5 +114,25 @@ limitations under the License.
             </exclusions>
         </dependency>
 
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <version>1.0-groovy-2.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-all</artifactId>
+            <version>2.4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <version>2.2.2</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/PersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/PersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/PersistenceProvider.java
new file mode 100644
index 0000000..87d2717
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/PersistenceProvider.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.api.provider;
+
+import java.util.Optional;
+
+/**
+ * A generic interface for an object persistence provider.
+ *
+ * This interface design is derived from org.springframework.data.repository.CrudRepository (ALv2).
+ *
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ *
+ * @param <T>
+ */
+public interface PersistenceProvider<T, ID> {
+
+    /**
+     * Returns the number of saved entities.
+     *
+     * @return the number of saved entities
+     */
+    long getCount();
+
+    /**
+     * Saves a given entity. Use the returned instance as the save operation might have side-effects.
+     *
+     * @param t must not be null
+     * @return the saved agentClass
+     * @throws IllegalArgumentException if agentClass is null
+     */
+    T save(T t);
+
+    /**
+     * Retrieves all saved entities.
+     *
+     * TODO: Modify this interface to support pagination and sorting
+     *
+     * @return a List of all saved entities, or an empty List if there are no saved entities
+     */
+    Iterable<T> getAll();
+
+    /**
+     * Checks existence an entity by id.
+     *
+     * @param id must not be null
+     * @return true if an entity with the given id exists; otherwise, false
+     * @throws IllegalArgumentException if name is null
+     */
+    boolean existsById(ID id);
+
+    /**
+     * Retrieves an entity by id.
+     *
+     * @param id must not be null
+     * @return the entity with the specified id (or empty optional)
+     * @throws IllegalArgumentException if name is null
+     */
+    Optional<T> getById(ID id);
+
+
+    /**
+     * Delete an entity by id.
+     *
+     * @param id must not be null
+     * @throws IllegalArgumentException if name is null
+     */
+    void deleteById(ID id);
+
+    /**
+     * Delete an entity.
+     *
+     * @param t must not be null
+     * @throws IllegalArgumentException if name is null
+     */
+    void delete(T t);
+
+    /**
+     * Delete all entities.
+     */
+    void deleteAll();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentClassPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentClassPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentClassPersistenceProvider.java
new file mode 100644
index 0000000..520cfdb
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentClassPersistenceProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.c2.api.provider.agent;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.model.AgentClass;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface AgentClassPersistenceProvider extends PersistenceProvider<AgentClass, String> {
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentManifestPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentManifestPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentManifestPersistenceProvider.java
new file mode 100644
index 0000000..a2dceff
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentManifestPersistenceProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.api.provider.agent;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.model.AgentManifest;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface AgentManifestPersistenceProvider extends PersistenceProvider<AgentManifest, String> {
+
+    /**
+     * Returns all agent manifests that match the IDs.
+     *
+     * @param agentManifestIds ids of the manifests to retrieve
+     * @return a list of manifests matching the specified ids.
+     *         If an id does not result in a match, nothing will be included for that id.
+     */
+    Iterable<AgentManifest> getAllById(Iterable<String> agentManifestIds);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentPersistenceProvider.java
new file mode 100644
index 0000000..33e1e7a
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/agent/AgentPersistenceProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.api.provider.agent;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.model.Agent;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface AgentPersistenceProvider extends PersistenceProvider<Agent, String> {
+
+    /**
+     * Returns all agents of the specified agent class
+     *
+     * @param agentClassName the name of the class to match
+     * @return agents matching the specified agent class name
+     */
+    Iterable<Agent> getByClassName(String agentClassName);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/device/DevicePersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/device/DevicePersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/device/DevicePersistenceProvider.java
new file mode 100644
index 0000000..6e273ee
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/device/DevicePersistenceProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.api.provider.device;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.api.provider.Provider;
+import org.apache.nifi.minifi.c2.model.Device;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface DevicePersistenceProvider extends PersistenceProvider<Device, String> {
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/heartbeat/HeartbeatPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/heartbeat/HeartbeatPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/heartbeat/HeartbeatPersistenceProvider.java
new file mode 100644
index 0000000..df5b003
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/heartbeat/HeartbeatPersistenceProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.api.provider.heartbeat;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.api.provider.Provider;
+import org.apache.nifi.minifi.c2.model.C2Heartbeat;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface HeartbeatPersistenceProvider extends PersistenceProvider<C2Heartbeat, String> {
+
+    /**
+     * Returns all heartbeats from the specified agent
+     *
+     * @param agentId the id of the agent to match
+     * @return heartbeats matching the specified agent id
+     */
+    Iterable<C2Heartbeat> getByAgent(String agentId);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/operations/OperationPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/operations/OperationPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/operations/OperationPersistenceProvider.java
new file mode 100644
index 0000000..cd86e24
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/api/provider/operations/OperationPersistenceProvider.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.minifi.c2.api.provider.operations;
+
+import org.apache.nifi.minifi.c2.api.provider.PersistenceProvider;
+import org.apache.nifi.minifi.c2.model.OperationRequest;
+
+import java.util.List;
+
+/**
+ * NOTE: Although this interface is intended to be an extension point, it is not yet considered stable and thus may
+ * change across releases until the the C2 Server APIs mature.
+ */
+public interface OperationPersistenceProvider extends PersistenceProvider<OperationRequest, String> {
+
+    /**
+     * Returns all operations targeting a given agent
+     *
+     * @param agentId the agent to match
+     * @return operations targeting the specified agent id
+     */
+    Iterable<OperationRequest> getByAgent(String agentId);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/exception/ResourceNotFoundException.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/exception/ResourceNotFoundException.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/exception/ResourceNotFoundException.java
new file mode 100644
index 0000000..fbe449c
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/exception/ResourceNotFoundException.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.minifi.c2.core.exception;
+
+/**
+ * An exception that is thrown when an entity is not found.
+ */
+public class ResourceNotFoundException extends RuntimeException {
+
+    public ResourceNotFoundException(String message) {
+        super(message);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/C2Repository.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/C2Repository.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/C2Repository.java
deleted file mode 100644
index 1495190..0000000
--- a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/C2Repository.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.minifi.c2.core.persistence;
-
-import org.apache.nifi.minifi.c2.model.TestObject;
-
-import java.util.Iterator;
-
-public interface C2Repository {
-
-    TestObject createTestObject(TestObject testObject);
-
-    Iterator<TestObject> getTestObjects();
-
-    TestObject getTestObjectById(String identifier);
-
-    TestObject updateTestObject(TestObject testObject);
-
-    TestObject deleteTestObject(String identifier);
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/VolatileC2Repository.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/VolatileC2Repository.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/VolatileC2Repository.java
deleted file mode 100644
index fec3344..0000000
--- a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/persistence/VolatileC2Repository.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.minifi.c2.core.persistence;
-
-import org.apache.nifi.minifi.c2.model.TestObject;
-import org.springframework.stereotype.Repository;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.UUID;
-
-@Repository
-public class VolatileC2Repository implements C2Repository {
-
-    private Map<String, TestObject> testObjectMap;
-
-    public VolatileC2Repository() {
-        this.testObjectMap = new HashMap<>();
-    }
-
-    @Override
-    public TestObject createTestObject(final TestObject testObject) {
-
-        final String id = UUID.randomUUID().toString();
-        TestObject createdTestObject = new TestObject();
-        createdTestObject.setIdentifier(id);
-        createdTestObject.setName(testObject.getName());
-        testObjectMap.put(id, createdTestObject);
-        return createdTestObject;
-
-    }
-
-    @Override
-    public Iterator<TestObject> getTestObjects() {
-        return testObjectMap.values().iterator();
-    }
-
-    @Override
-    public TestObject getTestObjectById(String identifier) {
-        return testObjectMap.get(identifier);
-    }
-
-    @Override
-    public TestObject updateTestObject(TestObject testObject) {
-        TestObject updatedTestObject = testObjectMap.get(testObject.getIdentifier());
-        updatedTestObject.setName(testObject.getName());
-        return updatedTestObject;
-    }
-
-    @Override
-    public TestObject deleteTestObject(String identifier) {
-        return testObjectMap.remove(identifier);
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactory.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactory.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactory.java
new file mode 100644
index 0000000..cb076d0
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.core.provider;
+
+/**
+ * A factory for obtaining the configured providers.
+ */
+public interface ProviderFactory {
+
+    /**
+     * Initialize the factory.
+     *
+     * @throws ProviderFactoryException if an error occurs during initialization
+     */
+    void initialize() throws ProviderFactoryException;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactoryException.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactoryException.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactoryException.java
new file mode 100644
index 0000000..8a06e79
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/ProviderFactoryException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.core.provider;
+
+/**
+ * An error that occurs while initializing a ProviderFactory.
+ */
+public class ProviderFactoryException extends RuntimeException {
+
+    public ProviderFactoryException() {
+    }
+
+    public ProviderFactoryException(String message) {
+        super(message);
+    }
+
+    public ProviderFactoryException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ProviderFactoryException(Throwable cause) {
+        super(cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/StandardProviderConfigurationContext.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/StandardProviderConfigurationContext.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/StandardProviderConfigurationContext.java
new file mode 100644
index 0000000..6ca1fa4
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/StandardProviderConfigurationContext.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.minifi.c2.core.provider;
+
+import org.apache.nifi.minifi.c2.api.provider.ProviderConfigurationContext;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Standard configuration context to be passed to onConfigured method of Providers.
+ */
+public class StandardProviderConfigurationContext implements ProviderConfigurationContext {
+
+    private final Map<String,String> properties;
+
+    public StandardProviderConfigurationContext(final Map<String, String> properties) {
+        this.properties = Collections.unmodifiableMap(new HashMap<>(properties));
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentClassPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentClassPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentClassPersistenceProvider.java
new file mode 100644
index 0000000..1b202ff
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentClassPersistenceProvider.java
@@ -0,0 +1,108 @@
+/*
+ * 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.c2.core.provider.persistence;
+
+import org.apache.nifi.minifi.c2.api.provider.agent.AgentClassPersistenceProvider;
+import org.apache.nifi.minifi.c2.model.AgentClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A simple, in-memory "persistence" provider in order to test the service layer.
+ *
+ * This is not designed for real use outside of development. For example:
+ *   - it only keeps an in-memory record of saved entities, there is no real persistence
+ *   - it does not support transactions
+ *   - it does not clone objects on save/retrieval, so any modifications made after interacting with this service
+ *     also modify the "persisted" copies.
+ *
+ * TODO, deep copy objects on save/get so that they cannot be modified outside this class without modifying the persisted copy.
+ */
+public class VolatileAgentClassPersistenceProvider implements AgentClassPersistenceProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(VolatileAgentClassPersistenceProvider.class);
+
+    private Map<String, AgentClass> agentClasses = new ConcurrentHashMap<>();
+
+    @Override
+    public long getCount() {
+        return agentClasses.size();
+    }
+
+    @Override
+    public AgentClass save(AgentClass agentClass) {
+        if (agentClass == null || agentClass.getName() == null) {
+            throw new IllegalArgumentException("Agent Class must be not null and have a name in order to be saved.");
+        }
+        agentClasses.put(agentClass.getName(), agentClass);
+        logger.debug("Saved AgentClass with name={}", agentClass.getName());
+        return agentClass;
+
+    }
+
+    @Override
+    public Iterable<AgentClass> getAll() {
+        return new ArrayList<>(agentClasses.values());
+    }
+
+    @Override
+    public boolean existsById(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name must be not null.");
+        }
+        return agentClasses.containsKey(name);
+    }
+
+    @Override
+    public Optional<AgentClass> getById(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name must be not null.");
+        }
+        return Optional.ofNullable(agentClasses.get(name));
+    }
+
+    @Override
+    public void deleteById(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Name must be not null.");
+        }
+        AgentClass removed = agentClasses.remove(name);
+        if (removed != null) {
+            logger.debug("Found and deleted AgentClass with name='{}'", name);
+        } else {
+            logger.debug("Could not delete AgentClass with name='{}' (no match).", name);
+        }
+    }
+
+    @Override
+    public void delete(AgentClass agentClass) {
+        if (agentClass == null || agentClass.getName() == null) {
+            throw new IllegalArgumentException("Agent Class must be not null and have a name.");
+        }
+        deleteById(agentClass.getName());
+    }
+
+    @Override
+    public void deleteAll() {
+        agentClasses.clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentManifestPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentManifestPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentManifestPersistenceProvider.java
new file mode 100644
index 0000000..634ec9f
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentManifestPersistenceProvider.java
@@ -0,0 +1,119 @@
+/*
+ * 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.c2.core.provider.persistence;
+
+import org.apache.nifi.minifi.c2.api.provider.agent.AgentManifestPersistenceProvider;
+import org.apache.nifi.minifi.c2.model.AgentManifest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A simple, in-memory "persistence" provider in order to test the service layer.
+ *
+ * This is not designed for real use outside of development. For example:
+ *   - it only keeps an in-memory record of saved entities, there is no real persistence
+ *   - it does not support transactions
+ *   - it does not clone objects on save/retrieval, so any modifications made after interacting with this service
+ *     also modify the "persisted" copies.
+ *
+ * TODO, deep copy objects on save/get so that they cannot be modified outside this class without modifying the persisted copy.
+ */
+public class VolatileAgentManifestPersistenceProvider implements AgentManifestPersistenceProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(VolatileAgentManifestPersistenceProvider.class);
+
+    private Map<String, AgentManifest> agentManifests = new ConcurrentHashMap<>();
+
+    @Override
+    public long getCount() {
+        return agentManifests.size();
+    }
+
+    @Override
+    public AgentManifest save(AgentManifest agentManifest) {
+        if (agentManifest == null || agentManifest.getIdentifier() == null) {
+            throw new IllegalArgumentException("Agent Manifest must be not null and have an id in order to be saved.");
+        }
+        agentManifests.put(agentManifest.getIdentifier(), agentManifest);
+        return agentManifest;
+    }
+
+    @Override
+    public Iterable<AgentManifest> getAll() {
+        return new ArrayList<>(agentManifests.values());
+    }
+
+    @Override
+    public Iterable<AgentManifest> getAllById(Iterable<String> agentManifestIds) {
+        if (agentManifestIds == null) {
+            throw new IllegalArgumentException("Agent manifest ids must not be null");
+        }
+
+        HashSet<AgentManifest> matchedManifests = new HashSet<>();
+        agentManifestIds.forEach(id -> {
+                AgentManifest manifest = agentManifests.get(id);
+                if (manifest != null) {
+                    matchedManifests.add(manifest);
+                }
+        });
+        return matchedManifests;
+
+    }
+
+    @Override
+    public boolean existsById(String agentManifestId) {
+        if (agentManifestId == null) {
+            throw new IllegalArgumentException("Id must be not null.");
+        }
+        return agentManifests.containsKey(agentManifestId);
+    }
+
+    @Override
+    public Optional<AgentManifest> getById(String agentManifestId) {
+        if (agentManifestId == null) {
+            throw new IllegalArgumentException("Id must be not null.");
+        }
+        return Optional.ofNullable(agentManifests.get(agentManifestId));
+    }
+
+    @Override
+    public void deleteById(String agentManifestId) {
+        if (agentManifestId == null) {
+            throw new IllegalArgumentException("Id must be not null.");
+        }
+        agentManifests.remove(agentManifestId);
+    }
+
+    @Override
+    public void delete(AgentManifest agentManifest) {
+        if (agentManifest == null || agentManifest.getIdentifier() == null) {
+            throw new IllegalArgumentException("Agent Manifest must be not null and must have an id");
+        }
+        deleteById(agentManifest.getIdentifier());
+    }
+
+    @Override
+    public void deleteAll() {
+        agentManifests.clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentPersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentPersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentPersistenceProvider.java
new file mode 100644
index 0000000..9386266
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileAgentPersistenceProvider.java
@@ -0,0 +1,104 @@
+/*
+ * 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.c2.core.provider.persistence;
+
+import org.apache.nifi.minifi.c2.api.provider.agent.AgentPersistenceProvider;
+import org.apache.nifi.minifi.c2.model.Agent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * A simple, in-memory "persistence" provider in order to test the service layer.
+ *
+ * This is not designed for real use outside of development. For example:
+ *   - it only keeps an in-memory record of saved entities, there is no real persistence
+ *   - it does not support transactions
+ *   - it does not clone objects on save/retrieval, so any modifications made after interacting with this service
+ *     also modify the "persisted" copies.
+ *
+ * TODO, deep copy objects on save/get so that they cannot be modified outside this class without modifying the persisted copy.
+ */
+public class VolatileAgentPersistenceProvider implements AgentPersistenceProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(VolatileAgentPersistenceProvider.class);
+
+    private Map<String, Agent> agents = new ConcurrentHashMap<>();
+
+    @Override
+    public long getCount() {
+        return agents.size();
+    }
+
+    @Override
+    public Agent save(Agent agent) {
+        if (agent == null || agent.getIdentifier() == null) {
+            throw new IllegalArgumentException("Agent must be not null and must have an id");
+        }
+        agents.put(agent.getIdentifier(), agent);
+        return agent;
+    }
+
+    @Override
+    public Iterable<Agent> getAll() {
+        return new ArrayList<>(agents.values());
+    }
+
+    @Override
+    public Iterable<Agent> getByClassName(String agentClassName) {
+        return agents.values().stream().filter(agent -> agentClassName.equals(agent.getAgentClass())).collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean existsById(String agentId) {
+        if (agentId == null) {
+            throw new IllegalArgumentException("Id must be not null.");
+        }
+        return agents.containsKey(agentId);
+    }
+
+    @Override
+    public Optional<Agent> getById(String agentId) {
+        if (agentId == null) {
+            throw new IllegalArgumentException("Id must be not null.");
+        }
+        return Optional.ofNullable(agents.get(agentId));
+    }
+
+    @Override
+    public void deleteById(String agentId) {
+        agents.remove(agentId);
+    }
+
+    @Override
+    public void delete(Agent agent) {
+        if (agent == null || agent.getIdentifier() == null) {
+            throw new IllegalArgumentException("Agent must be not null and must have an id");
+        }
+        deleteById(agent.getIdentifier());
+    }
+
+    @Override
+    public void deleteAll() {
+        agents.clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/11bb8dbe/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileDevicePersistenceProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileDevicePersistenceProvider.java b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileDevicePersistenceProvider.java
new file mode 100644
index 0000000..d5c62ff
--- /dev/null
+++ b/minifi-c2/minifi-c2-framework/src/main/java/org/apache/nifi/minifi/c2/core/provider/persistence/VolatileDevicePersistenceProvider.java
@@ -0,0 +1,102 @@
+/*
+ * 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.c2.core.provider.persistence;
+
+import org.apache.nifi.minifi.c2.api.provider.device.DevicePersistenceProvider;
+import org.apache.nifi.minifi.c2.model.Device;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A simple, in-memory "persistence" provider in order to test the service layer.
+ *
+ * This is not designed for real use outside of development. For example:
+ *   - it only keeps an in-memory record of saved entities, there is no real persistence
+ *   - it does not support transactions
+ *   - it does not clone objects on save/retrieval, so any modifications made after interacting with this service
+ *     also modify the "persisted" copies.
+ *
+ * TODO, deep copy objects on save/get so that they cannot be modified outside this class without modifying the persisted copy.
+ */
+public class VolatileDevicePersistenceProvider implements DevicePersistenceProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(VolatileDevicePersistenceProvider.class);
+
+    private Map<String, Device> devices = new ConcurrentHashMap<>();
+
+    @Override
+    public long getCount() {
+        return devices.size();
+    }
+
+    @Override
+    public Device save(Device device) {
+        if (device == null || device.getIdentifier() == null) {
+            throw new IllegalArgumentException("Device must be not null and have an id in order to be saved.");
+        }
+        devices.put(device.getIdentifier(), device);
+        return device;
+    }
+
+    @Override
+    public Iterable<Device> getAll() {
+        return new ArrayList<>(devices.values());
+    }
+
+    @Override
+    public boolean existsById(String deviceId) {
+        if (deviceId == null) {
+            throw new IllegalArgumentException("Device id cannot be null");
+        }
+        return devices.containsKey(deviceId);
+    }
+
+    @Override
+    public Optional<Device> getById(String deviceId) {
+        if (deviceId == null) {
+            throw new IllegalArgumentException("Device id cannot be null");
+        }
+        return Optional.ofNullable(devices.get(deviceId));
+    }
+
+    @Override
+    public void deleteById(String deviceId) {
+        if (deviceId == null) {
+            throw new IllegalArgumentException("Device id cannot be null");
+        }
+        devices.remove(deviceId);
+    }
+
+    @Override
+    public void delete(Device device) {
+        if (device == null || device.getIdentifier() == null) {
+            throw new IllegalArgumentException("Device must be not null and have an id");
+        }
+        deleteById(device.getIdentifier());
+    }
+
+    @Override
+    public void deleteAll() {
+        devices.clear();
+    }
+
+}