Merge "Allow storing database password in environment variables"
diff --git a/models-base/src/main/java/org/onap/policy/models/base/PfConceptGetterImpl.java b/models-base/src/main/java/org/onap/policy/models/base/PfConceptGetterImpl.java
index f07713a..c641a80 100644
--- a/models-base/src/main/java/org/onap/policy/models/base/PfConceptGetterImpl.java
+++ b/models-base/src/main/java/org/onap/policy/models/base/PfConceptGetterImpl.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -102,7 +102,7 @@
         }
 
         // The very fist key that could have this name
-        final PfConceptKey lowestArtifactKey = new PfConceptKey(conceptKeyName, "0.0.1");
+        final PfConceptKey lowestArtifactKey = new PfConceptKey(conceptKeyName, PfKey.NULL_KEY_VERSION);
         if (conceptKeyVersion != null) {
             lowestArtifactKey.setVersion(conceptKeyVersion);
         }
diff --git a/models-dao/src/main/java/org/onap/policy/models/dao/impl/DefaultPfDao.java b/models-dao/src/main/java/org/onap/policy/models/dao/impl/DefaultPfDao.java
index b3d72a3..6707e70 100644
--- a/models-dao/src/main/java/org/onap/policy/models/dao/impl/DefaultPfDao.java
+++ b/models-dao/src/main/java/org/onap/policy/models/dao/impl/DefaultPfDao.java
@@ -26,11 +26,13 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.Persistence;
 import javax.persistence.TypedQuery;
 import javax.ws.rs.core.Response;
+
 import org.onap.policy.models.base.PfConcept;
 import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.base.PfModelException;
@@ -411,6 +413,9 @@
         final EntityManager mg = getEntityManager();
         try {
             final T t = mg.find(someClass, key);
+            if (t != null) {
+                mg.refresh(t);
+            }
             return checkAndReturn(someClass, t);
         } finally {
             mg.close();
@@ -425,6 +430,9 @@
         final EntityManager mg = getEntityManager();
         try {
             final T t = mg.find(someClass, key);
+            if (t != null) {
+                mg.refresh(t);
+            }
             return checkAndReturn(someClass, t);
         } finally {
             mg.close();
@@ -439,6 +447,9 @@
         final EntityManager mg = getEntityManager();
         try {
             final T t = mg.find(someClass, key);
+            if (t != null) {
+                mg.refresh(t);
+            }
             return checkAndReturn(someClass, t);
         } finally {
             mg.close();
diff --git a/models-dao/src/test/java/org/onap/policy/models/dao/DummyTimestampEntity.java b/models-dao/src/test/java/org/onap/policy/models/dao/DummyTimestampEntity.java
new file mode 100644
index 0000000..d18a915
--- /dev/null
+++ b/models-dao/src/test/java/org/onap/policy/models/dao/DummyTimestampEntity.java
@@ -0,0 +1,110 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+
+import org.onap.policy.common.utils.validation.Assertions;
+import org.onap.policy.models.base.PfConcept;
+import org.onap.policy.models.base.PfKey;
+import org.onap.policy.models.base.PfTimestampKey;
+import org.onap.policy.models.base.PfValidationResult;
+
+@Entity
+@Table(name = "DummyTimestampEntity")
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class DummyTimestampEntity extends PfConcept {
+    private static final long serialVersionUID = -2962570563281067895L;
+
+    @EmbeddedId()
+    @NonNull
+    private PfTimestampKey key;
+
+    @Column
+    private double doubleValue;
+
+    /**
+     * Default constructor.
+     */
+    public DummyTimestampEntity() {
+        this.key = new PfTimestampKey();
+        this.doubleValue = 123.45;
+    }
+
+    public DummyTimestampEntity(DummyTimestampEntity source) {
+        this.key = source.key;
+        this.doubleValue = source.doubleValue;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param key the key
+     * @param doubleValue the double value
+     */
+    public DummyTimestampEntity(final PfTimestampKey key, final double doubleValue) {
+        this.key = key;
+        this.doubleValue = doubleValue;
+    }
+
+    @Override
+    public List<PfKey> getKeys() {
+        final List<PfKey> keyList = new ArrayList<>();
+        keyList.add(getKey());
+        return keyList;
+    }
+
+    @Override
+    public PfValidationResult validate(final PfValidationResult result) {
+        return key.validate(result);
+    }
+
+    @Override
+    public void clean() {
+        key.clean();
+    }
+
+
+    @Override
+    public int compareTo(@NonNull final PfConcept otherObj) {
+        if (this == otherObj) {
+            return 0;
+        }
+        if (getClass() != otherObj.getClass()) {
+            return this.getClass().getName().compareTo(otherObj.getClass().getName());
+        }
+
+        final DummyTimestampEntity other = (DummyTimestampEntity) otherObj;
+
+        return Double.compare(doubleValue, other.doubleValue);
+    }
+}
diff --git a/models-dao/src/test/java/org/onap/policy/models/dao/EntityTest.java b/models-dao/src/test/java/org/onap/policy/models/dao/EntityTest.java
index 70505aa..f5702e6 100644
--- a/models-dao/src/test/java/org/onap/policy/models/dao/EntityTest.java
+++ b/models-dao/src/test/java/org/onap/policy/models/dao/EntityTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,7 +28,10 @@
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
@@ -38,6 +41,7 @@
 import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.base.PfReferenceKey;
+import org.onap.policy.models.base.PfTimestampKey;
 import org.onap.policy.models.dao.impl.DefaultPfDao;
 
 /**
@@ -54,6 +58,9 @@
     private static final String VERSION003 = "0.0.3";
     private static final String VERSION002 = "0.0.2";
     private static final String VERSION001 = "0.0.1";
+    private static final Date TIMESTAMP0 = new Date();
+    private static final Date TIMESTAMP1 = new Date();
+    private static final Date TIMESTAMP2 = new Date();
     private PfDao pfDao;
 
     @Test
@@ -127,6 +134,7 @@
 
         final PfConceptKey nullKey = null;
         final PfReferenceKey nullRefKey = null;
+        final PfTimestampKey nullTimeKey = null;
         final List<PfConceptKey> nullKeyList = null;
         final List<PfConceptKey> emptyKeyList = new ArrayList<>();
         final List<PfReferenceKey> nullRKeyList = null;
@@ -141,6 +149,7 @@
         pfDao.deleteCollection(emptyKeyList);
         pfDao.delete(PfConceptKey.class, nullKey);
         pfDao.delete(PfReferenceKey.class, nullRefKey);
+        pfDao.delete(PfTimestampKey.class, nullTimeKey);
         pfDao.deleteByConceptKey(PfConceptKey.class, nullKeyList);
         pfDao.deleteByConceptKey(PfConceptKey.class, emptyKeyList);
         pfDao.deleteByReferenceKey(PfReferenceKey.class, nullRKeyList);
@@ -148,6 +157,7 @@
 
         pfDao.get(null, nullKey);
         pfDao.get(null, nullRefKey);
+        pfDao.get(null, nullTimeKey);
         pfDao.getAll(null);
         pfDao.getAll(null, nullKey);
         pfDao.getConcept(null, nullKey);
@@ -297,6 +307,57 @@
         assertEquals(2, deletedRCount);
 
         pfDao.update(new DummyReferenceEntity(new PfReferenceKey(owner5Key, "EntityF"), 120.0));
+
+        final PfTimestampKey atKey0 = new PfTimestampKey("AT-KEY0", VERSION001, TIMESTAMP0);
+        final PfTimestampKey atKey1 = new PfTimestampKey("AT-KEY1", VERSION001, TIMESTAMP1);
+        final PfTimestampKey atKey2 = new PfTimestampKey("AT-KEY2", VERSION001, TIMESTAMP2);
+        final DummyTimestampEntity tkeyInfo0 = new DummyTimestampEntity(atKey0, 200.0);
+        final DummyTimestampEntity tkeyInfo1 = new DummyTimestampEntity(atKey1, 200.1);
+        final DummyTimestampEntity tkeyInfo2 = new DummyTimestampEntity(atKey2, 200.2);
+
+        pfDao.create(tkeyInfo0);
+
+        final DummyTimestampEntity tkeyInfoBack0 = pfDao.get(DummyTimestampEntity.class, atKey0);
+        assertEquals(tkeyInfo0, tkeyInfoBack0);
+
+        final DummyTimestampEntity tkeyInfoBackNull =
+                pfDao.get(DummyTimestampEntity.class, PfTimestampKey.getNullKey());
+        assertNull(tkeyInfoBackNull);
+
+
+
+        final Set<DummyTimestampEntity> tkeyInfoSetIn = new TreeSet<>();
+        tkeyInfoSetIn.add(tkeyInfo1);
+        tkeyInfoSetIn.add(tkeyInfo2);
+
+        pfDao.createCollection(tkeyInfoSetIn);
+
+        Set<DummyTimestampEntity> tkeyInfoSetOut = new TreeSet<>(pfDao.getAll(DummyTimestampEntity.class));
+
+        tkeyInfoSetIn.add(tkeyInfo0);
+        assertEquals(tkeyInfoSetIn, tkeyInfoSetOut);
+
+        pfDao.delete(tkeyInfo1);
+        tkeyInfoSetIn.remove(tkeyInfo1);
+        tkeyInfoSetOut = new TreeSet<>(pfDao.getAll(DummyTimestampEntity.class));
+        assertEquals(tkeyInfoSetIn, tkeyInfoSetOut);
+
+        pfDao.deleteCollection(tkeyInfoSetIn);
+        tkeyInfoSetOut = new TreeSet<>(pfDao.getAll(DummyTimestampEntity.class));
+        assertEquals(0, tkeyInfoSetOut.size());
+
+        tkeyInfoSetIn.add(tkeyInfo2);
+        pfDao.createCollection(tkeyInfoSetIn);
+        tkeyInfoSetOut = new TreeSet<>(pfDao.getAll(DummyTimestampEntity.class));
+        assertEquals(keyInfoSetIn, keyInfoSetOut);
+
+        pfDao.delete(DummyTimestampEntity.class, atKey2);
+        tkeyInfoSetOut = new TreeSet<>(pfDao.getAll(DummyTimestampEntity.class));
+        assertEquals(3, keyInfoSetOut.size());
+        assertEquals(1, pfDao.size(DummyTimestampEntity.class));
+
+        pfDao.deleteAll(DummyTimestampEntity.class);
+        assertEquals(0, pfDao.size(DummyTimestampEntity.class));
     }
 
     private void testVersionOps() {
@@ -363,5 +424,41 @@
         assertEquals(3, pfDao.getFiltered(DummyConceptEntity.class, "BBB0", null).size());
         assertEquals(1, pfDao.getFiltered(DummyConceptEntity.class, "BBB0", VERSION003).size());
         assertEquals(6, pfDao.getFiltered(DummyConceptEntity.class, null, VERSION003).size());
+
+        final PfTimestampKey atKey0 = new PfTimestampKey("AT-KEY0", VERSION001, TIMESTAMP0);
+        final PfTimestampKey atKey1 = new PfTimestampKey("AT-KEY1", VERSION001, TIMESTAMP1);
+        final PfTimestampKey atKey2 = new PfTimestampKey("AT-KEY2", VERSION001, TIMESTAMP2);
+        final DummyTimestampEntity tkeyInfo0 = new DummyTimestampEntity(atKey0, 200.0);
+        final DummyTimestampEntity tkeyInfo1 = new DummyTimestampEntity(atKey1, 200.1);
+        final DummyTimestampEntity tkeyInfo2 = new DummyTimestampEntity(atKey2, 200.2);
+
+        pfDao.create(tkeyInfo0);
+        pfDao.create(tkeyInfo1);
+        pfDao.create(tkeyInfo2);
+
+
+        assertEquals(1, pfDao
+                .getFiltered(DummyTimestampEntity.class, "AT-KEY0", VERSION001, null, null, null, "DESC", 0).size());
+        assertEquals(1,
+                pfDao.getFiltered(DummyTimestampEntity.class, "AT-KEY0", null, null, null, null, "DESC", 0).size());
+        assertEquals(3, pfDao
+                .getFiltered(DummyTimestampEntity.class, null, VERSION001, TIMESTAMP0, TIMESTAMP2, null, "DESC", 0)
+                .size());
+        assertEquals(1, pfDao
+                .getFiltered(DummyTimestampEntity.class, "AT-KEY0", VERSION001, TIMESTAMP0, TIMESTAMP2, null, "DESC", 0)
+                .size());
+        assertEquals(3, pfDao
+                .getFiltered(DummyTimestampEntity.class, null, VERSION001, null, TIMESTAMP2, null, "DESC", 0).size());
+        assertEquals(3, pfDao
+                .getFiltered(DummyTimestampEntity.class, null, VERSION001, TIMESTAMP0, null, null, "DESC", 0).size());
+        assertEquals(2,
+                pfDao.getFiltered(DummyTimestampEntity.class, null, VERSION001, TIMESTAMP0, TIMESTAMP2, null, "DESC", 2)
+                        .size());
+
+        Map<String, Object> filterMap = new HashMap<>();
+        filterMap.put("doubleValue", 200.1);
+        assertEquals(1,
+                pfDao.getFiltered(DummyTimestampEntity.class, null, null, null, null, filterMap, "DESC", 0).size());
+
     }
 }
diff --git a/models-dao/src/test/resources/META-INF/persistence.xml b/models-dao/src/test/resources/META-INF/persistence.xml
index 5314ebb..04b2c5b 100644
--- a/models-dao/src/test/resources/META-INF/persistence.xml
+++ b/models-dao/src/test/resources/META-INF/persistence.xml
@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
   ============LICENSE_START=======================================================
-   Copyright (C) 2019 Nordix Foundation.
+   Copyright (C) 2019-2020 Nordix Foundation.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
-  
+
        http://www.apache.org/licenses/LICENSE-2.0
-  
+
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  
+
   SPDX-License-Identifier: Apache-2.0
   ============LICENSE_END=========================================================
 -->
@@ -28,12 +28,13 @@
         <class>org.onap.policy.models.base.PfConceptKey</class>
         <class>org.onap.policy.models.dao.DummyConceptEntity</class>
         <class>org.onap.policy.models.dao.DummyReferenceEntity</class>
+        <class>org.onap.policy.models.dao.DummyTimestampEntity</class>
 
         <properties>
             <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
             <property name="eclipselink.ddl-generation.output-mode" value="database" />
             <property name="eclipselink.logging.level" value="INFO" />
-            
+
             <property name="eclipselink.logging.level" value="ALL" />
             <property name="eclipselink.logging.level.jpa" value="ALL" />
             <property name="eclipselink.logging.level.ddl" value="ALL" />
@@ -44,7 +45,6 @@
             <property name="eclipselink.logging.level.server" value="ALL" />
             <property name="eclipselink.logging.level.query" value="ALL" />
             <property name="eclipselink.logging.level.properties" value="ALL" />
-            
         </properties>
     </persistence-unit>
 </persistence>
diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.input.json b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vCPE.policy.operational.input.json
rename to models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json
diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.json b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vCPE.policy.operational.output.json
rename to models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json
diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml b/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml
index e4a0694..2c7981a 100644
--- a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml
+++ b/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml
@@ -23,7 +23,7 @@
                actor: APPC
                recipe: Restart
                target:
-                 type: VM
+                 targetType: VM
                retry: 3
                timeout: 1200
                success: final_success
diff --git a/models-examples/src/main/resources/policies/vDNS.policy.operational.input.json b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vDNS.policy.operational.input.json
rename to models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json
diff --git a/models-examples/src/main/resources/policies/vDNS.policy.operational.output.json b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vDNS.policy.operational.output.json
rename to models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json
diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json
index 63c0d8b..f6f15fe 100644
--- a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json
+++ b/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json
@@ -4,32 +4,32 @@
         "policies": [
             {
                 "operational.modifyconfig": {
-                    "type": "onap.policies.controlloop.Operational",
-                    "version": "1.0.0",
+                    "type": "onap.policies.controlloop.operational.common.Drools",
+                    "type_version": "1.0.0",
                     "metadata": {
                         "policy-id": "operational.modifyconfig"
                     },
                     "properties": {
-                        "controlLoop": {
-                            "version": "2.0.0",
-                            "controlLoopName": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a",
-                            "trigger_policy": "unique-policy-id-1-modifyConfig",
-                            "timeout": 1200,
-                            "abatement": false
-                        },
-                        "policies": [
+                        "id": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a",
+                        "timeout": 1200,
+                        "abatement": false,
+                        "trigger": "unique-policy-id-1-modifyConfig",
+                        "operations": [
                             {
                                 "id": "unique-policy-id-1-modifyConfig",
-                                "name": "modify packet gen config",
-                                "description": null,
-                                "actor": "APPC",
-                                "recipe": "ModifyConfig",
-                                "target": {
-                                    "resourceID": "Eace933104d443b496b8.nodes.heat.vpg",
-                                    "type": "VNF"
+                                "description": "Modify the packet generator",
+                                "operation": {
+                                    "actor": "APPC",
+                                    "operation": "ModifyConfig",
+                                    "target": {
+                                        "targetType": "VNF",
+                                        "entityId": {
+                                            "resourceID": "bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38"
+                                        }
+                                    }
                                 },
-                                "retry": 0,
                                 "timeout": 300,
+                                "retries": 0,
                                 "success": "final_success",
                                 "failure": "final_failure",
                                 "failure_timeout": "final_failure_timeout",
@@ -37,7 +37,8 @@
                                 "failure_exception": "final_failure_exception",
                                 "failure_guard": "final_failure_guard"
                             }
-                        ]
+                        ],
+                        "controllerName": "usecases"
                     }
                 }
             }
diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json
rename to models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json
diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json
similarity index 100%
rename from models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json
rename to models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.Naming.yaml b/models-examples/src/main/resources/policytypes/onap.policies.Naming.yaml
index 8636b79..c30cefe 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.Naming.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.Naming.yaml
@@ -26,7 +26,7 @@
                 metadata:
                     matchable: true
             naming-recipe:
-                type: String
+                type: string
                 required: true
             name-operation:
                 type: string
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml
index f21fd5a..773e0c9 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml
@@ -3,4 +3,4 @@
    onap.policies.controlloop.Operational:
       derived_from: tosca.policies.Root
       version: 1.0.0
-      description: Operational Policy for Control Loops
\ No newline at end of file
+      description: Operational Policy for Control Loops Supporting Legacy YAML Policy Definition.
\ No newline at end of file
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml
index 2dc6ba8..4a918be 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml
@@ -3,116 +3,91 @@
     onap.policies.controlloop.operational.Common:
         derived_from: tosca.policies.Root
         version: 1.0.0
-        description: Operational Policy for Control Loop execution
+        description: |
+            Operational Policy for Control Loop execution. Originated in Frankfurt to support TOSCA Compliant
+            Policy Types. This does NOT support the legacy Policy YAML policy type.
         properties:
             id:
-                type: String
+                type: string
                 description: The unique control loop id.
                 required: true
             timeout:
-                type: Integer
+                type: integer
                 description: |
                     Overall timeout for executing all the operations. This timeout should equal or exceed the total
                     timeout for each operation listed.
                 required: true
             abatement:
-                type: Boolean
+                type: boolean
                 description: Whether an abatement event message will be expected for the control loop from DCAE.
                 required: true
                 default: false
             trigger:
-                type: String
+                type: string
                 description: Initial operation to execute upon receiving an Onset event message for the Control Loop.
                 required: true
             operations:
-                type: List
+                type: list
                 description: List of operations to be performed when Control Loop is triggered.
                 required: true
                 entry_schema:
                     type: onap.datatype.controlloop.Operation
 
-    onap.policies.controlloop.operational.common.Drools:
-        derived_from: onap.policies.controlloop.operational.Common
-        type_version: 1.0.0
-        version: 1.0.0
-        description: Operational policies for Drools PDP
-        properties:
-            controllerName:
-                type: String
-                description: Drools controller properties
-                required: false
-
 data_types:
-    # TBD if this is needed
-    onap.datatype.controlloop.operation.Failure:
-        derived_from: tosca.datatypes.Root
-        description: Captures information of an operational failure performed for control loop
-        properties:
-            messages:
-                type: String
-                description: error message
-                required: true
-            category:
-                type: String
-                description: |
-                    The category the error occurred in. Whether this is a general error from the actor, or the operation
-                    timed out, retries were exhausted in trying to execute the operation, a guard policy prevented the
-                    operation from occuring, or an exception in the system caused the failure.
-                constraints:
-                - valid_values: [error, timeout, retries, guard, exception]
-
     onap.datatype.controlloop.Target:
         derived_from: tosca.datatypes.Root
         description: Definition for a entity in A&AI to perform a control loop operation on
         properties:
             targetType:
-                type: String
+                type: string
                 description: Category for the target type
                 required: true
                 constraints:
                 - valid_values: [VNF, VM, VFMODULE, PNF]
             entityIds:
-                type: Map
+                type: map
                 description: |
                     Map of values that identify the resource. If none are provided, it is assumed that the
                     entity that generated the ONSET event will be the target.
                 required: false
+                entry_schema:
+                    type: string
 
     onap.datatype.controlloop.Actor:
         derived_from: tosca.datatypes.Root
         description: An actor/operation/target definition
         properties:
             actor:
-                type: String
+                type: string
                 description: The actor performing the operation.
                 required: true
             operation:
-                type: String
+                type: string
                 description: The operation the actor is performing.
                 required: true
             target:
-                type: String
+                type: onap.datatype.controlloop.Target
                 description: The resource the operation should be performed on.
                 required: true
                 metadata:
                     clamp_possible_values: <string:see clamp project for syntax>
             payload:
-                type: Map
+                type: map
                 description: Name/value pairs of payload information passed by Policy to the actor
                 required: false
                 entry_schema:
-                    type: String
+                    type: string
 
     onap.datatype.controlloop.Operation:
         derived_from: tosca.datatypes.Root
         description: An operation supported by an actor
         properties:
             id:
-                type: String
+                type: string
                 description: Unique identifier for the operation
                 required: true
             description:
-                type: String
+                type: string
                 description: A user-friendly description of the intent for the operation
                 required: false
             operation:
@@ -122,41 +97,41 @@
                 metadata:
                     clamp_possible_values: <string:see clamp project for syntax>
             timeout:
-                type: Integer
+                type: integer
                 description: The amount of time for the actor to perform the operation.
                 required: true
             retries:
-                type: Integer
+                type: integer
                 description: The number of retries the actor should attempt to perform the operation.
                 required: true
                 default: 0
             success:
-                type: String
+                type: string
                 description: Points to the operation to invoke on success. A value of "final_success" indicates and end to the operation.
                 required: false
                 default: final_success
             failure:
-                type: String
+                type: string
                 description: Points to the operation to invoke on Actor operation failure.
                 required: false
                 default: final_failure
             failure_timeout:
-                type: String
+                type: string
                 description: Points to the operation to invoke when the time out for the operation occurs.
                 required: false
                 default: final_failure_timeout
             failure_retries:
-                type: String
+                type: string
                 description: Points to the operation to invoke when the current operation has exceeded its max retries.
                 required: false
                 default: final_failure_retries
             failure_exception:
-                type: String
+                type: string
                 description: Points to the operation to invoke when the current operation causes an exception.
                 required: false
                 default: final_failure_exception
             failure_guard:
-                type: String
+                type: string
                 description: Points to the operation to invoke when the current operation is blocked due to guard policy enforcement.
                 required: false
                 default: final_failure_guard
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml
index dba6567..9c6c612 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml
@@ -2,22 +2,23 @@
 policy_types:
     onap.policies.controlloop.operational.common.Apex:
         derived_from: onap.policies.controlloop.operational.Common
+        type_version: 1.0.0
         version: 1.0.0
         description: Operational policies for Apex PDP
         properties:
             engineServiceParameters:
-                type: String
+                type: string
                 description: The engine parameters like name, instanceCount, policy implementation, parameters etc.
                 required: true
             eventInputParameters:
-                type: String
+                type: string
                 description: The event input parameters.
                 required: true
             eventOutputParameters:
-                type: String
+                type: string
                 description: The event output parameters.
                 required: true
             javaProperties:
-                type: String
+                type: string
                 description: Name/value pairs of properties to be set for APEX if needed.
                 required: false
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml
new file mode 100644
index 0000000..2d793cc
--- /dev/null
+++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml
@@ -0,0 +1,13 @@
+tosca_definitions_version: tosca_simple_yaml_1_0_0
+policy_types:
+    onap.policies.controlloop.operational.common.Drools:
+        derived_from: onap.policies.controlloop.operational.Common
+        type_version: 1.0.0
+        version: 1.0.0
+        description: Operational policies for Drools PDP
+        properties:
+            controllerName:
+                type: string
+                description: Drools controller properties
+                required: false
+
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml
index 2f5abdd..5fa4308 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml
@@ -2,7 +2,8 @@
 policy_types:
    onap.policies.Monitoring:
       derived_from: tosca.policies.Root
-      description: a base policy type for all policies that governs monitoring provisioning
+      version: 1.0.0
+      description: a base policy type for all policies that govern monitoring provisioning
    onap.policies.monitoring.cdap.tca.hi.lo.app:
       derived_from: onap.policies.Monitoring
       version: 1.0.0
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml
index cf70e9b..8419b09 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml
@@ -2,10 +2,10 @@
 policy_types:
    onap.policies.Monitoring:
       derived_from: tosca.policies.Root
-      description: a base policy type for all policies that govern monitoring provision
+      description: a base policy type for all policies that govern monitoring provisioning
       version: 1.0.0
    onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server:
-      derived_from: policy.nodes.Root
+      derived_from: onap.policies.Monitoring
       version: 1.0.0
       properties:
          buscontroller_feed_publishing_endpoint:
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.native.Apex.yaml b/models-examples/src/main/resources/policytypes/onap.policies.native.Apex.yaml
index 1a394cd..8c780f6 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.native.Apex.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.native.Apex.yaml
@@ -43,15 +43,15 @@
                 required: false
                 default: "1.0.0"
             id:
-                type: int
+                type: integer
                 description: Specifies the engine id
                 required: true
             instance_count:
-                type: int
+                type: integer
                 description: Specifies the number of engine threads that should be run
                 required: true
             deployment_port:
-                type: int
+                type: integer
                 description: Specifies the port to connect to for engine administration
                 required: false
                 default: 1
@@ -59,17 +59,14 @@
                 type: string
                 description: The name of the file from which to read the APEX policy model
                 required: false
-                default: ""
             policy_type_impl:
                 type: string
                 description: The policy type implementation from which to read the APEX policy model
                 required: false
-                default: ""
             periodic_event_period:
                 type: string
                 description: The time interval in milliseconds for the periodic scanning event, 0 means don't scan
                 required: false
-                default: 0
             engine:
                 type: onap.datatypes.native.apex.engineservice.Engine
                 description: The parameters for all engines in the APEX engine service
@@ -98,7 +95,7 @@
                 description: Specifies a filter as a regular expression, events that do not match the filter are dropped, the default is to let all events through
                 required: false
             synchronous_mode:
-                type: bool
+                type: boolean
                 description: Specifies the event handler is syncronous (receive event and send response)
                 required: false
                 default: false
@@ -106,14 +103,12 @@
                 type: string
                 description: The peer event handler (output for input or input for output) of this event handler in synchronous mode, this parameter is mandatory if the event handler is in synchronous mode
                 required: false
-                default: ""
             synchronous_timeout:
-                type: int
+                type: integer
                 description: The timeout in milliseconds for responses to be issued by APEX torequests, this parameter is mandatory if the event handler is in synchronous mode
                 required: false
-                default: ""
             requestor_mode:
-                type: bool
+                type: boolean
                 description: Specifies the event handler is in requestor mode (send event and wait for response mode)
                 required: false
                 default: false
@@ -121,12 +116,10 @@
                 type: string
                 description: The peer event handler (output for input or input for output) of this event handler in requestor mode, this parameter is mandatory if the event handler is in requestor mode
                 required: false
-                default: ""
             requestor_timeout:
-                type: int
+                type: integer
                 description: The timeout in milliseconds for wait for responses to requests, this parameter is mandatory if the event handler is in requestor mode
                 required: false
-                default: ""
     onap.datatypes.native.apex.CarrierTechnology:
         derived_from: tosca.datatypes.Root
         properties:
@@ -149,7 +142,7 @@
                 type: string
                 description: The class name of the class that overrides default handling of the event protocol for this carrier technology, defaults to the supplied event protocol class
                 required: false
-    onap.datatypes.native.apex.Environmental:
+    onap.datatypes.native.apex.Environment:
         derived_from: tosca.datatypes.Root
         properties:
             name:
@@ -188,7 +181,7 @@
                 entry_schema:
                     type: onap.datatypes.native.apex.Plugin
             locking:
-                type: onap.datatypes.native.apex.plugin
+                type: onap.datatypes.native.apex.Plugin
                 description: The plugin to be used for locking context in and between APEX PDPs at runtime
                 required: false
             persistence:
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.native.Xacml.yaml b/models-examples/src/main/resources/policytypes/onap.policies.native.Xacml.yaml
index d065f61..8ed45a0 100644
--- a/models-examples/src/main/resources/policytypes/onap.policies.native.Xacml.yaml
+++ b/models-examples/src/main/resources/policytypes/onap.policies.native.Xacml.yaml
@@ -10,7 +10,7 @@
         version: 1.0.0
         properties:
             policy:
-                type: String
+                type: string
                 required: true
                 description: The XML XACML 3.0 PolicySet or Policy
                 metadata:
diff --git a/models-interactions/model-actors/actor.aai/pom.xml b/models-interactions/model-actors/actor.aai/pom.xml
new file mode 100644
index 0000000..4e932a1
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (C) 2018 Huawei Intellectual Property. All rights reserved.
+  Modifications Copyright (C) 2019-2020 Nordix Foundation.
+  Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ============LICENSE_END=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>actor.aai</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java
new file mode 100644
index 0000000..df427c3
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+
+/**
+ * A&AI Actor.
+ */
+public class AaiActorServiceProvider extends HttpActor {
+    public static final String NAME = AaiConstants.ACTOR_NAME;
+
+    /**
+     * Constructs the object.
+     */
+    public AaiActorServiceProvider() {
+        super(NAME);
+
+        addOperator(HttpOperator.makeOperator(NAME, AaiCustomQueryOperation.NAME,
+                        AaiCustomQueryOperation::new));
+
+        // add all "get" operators
+        for (String operation : AaiGetOperation.OPERATIONS) {
+            addOperator(HttpOperator.makeOperator(NAME, operation, AaiGetOperation::new));
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java
new file mode 100644
index 0000000..e32734b
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java
@@ -0,0 +1,132 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A&AI Custom Query. Stores the {@link AaiCqResponse} in the context. In addition, if the
+ * context does not contain the "tenant" data for the vserver, then it will request that,
+ * as well.
+ */
+public class AaiCustomQueryOperation extends HttpOperation<String> {
+    private static final Logger logger = LoggerFactory.getLogger(AaiCustomQueryOperation.class);
+
+    public static final String NAME = "CustomQuery";
+
+    public static final String RESOURCE_LINK = "resource-link";
+    public static final String RESULT_DATA = "result-data";
+
+    private static final String PREFIX = "/aai/v16";
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public AaiCustomQueryOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, String.class);
+    }
+
+    /**
+     * Queries the vserver, if necessary.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        String vserver = params.getTargetEntity();
+
+        ControlLoopOperationParams tenantParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
+                        .operation(AaiGetOperation.TENANT).payload(null).retry(null).timeoutSec(null).build();
+
+        return params.getContext().obtain(AaiGetOperation.getTenantKey(vserver), tenantParams);
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        Map<String, String> request = makeRequest();
+
+        Entity<Map<String, String>> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+        Map<String, Object> headers = makeHeaders();
+
+        headers.put("Accept", MediaType.APPLICATION_JSON);
+        String url = makeUrl();
+
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+        // @formatter:off
+        return handleResponse(outcome, url,
+            callback -> operator.getClient().put(callback, makePath(), entity, headers));
+        // @formatter:on
+    }
+
+    /**
+     * Constructs the custom query using the previously retrieved tenant data.
+     */
+    private Map<String, String> makeRequest() {
+        String vserver = params.getTargetEntity();
+        StandardCoderObject tenant = params.getContext().getProperty(AaiGetOperation.getTenantKey(vserver));
+
+        String resourceLink = tenant.getString(RESULT_DATA, 0, RESOURCE_LINK);
+        if (resourceLink == null) {
+            throw new IllegalArgumentException("cannot perform custom query - no resource-link");
+        }
+
+        resourceLink = resourceLink.replace(PREFIX, "");
+
+        return Map.of("start", resourceLink, "query", "query/closed-loop");
+    }
+
+    @Override
+    protected Map<String, Object> makeHeaders() {
+        return AaiUtil.makeHeaders(params);
+    }
+
+    /**
+     * Injects the response into the context.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
+                    Response rawResponse, String response) {
+
+        logger.info("{}: caching response for {}", getFullName(), params.getRequestId());
+        params.getContext().setProperty(AaiCqResponse.CONTEXT_KEY, new AaiCqResponse(response));
+
+        return super.postProcessResponse(outcome, url, rawResponse, response);
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java
new file mode 100644
index 0000000..ee1c461
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java
@@ -0,0 +1,137 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Superclass of A&AI operators that use "get" to perform their request and store their
+ * response within the context as a {@link StandardCoderObject}. The property name under
+ * which they are stored is ${actor}.${operation}.${targetEntity}.
+ */
+public class AaiGetOperation extends HttpOperation<StandardCoderObject> {
+    private static final Logger logger = LoggerFactory.getLogger(AaiGetOperation.class);
+
+    public static final int DEFAULT_RETRY = 3;
+
+    // operation names
+    public static final String TENANT = "Tenant";
+
+    // property prefixes
+    private static final String TENANT_KEY_PREFIX = AaiConstants.CONTEXT_PREFIX + TENANT + ".";
+
+    /**
+     * Operation names supported by this operator.
+     */
+    public static final Set<String> OPERATIONS = Set.of(TENANT);
+
+
+    /**
+     * Responses that are retrieved from A&AI are placed in the operation context under
+     * the name "${propertyPrefix}.${targetEntity}".
+     */
+    private final String propertyPrefix;
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public AaiGetOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, StandardCoderObject.class);
+        this.propertyPrefix = operator.getFullName() + ".";
+    }
+
+    /**
+     * Gets the "context key" for the tenant query response associated with the given
+     * target entity.
+     *
+     * @param targetEntity target entity
+     * @return the "context key" for the response associated with the given target
+     */
+    public static String getTenantKey(String targetEntity) {
+        return (TENANT_KEY_PREFIX + targetEntity);
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        Map<String, Object> headers = makeHeaders();
+
+        headers.put("Accept", MediaType.APPLICATION_JSON);
+        String url = makeUrl();
+
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+        // @formatter:off
+        return handleResponse(outcome, url,
+            callback -> operator.getClient().get(callback, makePath(), headers));
+        // @formatter:on
+    }
+
+    @Override
+    protected Map<String, Object> makeHeaders() {
+        return AaiUtil.makeHeaders(params);
+    }
+
+    @Override
+    public String makePath() {
+        return (operator.getPath() + "/" + params.getTargetEntity());
+    }
+
+    /**
+     * Injects the response into the context.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
+                    Response rawResponse, StandardCoderObject response) {
+        String entity = params.getTargetEntity();
+
+        logger.info("{}: caching response of {} for {}", getFullName(), entity, params.getRequestId());
+
+        params.getContext().setProperty(propertyPrefix + entity, response);
+
+        return super.postProcessResponse(outcome, url, rawResponse, response);
+    }
+
+    /**
+     * Provides a default retry value, if none specified.
+     */
+    @Override
+    protected int getRetry(Integer retry) {
+        return (retry == null ? DEFAULT_RETRY : retry);
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java
new file mode 100644
index 0000000..14edc3a
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java
@@ -0,0 +1,50 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+
+/**
+ * Utilities used by A&AI classes.
+ */
+public class AaiUtil {
+
+    private AaiUtil() {
+        // do nothing
+    }
+
+    /**
+     * Makes standard request headers for A&AI requests.
+     *
+     * @param params operation parameters
+     * @return new request headers
+     */
+    public static Map<String, Object> makeHeaders(ControlLoopOperationParams params) {
+        Map<String, Object> headers = new HashMap<>();
+
+        headers.put("X-FromAppId", "POLICY");
+        headers.put("X-TransactionId", params.getRequestId().toString());
+
+        return headers;
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor b/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor
new file mode 100644
index 0000000..6a52e3f
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor
@@ -0,0 +1 @@
+org.onap.policy.controlloop.actor.aai.AaiActorServiceProvider
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java
new file mode 100644
index 0000000..513f339
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class AaiActorServiceProviderTest {
+
+    @Test
+    public void testAaiActorServiceProvider() {
+        final AaiActorServiceProvider prov = new AaiActorServiceProvider();
+
+        // verify that it has the operators we expect
+        List<String> expected = new LinkedList<>();
+        expected.add(AaiCustomQueryOperation.NAME);
+        expected.addAll(AaiGetOperation.OPERATIONS);
+
+        Collections.sort(expected);
+
+        var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java
new file mode 100644
index 0000000..a935087
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java
@@ -0,0 +1,200 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiCustomQueryOperationTest extends BasicAaiOperation<Map<String, String>> {
+    private static final StandardCoder coder = new StandardCoder();
+
+    private static final String MY_LINK = "my-link";
+
+    @Mock
+    private Actor tenantActor;
+
+    private AaiCustomQueryOperation oper;
+
+    public AaiCustomQueryOperationTest() {
+        super(AaiConstants.ACTOR_NAME, AaiCustomQueryOperation.NAME);
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        MyTenantOperator tenantOperator = new MyTenantOperator();
+
+        when(service.getActor(AaiConstants.ACTOR_NAME)).thenReturn(tenantActor);
+        when(tenantActor.getOperator(AaiGetOperation.TENANT)).thenReturn(tenantOperator);
+
+        oper = new AaiCustomQueryOperation(params, operator);
+    }
+
+    @Test
+    public void testAaiCustomQueryOperation() {
+        assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+        assertEquals(AaiCustomQueryOperation.NAME, oper.getName());
+    }
+
+    @Test
+    public void testStartOperationAsync_testStartPreprocessorAsync_testMakeRequest_testPostProcess() throws Exception {
+        // need two responses
+        when(rawResponse.readEntity(String.class)).thenReturn(makeTenantReply()).thenReturn(makeCqReply());
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.SUCCESS, getResult(future2));
+
+        // tenant response should have been cached within the context
+        assertNotNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+        // custom query response should have been cached within the context
+        AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+        assertNotNull(cqData);
+    }
+
+    /**
+     * Tests when preprocessor step is not needed.
+     */
+    @Test
+    public void testStartOperationAsync_testStartPreprocessorAsyncNotNeeded() throws Exception {
+        // pre-load the tenant data
+        final StandardCoderObject data = preloadTenantData();
+
+        // only need one response
+        when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.SUCCESS, getResult(future2));
+
+        // should not have replaced tenant response
+        assertSame(data, context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+        // custom query response should have been cached within the context
+        AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+        assertNotNull(cqData);
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        verifyHeaders(oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakeRequestNoResourceLink() throws Exception {
+        // pre-load EMPTY tenant data
+        preloadTenantData(new StandardCoderObject());
+
+        when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, getResult(future2));
+    }
+
+    private String makeTenantReply() throws Exception {
+        Map<String, String> links = Map.of(AaiCustomQueryOperation.RESOURCE_LINK, MY_LINK);
+        List<Map<String, String>> data = Arrays.asList(links);
+
+        Map<String, Object> reply = Map.of(AaiCustomQueryOperation.RESULT_DATA, data);
+        return coder.encode(reply);
+    }
+
+    private String makeCqReply() {
+        return "{}";
+    }
+
+    private StandardCoderObject preloadTenantData() throws Exception {
+        StandardCoderObject data = coder.decode(makeTenantReply(), StandardCoderObject.class);
+        preloadTenantData(data);
+        return data;
+    }
+
+    private void preloadTenantData(StandardCoderObject data) {
+        context.setProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY), data);
+    }
+
+    private PolicyResult getResult(CompletableFuture<OperationOutcome> future2)
+                    throws InterruptedException, ExecutionException, TimeoutException {
+
+        executor.runAll(100);
+        assertTrue(future2.isDone());
+
+        return future2.get().getResult();
+    }
+
+    protected class MyTenantOperator extends HttpOperator {
+        public MyTenantOperator() {
+            super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+
+            HttpParams http = HttpParams.builder().clientName(MY_CLIENT).path(PATH).timeoutSec(1).build();
+
+            configure(Util.translateToMap(AaiGetOperation.TENANT, http));
+            start();
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return new AaiGetOperation(params, this);
+        }
+
+        @Override
+        protected HttpClientFactory getClientFactory() {
+            return factory;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java
new file mode 100644
index 0000000..6548642
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java
@@ -0,0 +1,137 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiGetOperationTest extends BasicAaiOperation<Void> {
+
+    private static final String INPUT_FIELD = "input";
+    private static final String TEXT = "my-text";
+
+    private AaiGetOperation oper;
+
+    public AaiGetOperationTest() {
+        super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new AaiGetOperation(params, operator);
+    }
+
+    @Test
+    public void testGetRetry() {
+        // use default if null retry
+        assertEquals(AaiGetOperation.DEFAULT_RETRY, oper.getRetry(null));
+
+        // otherwise, use specified value
+        assertEquals(0, oper.getRetry(0));
+        assertEquals(10, oper.getRetry(10));
+    }
+
+    @Test
+    public void testStartOperationAsync_testStartQueryAsync_testPostProcessResponse() throws Exception {
+
+        // return a map in the reply
+        Map<String, String> reply = Map.of(INPUT_FIELD, TEXT);
+        when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(reply));
+
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+        assertFalse(future2.isDone());
+
+        executor.runAll(100);
+        assertTrue(future2.isDone());
+
+        assertEquals(PolicyResult.SUCCESS, future2.get().getResult());
+
+        // data should have been cached within the context
+        StandardCoderObject data = context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY));
+        assertNotNull(data);
+        assertEquals(TEXT, data.getString(INPUT_FIELD));
+    }
+
+    /**
+     * Tests startOperationAsync() when there's a failure.
+     */
+    @Test
+    public void testStartOperationAsyncFailure() throws Exception {
+
+        when(rawResponse.getStatus()).thenReturn(500);
+        when(rawResponse.readEntity(String.class)).thenReturn("");
+
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+        assertFalse(future2.isDone());
+
+        executor.runAll(100);
+        assertTrue(future2.isDone());
+
+        assertEquals(PolicyResult.FAILURE, future2.get().getResult());
+
+        // data should NOT have been cached within the context
+        assertNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        verifyHeaders(oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakePath() {
+        assertEquals(PATH + "/" + TARGET_ENTITY, oper.makePath());
+    }
+
+    @Test
+    public void testAaiGetOperator() {
+        assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+        assertEquals(AaiGetOperation.TENANT, oper.getName());
+    }
+
+    @Test
+    public void testGetTenantKey() {
+        assertEquals("AAI.Tenant." + TARGET_ENTITY, AaiGetOperation.getTenantKey(TARGET_ENTITY));
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java
new file mode 100644
index 0000000..ae38cca
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import org.junit.Test;
+
+public class AaiUtilTest extends BasicAaiOperation<Void> {
+
+    @Test
+    public void testMakeHeaders() {
+        makeContext();
+
+        Map<String, Object> headers = AaiUtil.makeHeaders(params);
+
+        verifyHeaders(headers);
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java
new file mode 100644
index 0000000..00485c9
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Map;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicAaiOperation<Q> extends BasicHttpOperation<Q> {
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicAaiOperation() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicAaiOperation(String actor, String operation) {
+        super(actor, operation);
+    }
+
+    protected void verifyHeaders(Map<String, Object> headers) {
+        assertEquals("POLICY", headers.get("X-FromAppId").toString());
+        assertEquals(params.getRequestId().toString(), headers.get("X-TransactionId"));
+    }
+}
diff --git a/models-interactions/model-actors/actor.appc/pom.xml b/models-interactions/model-actors/actor.appc/pom.xml
index 74bff9a..0cc243c 100644
--- a/models-interactions/model-actors/actor.appc/pom.xml
+++ b/models-interactions/model-actors/actor.appc/pom.xml
@@ -18,57 +18,82 @@
   ============LICENSE_END=========================================================
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
-  <parent>
-   <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-    <artifactId>model-actors</artifactId>
-    <version>2.2.1-SNAPSHOT</version>
-  </parent>
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
 
-  <artifactId>actor.appc</artifactId>
+    <artifactId>actor.appc</artifactId>
 
-  <dependencies>
-    <dependency>
-     <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-      <artifactId>actorServiceProvider</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>appc</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>events</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.code.gson</groupId>
-      <artifactId>gson</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions</groupId>
-      <artifactId>simulators</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.common</groupId>
-      <artifactId>policy-endpoints</artifactId>
-      <version>${policy.common.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>appc</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
index 0da1e2a..2491c33 100644
--- a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
+++ b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
@@ -33,17 +33,19 @@
 import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.controlloop.ControlLoopOperation;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicActor;
 import org.onap.policy.controlloop.policy.Policy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
-public class AppcActorServiceProvider extends ActorImpl {
+public class AppcActorServiceProvider extends BidirectionalTopicActor {
     private static final String NAME = "APPC";
 
     private static final Logger logger = LoggerFactory.getLogger(AppcActorServiceProvider.class);
 
+    // TODO old code: remove lines down to **HERE**
+
     private static final StandardCoder coder = new StandardCoder();
 
     // Strings for targets
@@ -57,17 +59,26 @@
     private static final String RECIPE_MODIFY = "ModifyConfig";
 
     private static final ImmutableList<String> recipes =
-            ImmutableList.of(RECIPE_RESTART, RECIPE_REBUILD, RECIPE_MIGRATE, RECIPE_MODIFY);
+                    ImmutableList.of(RECIPE_RESTART, RECIPE_REBUILD, RECIPE_MIGRATE, RECIPE_MODIFY);
     private static final ImmutableMap<String, List<String>> targets = new ImmutableMap.Builder<String, List<String>>()
-            .put(RECIPE_RESTART, ImmutableList.of(TARGET_VM)).put(RECIPE_REBUILD, ImmutableList.of(TARGET_VM))
-            .put(RECIPE_MIGRATE, ImmutableList.of(TARGET_VM)).put(RECIPE_MODIFY, ImmutableList.of(TARGET_VNF)).build();
+                    .put(RECIPE_RESTART, ImmutableList.of(TARGET_VM)).put(RECIPE_REBUILD, ImmutableList.of(TARGET_VM))
+                    .put(RECIPE_MIGRATE, ImmutableList.of(TARGET_VM)).put(RECIPE_MODIFY, ImmutableList.of(TARGET_VNF))
+                    .build();
     private static final ImmutableMap<String, List<String>> payloads = new ImmutableMap.Builder<String, List<String>>()
-            .put(RECIPE_MODIFY, ImmutableList.of("generic-vnf.vnf-id")).build();
+                    .put(RECIPE_MODIFY, ImmutableList.of("generic-vnf.vnf-id")).build();
 
+    // **HERE**
+
+    /**
+     * Constructs the object.
+     */
     public AppcActorServiceProvider() {
         super(NAME);
     }
 
+
+    // TODO old code: remove lines down to **HERE**
+
     @Override
     public String actor() {
         return NAME;
@@ -89,17 +100,19 @@
     }
 
     /**
-     * Constructs an APPC request conforming to the legacy API. The legacy API will be deprecated in
-     * future releases as all legacy functionality is moved into the LCM API.
+     * Constructs an APPC request conforming to the legacy API. The legacy API will be
+     * deprecated in future releases as all legacy functionality is moved into the LCM
+     * API.
      *
      * @param onset the event that is reporting the alert for policy to perform an action
-     * @param operation the control loop operation specifying the actor, operation, target, etc.
-     * @param policy the policy the was specified from the yaml generated by CLAMP or through the
-     *        Policy GUI/API
+     * @param operation the control loop operation specifying the actor, operation,
+     *        target, etc.
+     * @param policy the policy the was specified from the yaml generated by CLAMP or
+     *        through the Policy GUI/API
      * @return an APPC request conforming to the legacy API
      */
     public static Request constructRequest(VirtualControlLoopEvent onset, ControlLoopOperation operation, Policy policy,
-            String targetVnf) {
+                    String targetVnf) {
         /*
          * Construct an APPC request
          */
@@ -144,4 +157,5 @@
         }
     }
 
+    // **HERE**
 }
diff --git a/models-interactions/model-actors/actor.sdnc/pom.xml b/models-interactions/model-actors/actor.sdnc/pom.xml
index 04040db..4bb03ec 100644
--- a/models-interactions/model-actors/actor.sdnc/pom.xml
+++ b/models-interactions/model-actors/actor.sdnc/pom.xml
@@ -19,58 +19,71 @@
   ============LICENSE_END=========================================================
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
-  <parent>
-   <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-    <artifactId>model-actors</artifactId>
-    <version>2.2.1-SNAPSHOT</version>
-  </parent>
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
 
-  <artifactId>actor.sdnc</artifactId>
+    <artifactId>actor.sdnc</artifactId>
 
-  <dependencies>
-    <dependency>
-     <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-      <artifactId>actorServiceProvider</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>sdnc</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>events</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>aai</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.common</groupId>
-      <artifactId>policy-endpoints</artifactId>
-      <version>${policy.common.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions</groupId>
-      <artifactId>simulators</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>sdnc</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java
similarity index 87%
rename from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java
rename to models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java
index 2927bd8..26cdfad 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java
@@ -23,6 +23,8 @@
 import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.sdnc.SdncHealRequest;
 import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
 import org.onap.policy.sdnc.SdncHealRequestInfo;
@@ -34,7 +36,7 @@
 import org.onap.policy.sdnc.SdncHealVnfInfo;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class BandwidthOnDemandOperator extends SdncOperator {
+public class BandwidthOnDemandOperation extends SdncOperation {
     public static final String NAME = "BandwidthOnDemand";
 
     public static final String URI = "/GENERIC-RESOURCE-API:vf-module-topology-operation";
@@ -46,14 +48,17 @@
     /**
      * Constructs the object.
      *
-     * @param actorName name of the actor with which this operator is associated
+     * @param params operation parameters
+     * @param operator operator that created this operation
      */
-    public BandwidthOnDemandOperator(String actorName) {
-        super(actorName, NAME);
+    public BandwidthOnDemandOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator);
     }
 
     @Override
-    protected SdncRequest constructRequest(ControlLoopEventContext context) {
+    protected SdncRequest makeRequest(int attempt) {
+        ControlLoopEventContext context = params.getContext();
+
         String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
         if (StringUtils.isBlank(serviceInstance)) {
             throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java
similarity index 85%
rename from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java
rename to models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java
index da400f8..f255f3e 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java
@@ -23,6 +23,8 @@
 import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.sdnc.SdncHealNetworkInfo;
 import org.onap.policy.sdnc.SdncHealRequest;
 import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
@@ -30,7 +32,7 @@
 import org.onap.policy.sdnc.SdncHealServiceInfo;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class RerouteOperator extends SdncOperator {
+public class RerouteOperation extends SdncOperation {
     public static final String NAME = "Reroute";
 
     public static final String URI = "/GENERIC-RESOURCE-API:network-topology-operation";
@@ -42,14 +44,17 @@
     /**
      * Constructs the object.
      *
-     * @param actorName name of the actor with which this operator is associated
+     * @param params operation parameters
+     * @param operator operator that created this operation
      */
-    public RerouteOperator(String actorName) {
-        super(actorName, NAME);
+    public RerouteOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator);
     }
 
     @Override
-    protected SdncRequest constructRequest(ControlLoopEventContext context) {
+    protected SdncRequest makeRequest(int attempt) {
+        ControlLoopEventContext context = params.getContext();
+
         String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
         if (StringUtils.isBlank(serviceInstance)) {
             throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
index 8dc8ba5..99a4fda 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
@@ -30,6 +30,7 @@
 import org.onap.policy.controlloop.ControlLoopOperation;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
 import org.onap.policy.controlloop.policy.Policy;
 import org.onap.policy.sdnc.SdncHealNetworkInfo;
 import org.onap.policy.sdnc.SdncHealRequest;
@@ -76,8 +77,11 @@
     public SdncActorServiceProvider() {
         super(NAME);
 
-        addOperator(new RerouteOperator(NAME));
-        addOperator(new BandwidthOnDemandOperator(NAME));
+        addOperator(HttpOperator.makeOperator(NAME, RerouteOperation.NAME,
+                        RerouteOperation::new));
+
+        addOperator(HttpOperator.makeOperator(NAME, BandwidthOnDemandOperation.NAME,
+                        BandwidthOnDemandOperation::new));
     }
 
 
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
new file mode 100644
index 0000000..406722e
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
@@ -0,0 +1,95 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+
+/**
+ * Superclass for SDNC Operators.
+ */
+public abstract class SdncOperation extends HttpOperation<SdncResponse> {
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public SdncOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, SdncResponse.class);
+    }
+
+    /**
+     * Starts the GUARD.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        return startGuardAsync();
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        SdncRequest request = makeRequest(attempt);
+
+        Entity<SdncRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+        Map<String, Object> headers = makeHeaders();
+
+        headers.put("Accept", MediaType.APPLICATION_JSON);
+        String url = makeUrl();
+
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+        // @formatter:off
+        return handleResponse(outcome, url,
+            callback -> operator.getClient().post(callback, makePath(), entity, headers));
+        // @formatter:on
+    }
+
+    /**
+     * Makes the request.
+     *
+     * @param attempt current attempt, starting with "1"
+     * @return a new request to be posted
+     */
+    protected abstract SdncRequest makeRequest(int attempt);
+
+    /**
+     * Checks that the response has an "output" and that the output indicates success.
+     */
+    @Override
+    protected boolean isSuccess(Response rawResponse, SdncResponse response) {
+        return response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode());
+    }
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java
deleted file mode 100644
index 479ee90..0000000
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.controlloop.actorserviceprovider.AsyncResponseHandler;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Superclass for SDNC Operators.
- */
-public abstract class SdncOperator extends HttpOperator {
-    private static final Logger logger = LoggerFactory.getLogger(SdncOperator.class);
-
-    /**
-     * Constructs the object.
-     *
-     * @param actorName name of the actor with which this operator is associated
-     * @param name operation name
-     */
-    public SdncOperator(String actorName, String name) {
-        super(actorName, name);
-    }
-
-    @Override
-    protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
-                    OperationOutcome outcome) {
-
-        SdncRequest request = constructRequest(params.getContext());
-        return postRequest(params, outcome, request);
-    }
-
-    /**
-     * Constructs the request.
-     *
-     * @param context associated event context
-     * @return a new request
-     */
-    protected abstract SdncRequest constructRequest(ControlLoopEventContext context);
-
-    /**
-     * Posts the request and and arranges to retrieve the response.
-     *
-     * @param params operation parameters
-     * @param outcome updated with the response
-     * @param sdncRequest request to be posted
-     * @return the result of the request
-     */
-    private CompletableFuture<OperationOutcome> postRequest(ControlLoopOperationParams params, OperationOutcome outcome,
-                    SdncRequest sdncRequest) {
-        Map<String, Object> headers = new HashMap<>();
-
-        headers.put("Accept", "application/json");
-        String sdncUrl = getClient().getBaseUrl();
-
-        Util.logRestRequest(sdncUrl, sdncRequest);
-
-        Entity<SdncRequest> entity = Entity.entity(sdncRequest, MediaType.APPLICATION_JSON);
-
-        ResponseHandler handler = new ResponseHandler(params, outcome, sdncUrl);
-        return handler.handle(getClient().post(handler, getPath(), entity, headers));
-    }
-
-    private class ResponseHandler extends AsyncResponseHandler<Response> {
-        private final String sdncUrl;
-
-        public ResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome, String sdncUrl) {
-            super(params, outcome);
-            this.sdncUrl = sdncUrl;
-        }
-
-        /**
-         * Handles the response.
-         */
-        @Override
-        protected OperationOutcome doComplete(Response rawResponse) {
-            String strResponse = HttpClient.getBody(rawResponse, String.class);
-
-            Util.logRestResponse(sdncUrl, strResponse);
-
-            SdncResponse response;
-            try {
-                response = makeDecoder().decode(strResponse, SdncResponse.class);
-            } catch (CoderException e) {
-                logger.warn("Sdnc Heal cannot decode response with http error code {}", rawResponse.getStatus(), e);
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
-            }
-
-            if (response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode())) {
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.SUCCESS);
-
-            } else {
-                logger.info("Sdnc Heal Restcall failed with http error code {}", rawResponse.getStatus());
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE);
-            }
-        }
-
-        /**
-         * Handles exceptions.
-         */
-        @Override
-        protected OperationOutcome doFailed(Throwable thrown) {
-            logger.info("Sdnc Heal Restcall threw an exception", thrown);
-            return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
-        }
-    }
-
-    // these may be overridden by junit tests
-
-    protected StandardCoder makeDecoder() {
-        return new StandardCoder();
-    }
-}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java
similarity index 64%
rename from models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java
rename to models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java
index 02931a4..42042da 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java
@@ -26,45 +26,51 @@
 import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class BandwidthOnDemandOperatorTest extends BasicOperator {
+public class BandwidthOnDemandOperationTest extends BasicSdncOperation {
 
-    private BandwidthOnDemandOperator oper;
+    private BandwidthOnDemandOperation oper;
 
+    public BandwidthOnDemandOperationTest() {
+        super(DEFAULT_ACTOR, BandwidthOnDemandOperation.NAME);
+    }
 
     /**
      * Set up.
      */
     @Before
-    public void setUp() {
-        makeContext();
-        oper = new BandwidthOnDemandOperator(ACTOR);
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new BandwidthOnDemandOperation(params, operator);
     }
 
     @Test
     public void testBandwidthOnDemandOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(BandwidthOnDemandOperator.NAME, oper.getName());
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(BandwidthOnDemandOperation.NAME, oper.getName());
     }
 
     @Test
-    public void testConstructRequest() throws CoderException {
-        SdncRequest request = oper.constructRequest(context);
+    public void testMakeRequest() throws Exception {
+        SdncRequest request = oper.makeRequest(1);
         assertEquals("my-service", request.getNsInstanceId());
         assertEquals(REQ_ID, request.getRequestId());
-        assertEquals(BandwidthOnDemandOperator.URI, request.getUrl());
+        assertEquals(BandwidthOnDemandOperation.URI, request.getUrl());
         assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
 
         verifyRequest("bod.json", request);
 
-        verifyMissing(oper, BandwidthOnDemandOperator.SERVICE_ID_KEY, "service");
+        verifyMissing(BandwidthOnDemandOperation.SERVICE_ID_KEY, "service", BandwidthOnDemandOperation::new);
+
+        // perform the operation
+        makeContext();
+        verifyRequest("bod.json", verifyOperation(oper));
     }
 
     @Override
     protected Map<String, String> makeEnrichment() {
-        return Map.of(BandwidthOnDemandOperator.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperator.VNF_ID,
+        return Map.of(BandwidthOnDemandOperation.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperation.VNF_ID,
                         "my-vnf");
     }
 }
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java
deleted file mode 100644
index b9028d4..0000000
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.junit.Assert.assertEquals;
-
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.UUID;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.resources.ResourceUtils;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-
-/**
- * Superclass for various operator tests.
- */
-public abstract class BasicOperator {
-    protected static final UUID REQ_ID = UUID.randomUUID();
-    protected static final String ACTOR = "my-actor";
-
-    protected Map<String, String> enrichment;
-    protected VirtualControlLoopEvent event;
-    protected ControlLoopEventContext context;
-
-    /**
-     * Pretty-prints a request and verifies that the result matches the expected JSON.
-     *
-     * @param <T> request type
-     * @param expectedJsonFile name of the file containing the expected JSON
-     * @param request request to verify
-     * @throws CoderException if the request cannot be pretty-printed
-     */
-    protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
-        String json = new StandardCoder().encode(request, true);
-        String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
-
-        // strip request id, because it changes each time
-        final String stripper = "svc-request-id[^,]*";
-        json = json.replaceFirst(stripper, "").trim();
-        expected = expected.replaceFirst(stripper, "").trim();
-
-        assertEquals(expected, json);
-    }
-
-    /**
-     * Verifies that an exception is thrown if a field is missing from the enrichment
-     * data.
-     *
-     * @param oper operator to construct the request
-     * @param fieldName name of the field to be removed from the enrichment data
-     * @param expectedText text expected in the exception message
-     */
-    protected void verifyMissing(SdncOperator oper, String fieldName, String expectedText) {
-        makeContext();
-        enrichment.remove(fieldName);
-
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.constructRequest(context))
-                        .withMessageContaining("missing").withMessageContaining(expectedText);
-    }
-
-    protected void makeContext() {
-        // need a mutable map, so make a copy
-        enrichment = new TreeMap<>(makeEnrichment());
-
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-        event.setAai(enrichment);
-
-        context = new ControlLoopEventContext(event);
-    }
-
-    protected abstract Map<String, String> makeEnrichment();
-}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java
new file mode 100644
index 0000000..db8751d
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java
@@ -0,0 +1,152 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.resources.ResourceUtils;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+import org.onap.policy.sdnc.SdncResponseOutput;
+import org.powermock.reflect.Whitebox;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicSdncOperation extends BasicHttpOperation<SdncRequest> {
+
+    protected SdncResponse response;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicSdncOperation() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicSdncOperation(String actor, String operation) {
+        super(actor, operation);
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        super.setUp();
+
+        response = new SdncResponse();
+
+        SdncResponseOutput output = new SdncResponseOutput();
+        response.setResponseOutput(output);
+        output.setResponseCode("200");
+
+        when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(response));
+    }
+
+    /**
+     * Runs the operation and verifies that the response is successful.
+     *
+     * @param operation operation to run
+     * @return the request that was posted
+     */
+    protected SdncRequest verifyOperation(SdncOperation operation)
+                    throws InterruptedException, ExecutionException, TimeoutException {
+
+        CompletableFuture<OperationOutcome> future2 = operation.start();
+        executor.runAll(100);
+        assertFalse(future2.isDone());
+
+        verify(client).post(callbackCaptor.capture(), any(), requestCaptor.capture(), any());
+        callbackCaptor.getValue().completed(rawResponse);
+
+        executor.runAll(100);
+        assertTrue(future2.isDone());
+
+        assertEquals(PolicyResult.SUCCESS, future2.get().getResult());
+
+        return requestCaptor.getValue().getEntity();
+    }
+
+    /**
+     * Pretty-prints a request and verifies that the result matches the expected JSON.
+     *
+     * @param <T> request type
+     * @param expectedJsonFile name of the file containing the expected JSON
+     * @param request request to verify
+     * @throws CoderException if the request cannot be pretty-printed
+     */
+    protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
+        String json = new StandardCoder().encode(request, true);
+        String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
+
+        // strip request id, because it changes each time
+        final String stripper = "svc-request-id[^,]*";
+        json = json.replaceFirst(stripper, "").trim();
+        expected = expected.replaceFirst(stripper, "").trim();
+
+        assertEquals(expected, json);
+    }
+
+    /**
+     * Verifies that an exception is thrown if a field is missing from the enrichment
+     * data.
+     *
+     * @param fieldName name of the field to be removed from the enrichment data
+     * @param expectedText text expected in the exception message
+     */
+    protected void verifyMissing(String fieldName, String expectedText,
+                    BiFunction<ControlLoopOperationParams,HttpOperator,SdncOperation> maker) {
+
+        makeContext();
+        enrichment.remove(fieldName);
+
+        SdncOperation oper = maker.apply(params, operator);
+
+        assertThatIllegalArgumentException().isThrownBy(() -> Whitebox.invokeMethod(oper, "makeRequest", 1))
+                        .withMessageContaining("missing").withMessageContaining(expectedText);
+    }
+
+    protected abstract Map<String, String> makeEnrichment();
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java
similarity index 63%
rename from models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java
rename to models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java
index 0a7bcad..a98c381 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java
@@ -26,45 +26,51 @@
 import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class RerouteOperatorTest extends BasicOperator {
+public class RerouteOperationTest extends BasicSdncOperation {
 
-    private RerouteOperator oper;
+    private RerouteOperation oper;
 
+    public RerouteOperationTest() {
+        super(DEFAULT_ACTOR, RerouteOperation.NAME);
+    }
 
     /**
      * Set up.
      */
     @Before
-    public void setUp() {
-        makeContext();
-        oper = new RerouteOperator(ACTOR);
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new RerouteOperation(params, operator);
     }
 
     @Test
     public void testRerouteOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(RerouteOperator.NAME, oper.getName());
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(RerouteOperation.NAME, oper.getName());
     }
 
     @Test
-    public void testConstructRequest() throws CoderException {
-        SdncRequest request = oper.constructRequest(context);
+    public void testMakeRequest() throws Exception {
+        SdncRequest request = oper.makeRequest(1);
         assertEquals("my-service", request.getNsInstanceId());
         assertEquals(REQ_ID, request.getRequestId());
-        assertEquals(RerouteOperator.URI, request.getUrl());
+        assertEquals(RerouteOperation.URI, request.getUrl());
         assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
 
         verifyRequest("reroute.json", request);
 
-        verifyMissing(oper, RerouteOperator.SERVICE_ID_KEY, "service");
-        verifyMissing(oper, RerouteOperator.NETWORK_ID_KEY, "network");
+        verifyMissing(RerouteOperation.SERVICE_ID_KEY, "service", RerouteOperation::new);
+        verifyMissing(RerouteOperation.NETWORK_ID_KEY, "network", RerouteOperation::new);
+
+        // perform the operation
+        makeContext();
+        verifyRequest("reroute.json", verifyOperation(oper));
     }
 
     @Override
     protected Map<String, String> makeEnrichment() {
-        return Map.of(RerouteOperator.SERVICE_ID_KEY, "my-service", RerouteOperator.NETWORK_ID_KEY, "my-network");
+        return Map.of(RerouteOperation.SERVICE_ID_KEY, "my-service", RerouteOperation.NETWORK_ID_KEY, "my-network");
     }
 }
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
index 08655c3..ac81d49 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
@@ -41,7 +41,7 @@
 
 public class SdncActorServiceProviderTest {
 
-    private static final String REROUTE = RerouteOperator.NAME;
+    private static final String REROUTE = RerouteOperation.NAME;
 
     /**
      * Set up before test class.
@@ -63,7 +63,7 @@
         final SdncActorServiceProvider prov = new SdncActorServiceProvider();
 
         // verify that it has the operators we expect
-        var expected = Arrays.asList(BandwidthOnDemandOperator.NAME, RerouteOperator.NAME).stream().sorted()
+        var expected = Arrays.asList(BandwidthOnDemandOperation.NAME, RerouteOperation.NAME).stream().sorted()
                         .collect(Collectors.toList());
         var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
 
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java
new file mode 100644
index 0000000..e0825e1
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.sdnc.SdncRequest;
+
+public class SdncOperationTest extends BasicSdncOperation {
+
+    private SdncRequest request;
+    private SdncOperation oper;
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        oper = new SdncOperation(params, operator) {
+            @Override
+            protected SdncRequest makeRequest(int attempt) {
+                return request;
+            }
+        };
+    }
+
+    @Test
+    public void testSdncOperator() {
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(DEFAULT_OPERATION, oper.getName());
+    }
+
+    @Test
+    public void testStartOperationAsync_testStartRequestAsync() throws Exception {
+        verifyOperation(oper);
+    }
+
+    @Test
+    public void testIsSuccess() {
+        // success case
+        response.getResponseOutput().setResponseCode("200");
+        assertTrue(oper.isSuccess(null, response));
+
+        // failure code
+        response.getResponseOutput().setResponseCode("555");
+        assertFalse(oper.isSuccess(null, response));
+
+        // null code
+        response.getResponseOutput().setResponseCode(null);
+        assertFalse(oper.isSuccess(null, response));
+
+        // null output
+        response.setResponseOutput(null);
+        assertFalse(oper.isSuccess(null, response));
+    }
+
+    @Override
+    protected Map<String, String> makeEnrichment() {
+        return new TreeMap<>();
+    }
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java
deleted file mode 100644
index 25d383e..0000000
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import lombok.Setter;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
-import org.onap.policy.common.endpoints.http.server.HttpServletServer;
-import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
-import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
-import org.onap.policy.common.gson.GsonMessageBodyHandler;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.network.NetworkUtil;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncHealRequest;
-import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.onap.policy.sdnc.SdncResponseOutput;
-
-public class SdncOperatorTest {
-    public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
-    private static final String EXPECTED_EXCEPTION = "expected exception";
-    public static final String HTTP_CLIENT = "my-http-client";
-    public static final String HTTP_NO_SERVER = "my-http-no-server-client";
-    private static final String ACTOR = "my-actor";
-    private static final String OPERATION = "my-operation";
-
-    /**
-     * Outcome to be added to the response.
-     */
-    @Setter
-    private static SdncResponseOutput output;
-
-
-    private VirtualControlLoopEvent event;
-    private ControlLoopEventContext context;
-    private MyOper oper;
-
-    /**
-     * Starts the SDNC simulator.
-     */
-    @BeforeClass
-    public static void setUpBeforeClass() throws Exception {
-        // allocate a port
-        int port = NetworkUtil.allocPort();
-
-        /*
-         * Start the simulator. Must use "Properties" to configure it, otherwise the
-         * server will use the wrong serialization provider.
-         */
-        Properties svrprops = getServerProperties("my-server", port);
-        HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
-
-        /*
-         * Start the clients, one to the server, and one to a non-existent server.
-         */
-        TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath("sdnc")
-                        .serializationProvider(GsonMessageBodyHandler.class.getName());
-
-        HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
-
-        HttpClientFactoryInstance.getClientFactory()
-                        .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
-    }
-
-    @AfterClass
-    public static void tearDownAfterClass() {
-        HttpClientFactoryInstance.getClientFactory().destroy();
-        HttpServletServerFactoryInstance.getServerFactory().destroy();
-    }
-
-    /**
-     * Initializes {@link #oper} and sets {@link #output} to a success code.
-     */
-    @Before
-    public void setUp() {
-        event = new VirtualControlLoopEvent();
-        context = new ControlLoopEventContext(event);
-
-        initOper(HTTP_CLIENT);
-
-        output = new SdncResponseOutput();
-        output.setResponseCode("200");
-    }
-
-    @After
-    public void tearDown() {
-        oper.shutdown();
-    }
-
-    @Test
-    public void testSdncOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(OPERATION, oper.getName());
-        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
-    }
-
-    @Test
-    public void testGetClient() {
-        assertNotNull(oper.getTheClient());
-    }
-
-    @Test
-    public void testStartOperationAsync_testPostRequest() throws Exception {
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when decode() throws an exception.
-     */
-    @Test
-    public void testPostRequestDecodeException() throws Exception {
-
-        oper.setDecodeFailure(true);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when there is no "output" field in the response.
-     */
-    @Test
-    public void testPostRequestNoOutput() throws Exception {
-
-        setOutput(null);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when the output is not a success.
-     */
-    @Test
-    public void testPostRequestOutputFailure() throws Exception {
-
-        output.setResponseCode(null);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when the post() request throws an exception retrieving the
-     * response.
-     */
-    @Test
-    public void testPostRequestException() throws Exception {
-
-        // reset "oper" to point to a non-existent server
-        oper.shutdown();
-        initOper(HTTP_NO_SERVER);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
-    }
-
-    private static Properties getServerProperties(String name, int port) {
-        final Properties props = new Properties();
-        props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
-
-        final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
-
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
-
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
-                        GsonMessageBodyHandler.class.getName());
-        return props;
-    }
-
-    /**
-     * Initializes {@link #oper}.
-     *
-     * @param clientName name of the client which it should use
-     */
-    private void initOper(String clientName) {
-        oper = new MyOper();
-
-        HttpParams params = HttpParams.builder().clientName(clientName).path("request").build();
-        Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
-        oper.configure(mapParams);
-        oper.start();
-    }
-
-    /**
-     * Runs the operation.
-     *
-     * @return the outcome of the operation, or {@code null} if it does not complete in
-     *         time
-     */
-    private OperationOutcome runOperation() throws InterruptedException, ExecutionException, TimeoutException {
-        ControlLoopOperationParams params =
-                        ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
-
-        CompletableFuture<OperationOutcome> future = oper.startOperationAsync(params, 1, params.makeOutcome());
-
-        return future.get(5, TimeUnit.SECONDS);
-    }
-
-
-    private class MyOper extends SdncOperator {
-
-        /**
-         * Set to {@code true} to cause the decoder to throw an exception.
-         */
-        @Setter
-        private boolean decodeFailure = false;
-
-        public MyOper() {
-            super(ACTOR, OPERATION);
-        }
-
-        protected HttpClient getTheClient() {
-            return getClient();
-        }
-
-        @Override
-        protected SdncRequest constructRequest(ControlLoopEventContext context) {
-            SdncRequest request = new SdncRequest();
-
-            SdncHealRequest heal = new SdncHealRequest();
-            request.setHealRequest(heal);
-
-            return request;
-        }
-
-        @Override
-        protected StandardCoder makeDecoder() {
-            if (decodeFailure) {
-                // return a coder that throws exceptions when decode() is invoked
-                return new StandardCoder() {
-                    @Override
-                    public <T> T decode(String json, Class<T> clazz) throws CoderException {
-                        throw new CoderException(EXPECTED_EXCEPTION);
-                    }
-                };
-
-            } else {
-                return super.makeDecoder();
-            }
-        }
-    }
-
-    /**
-     * SDNC Simulator.
-     */
-    @Path("/sdnc")
-    @Produces(MEDIA_TYPE_APPLICATION_JSON)
-    public static class Server {
-
-        /**
-         * Generates a response.
-         *
-         * @param request incoming request
-         * @return resulting response
-         */
-        @POST
-        @Path("/request")
-        @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
-        public Response postRequest(SdncRequest request) {
-
-            SdncResponse response = new SdncResponse();
-            response.setResponseOutput(output);
-
-            return Response.status(Status.OK).entity(response).build();
-        }
-    }
-}
diff --git a/models-interactions/model-actors/actor.test/pom.xml b/models-interactions/model-actors/actor.test/pom.xml
new file mode 100644
index 0000000..3a10fa3
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ============LICENSE_END=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>actor.test</artifactId>
+    <description>Utilities for testing actors</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>utils-test</artifactId>
+            <version>${policy.common.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java
new file mode 100644
index 0000000..14c7ef5
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java
@@ -0,0 +1,181 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+
+/**
+ * Superclass for various BidirectionalTopicOperation tests.
+ */
+public class BasicBidirectionalTopicOperation {
+    protected static final UUID REQ_ID = UUID.randomUUID();
+    protected static final String DEFAULT_ACTOR = "default-actor";
+    protected static final String DEFAULT_OPERATION = "default-operation";
+    protected static final String MY_SINK = "my-sink";
+    protected static final String MY_SOURCE = "my-source";
+    protected static final String TARGET_ENTITY = "my-target";
+    protected static final Coder coder = new StandardCoder();
+    protected static final int TIMEOUT = 10;
+
+    protected final String actorName;
+    protected final String operationName;
+
+    @Captor
+    protected ArgumentCaptor<BiConsumer<String, StandardCoderObject>> listenerCaptor;
+
+    @Mock
+    protected ActorService service;
+    @Mock
+    protected BidirectionalTopicHandler topicHandler;
+    @Mock
+    protected Forwarder forwarder;
+    @Mock
+    protected BidirectionalTopicOperator operator;
+
+    protected BidirectionalTopicParams topicParams;
+    protected ControlLoopOperationParams params;
+    protected Map<String, String> enrichment;
+    protected VirtualControlLoopEvent event;
+    protected ControlLoopEventContext context;
+    protected OperationOutcome outcome;
+    protected PseudoExecutor executor;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicBidirectionalTopicOperation() {
+        this.actorName = DEFAULT_ACTOR;
+        this.operationName = DEFAULT_OPERATION;
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicBidirectionalTopicOperation(String actor, String operation) {
+        this.actorName = actor;
+        this.operationName = operation;
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        executor = new PseudoExecutor();
+
+        makeContext();
+
+        outcome = params.makeOutcome();
+        topicParams = BidirectionalTopicParams.builder().sinkTopic(MY_SINK).sourceTopic(MY_SOURCE).timeoutSec(TIMEOUT)
+                        .build();
+
+        initOperator();
+    }
+
+    /**
+     * Reinitializes {@link #enrichment}, {@link #event}, {@link #context}, and
+     * {@link #params}.
+     * <p/>
+     * Note: {@link #params} is configured to use {@link #executor}.
+     */
+    protected void makeContext() {
+        enrichment = new TreeMap<>(makeEnrichment());
+
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+        event.setAai(enrichment);
+
+        context = new ControlLoopEventContext(event);
+
+        params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service)
+                        .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).payload(makePayload())
+                        .build();
+    }
+
+    protected Map<String, String> makePayload() {
+        return null;
+    }
+
+    /**
+     * Initializes an operator so that it is "alive" and has the given names.
+     */
+    protected void initOperator() {
+        when(operator.isAlive()).thenReturn(true);
+        when(operator.getFullName()).thenReturn(actorName + "." + operationName);
+        when(operator.getActorName()).thenReturn(actorName);
+        when(operator.getName()).thenReturn(operationName);
+        when(operator.getTopicHandler()).thenReturn(topicHandler);
+        when(operator.getForwarder()).thenReturn(forwarder);
+        when(operator.getParams()).thenReturn(topicParams);
+    }
+
+    /**
+     * Makes enrichment data.
+     *
+     * @return enrichment data
+     */
+    protected Map<String, String> makeEnrichment() {
+        return new TreeMap<>();
+    }
+
+    /**
+     * Provides a response to the topic {@link #listenerCaptor}.
+     *
+     * @param listener listener to which to provide the response
+     * @param response response to be provided
+     */
+    protected void provideResponse(BiConsumer<String, StandardCoderObject> listener, String response) {
+        try {
+            StandardCoderObject sco = coder.decode(response, StandardCoderObject.class);
+            listener.accept(response, sco);
+
+        } catch (CoderException e) {
+            throw new IllegalArgumentException("response is not a Map", e);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
new file mode 100644
index 0000000..4929292
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
@@ -0,0 +1,190 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+
+/**
+ * Superclass for various HttpOperation tests.
+ *
+ * @param <Q> request type
+ */
+public class BasicHttpOperation<Q> {
+    protected static final UUID REQ_ID = UUID.randomUUID();
+    protected static final String DEFAULT_ACTOR = "default-actor";
+    protected static final String DEFAULT_OPERATION = "default-operation";
+    protected static final String MY_CLIENT = "my-client";
+    protected static final String BASE_URI = "/base-uri";
+    protected static final String PATH = "/my-path";
+    protected static final String TARGET_ENTITY = "my-target";
+
+    protected final String actorName;
+    protected final String operationName;
+
+    @Captor
+    protected ArgumentCaptor<InvocationCallback<Response>> callbackCaptor;
+
+    @Captor
+    protected ArgumentCaptor<Entity<Q>> requestCaptor;
+
+    @Captor
+    protected ArgumentCaptor<Map<String, Object>> headerCaptor;
+
+    @Mock
+    protected ActorService service;
+
+    @Mock
+    protected HttpClient client;
+
+    @Mock
+    protected HttpClientFactory factory;
+
+    @Mock
+    protected Response rawResponse;
+
+    @Mock
+    protected HttpOperator operator;
+
+    protected CompletableFuture<Response> future;
+    protected ControlLoopOperationParams params;
+    protected Map<String, String> enrichment;
+    protected VirtualControlLoopEvent event;
+    protected ControlLoopEventContext context;
+    protected OperationOutcome outcome;
+    protected PseudoExecutor executor;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicHttpOperation() {
+        this.actorName = DEFAULT_ACTOR;
+        this.operationName = DEFAULT_OPERATION;
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicHttpOperation(String actor, String operation) {
+        this.actorName = actor;
+        this.operationName = operation;
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(factory.get(MY_CLIENT)).thenReturn(client);
+
+        when(rawResponse.getStatus()).thenReturn(200);
+
+        future = new CompletableFuture<>();
+        when(client.getBaseUrl()).thenReturn(BASE_URI);
+
+        executor = new PseudoExecutor();
+
+        makeContext();
+
+        outcome = params.makeOutcome();
+
+        initOperator();
+    }
+
+    /**
+     * Reinitializes {@link #enrichment}, {@link #event}, {@link #context}, and
+     * {@link #params}.
+     * <p/>
+     * Note: {@link #params} is configured to use {@link #executor}.
+     */
+    protected void makeContext() {
+        enrichment = new TreeMap<>(makeEnrichment());
+
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+        event.setAai(enrichment);
+
+        context = new ControlLoopEventContext(event);
+
+        params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service)
+                        .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).build();
+    }
+
+    /**
+     * Initializes an operator so that it is "alive" and has the given names.
+     */
+    protected void initOperator() {
+        when(operator.isAlive()).thenReturn(true);
+        when(operator.getFullName()).thenReturn(actorName + "." + operationName);
+        when(operator.getActorName()).thenReturn(actorName);
+        when(operator.getName()).thenReturn(operationName);
+        when(operator.getClient()).thenReturn(client);
+        when(operator.getPath()).thenReturn(PATH);
+    }
+
+    /**
+     * Makes enrichment data.
+     *
+     * @return enrichment data
+     */
+    protected Map<String, String> makeEnrichment() {
+        return new TreeMap<>();
+    }
+
+    /**
+     * Provides a response to an asynchronous HttpClient call.
+     *
+     * @param response response to be provided to the call
+     * @return a function that provides the response to the call
+     */
+    protected Answer<CompletableFuture<Response>> provideResponse(Response response) {
+        return args -> {
+            InvocationCallback<Response> cb = args.getArgument(0);
+            cb.completed(response);
+            return CompletableFuture.completedFuture(response);
+        };
+    }
+}
diff --git a/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java
new file mode 100644
index 0000000..4fd5591
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java
@@ -0,0 +1,140 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.util.function.BiConsumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+public class BasicBidirectionalTopicOperationTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener;
+
+    private BasicBidirectionalTopicOperation oper;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        oper = new BasicBidirectionalTopicOperation(ACTOR, OPERATION);
+        oper.setUp();
+    }
+
+    @Test
+    public void testBasicBidirectionalTopicOperation() {
+        oper = new BasicBidirectionalTopicOperation();
+        assertEquals(BasicHttpOperation.DEFAULT_ACTOR, oper.actorName);
+        assertEquals(BasicHttpOperation.DEFAULT_OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testBasicBidirectionalTopicOperationStringString() {
+        assertEquals(ACTOR, oper.actorName);
+        assertEquals(OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testSetUp() {
+        assertNotNull(oper.topicParams);
+        assertNotNull(oper.context);
+        assertNotNull(oper.outcome);
+        assertNotNull(oper.executor);
+        assertTrue(oper.operator.isAlive());
+    }
+
+    @Test
+    public void testMakeContext() {
+        oper.makeContext();
+
+        assertTrue(oper.enrichment.isEmpty());
+
+        assertSame(BasicBidirectionalTopicOperation.REQ_ID, oper.event.getRequestId());
+        assertSame(oper.enrichment, oper.event.getAai());
+
+        assertSame(oper.event, oper.context.getEvent());
+
+        assertSame(oper.context, oper.params.getContext());
+        assertSame(oper.service, oper.params.getActorService());
+        assertSame(oper.executor, oper.params.getExecutor());
+        assertEquals(ACTOR, oper.params.getActor());
+        assertEquals(OPERATION, oper.params.getOperation());
+        assertEquals(BasicBidirectionalTopicOperation.TARGET_ENTITY, oper.params.getTargetEntity());
+    }
+
+    @Test
+    public void testMakePayload() {
+        assertNull(oper.makePayload());
+    }
+
+    @Test
+    public void testInitOperator() {
+        oper.initOperator();
+
+        assertTrue(oper.operator.isAlive());
+        assertEquals(ACTOR + "." + OPERATION, oper.operator.getFullName());
+        assertEquals(ACTOR, oper.operator.getActorName());
+        assertEquals(OPERATION, oper.operator.getName());
+        assertSame(oper.topicHandler, oper.operator.getTopicHandler());
+        assertSame(oper.forwarder, oper.operator.getForwarder());
+        assertSame(oper.topicParams, oper.operator.getParams());
+    }
+
+    @Test
+    public void testMakeEnrichment() {
+        assertTrue(oper.makeEnrichment().isEmpty());
+    }
+
+    @Test
+    public void testProvideResponse() {
+        String response = "{\"input\": 10}";
+
+        oper.provideResponse(listener, response);
+
+        ArgumentCaptor<StandardCoderObject> scoCaptor = ArgumentCaptor.forClass(StandardCoderObject.class);
+        verify(listener).accept(eq(response), scoCaptor.capture());
+
+        assertEquals("10", scoCaptor.getValue().getString("input"));
+
+        // try with an invalid response
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.provideResponse(listener, "{invalid json"))
+                        .withMessage("response is not a Map");
+    }
+}
diff --git a/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
new file mode 100644
index 0000000..096b8b8
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BasicHttpOperationTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+
+    private BasicHttpOperation<String> oper;
+
+
+    @Before
+    public void setUp() throws Exception {
+        oper = new BasicHttpOperation<>(ACTOR, OPERATION);
+        oper.setUp();
+    }
+
+    @Test
+    public void testBasicHttpOperation() {
+        oper = new BasicHttpOperation<>();
+        assertEquals(BasicHttpOperation.DEFAULT_ACTOR, oper.actorName);
+        assertEquals(BasicHttpOperation.DEFAULT_OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testBasicHttpOperationStringString() {
+        assertEquals(ACTOR, oper.actorName);
+        assertEquals(OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testSetUp() throws Exception {
+        assertNotNull(oper.client);
+        assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT));
+        assertEquals(200, oper.rawResponse.getStatus());
+        assertNotNull(oper.future);
+        assertEquals(BasicHttpOperation.BASE_URI, oper.client.getBaseUrl());
+        assertNotNull(oper.context);
+        assertNotNull(oper.outcome);
+        assertNotNull(oper.executor);
+        assertTrue(oper.operator.isAlive());
+    }
+
+    @Test
+    public void testMakeContext() {
+        oper.makeContext();
+
+        assertTrue(oper.enrichment.isEmpty());
+
+        assertSame(BasicHttpOperation.REQ_ID, oper.event.getRequestId());
+        assertSame(oper.enrichment, oper.event.getAai());
+
+        assertSame(oper.event, oper.context.getEvent());
+
+        assertSame(oper.context, oper.params.getContext());
+        assertSame(oper.service, oper.params.getActorService());
+        assertSame(oper.executor, oper.params.getExecutor());
+        assertEquals(ACTOR, oper.params.getActor());
+        assertEquals(OPERATION, oper.params.getOperation());
+        assertEquals(BasicHttpOperation.TARGET_ENTITY, oper.params.getTargetEntity());
+    }
+
+    @Test
+    public void testInitOperator() throws Exception {
+        oper.initOperator();
+
+        assertTrue(oper.operator.isAlive());
+        assertEquals(ACTOR + "." + OPERATION, oper.operator.getFullName());
+        assertEquals(ACTOR, oper.operator.getActorName());
+        assertEquals(OPERATION, oper.operator.getName());
+        assertSame(oper.client, oper.operator.getClient());
+        assertEquals(BasicHttpOperation.PATH, oper.operator.getPath());
+    }
+
+    @Test
+    public void testMakeEnrichment() {
+        assertTrue(oper.makeEnrichment().isEmpty());
+    }
+
+    @Test
+    public void testProvideResponse() throws Exception {
+        InvocationCallback<Response> cb = new InvocationCallback<>() {
+            @Override
+            public void completed(Response response) {
+                // do nothing
+            }
+
+            @Override
+            public void failed(Throwable throwable) {
+                // do nothing
+            }
+        };
+
+
+        when(oper.client.get(any(), any(), any())).thenAnswer(oper.provideResponse(oper.rawResponse));
+
+        assertSame(oper.rawResponse, oper.client.get(cb, null, null).get());
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
index 2886b1f..24c2cfc 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
@@ -40,7 +40,7 @@
  * {@link #start()} to start all of the actors. When finished using the actor service,
  * invoke {@link #stop()} or {@link #shutdown()}.
  */
-public class ActorService extends StartConfigPartial<Map<String, Object>> {
+public class ActorService extends StartConfigPartial<Map<String, Map<String, Object>>> {
     private static final Logger logger = LoggerFactory.getLogger(ActorService.class);
 
     private final Map<String, Actor> name2actor;
@@ -116,14 +116,14 @@
     }
 
     @Override
-    protected void doConfigure(Map<String, Object> parameters) {
+    protected void doConfigure(Map<String, Map<String, Object>> parameters) {
         logger.info("configuring actors");
 
         BeanValidationResult valres = new BeanValidationResult("ActorService", parameters);
 
         for (Actor actor : name2actor.values()) {
             String actorName = actor.getName();
-            Map<String, Object> subparams = Util.translateToMap(actorName, parameters.get(actorName));
+            Map<String, Object> subparams = parameters.get(actorName);
 
             if (subparams != null) {
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java
deleted file mode 100644
index d784038..0000000
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actorserviceprovider;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import javax.ws.rs.client.InvocationCallback;
-import lombok.AccessLevel;
-import lombok.Getter;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Handler for a <i>single</i> asynchronous response.
- *
- * @param <T> response type
- */
-@Getter
-public abstract class AsyncResponseHandler<T> implements InvocationCallback<T> {
-
-    private static final Logger logger = LoggerFactory.getLogger(AsyncResponseHandler.class);
-
-    @Getter(AccessLevel.NONE)
-    private final PipelineControllerFuture<OperationOutcome> result = new PipelineControllerFuture<>();
-    private final ControlLoopOperationParams params;
-    private final OperationOutcome outcome;
-
-    /**
-     * Constructs the object.
-     *
-     * @param params operation parameters
-     * @param outcome outcome to be populated based on the response
-     */
-    public AsyncResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
-        this.params = params;
-        this.outcome = outcome;
-    }
-
-    /**
-     * Handles the given future, arranging to cancel it when the response is received.
-     *
-     * @param future future to be handled
-     * @return a future to be used to cancel or wait for the response
-     */
-    public CompletableFuture<OperationOutcome> handle(Future<T> future) {
-        result.add(future);
-        return result;
-    }
-
-    /**
-     * Invokes {@link #doComplete()} and then completes "this" with the returned value.
-     */
-    @Override
-    public void completed(T rawResponse) {
-        try {
-            logger.trace("{}.{}: response completed for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.complete(doComplete(rawResponse));
-
-        } catch (RuntimeException e) {
-            logger.trace("{}.{}: response handler threw an exception for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.completeExceptionally(e);
-        }
-    }
-
-    /**
-     * Invokes {@link #doFailed()} and then completes "this" with the returned value.
-     */
-    @Override
-    public void failed(Throwable throwable) {
-        try {
-            logger.trace("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.complete(doFailed(throwable));
-
-        } catch (RuntimeException e) {
-            logger.trace("{}.{}: response failure handler threw an exception for {}", params.getActor(),
-                            params.getOperation(), params.getRequestId());
-            result.completeExceptionally(e);
-        }
-    }
-
-    /**
-     * Completes the processing of a response.
-     *
-     * @param rawResponse raw response that was received
-     * @return the outcome
-     */
-    protected abstract OperationOutcome doComplete(T rawResponse);
-
-    /**
-     * Handles a response exception.
-     *
-     * @param thrown exception that was thrown
-     * @return the outcome
-     */
-    protected abstract OperationOutcome doFailed(Throwable thrown);
-}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java
new file mode 100644
index 0000000..39977fd
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * This is the service interface for defining an Actor operation used in Control Loop
+ * Operational Policies for performing actions on runtime entities.
+ */
+public interface Operation {
+
+    /**
+     * Gets the name of the associated actor.
+     *
+     * @return the name of the associated actor
+     */
+    String getActorName();
+
+    /**
+     * Gets the name of the operation.
+     *
+     * @return the operation name
+     */
+    String getName();
+
+    /**
+     * Called by enforcement PDP engine to start the operation. As part of the operation,
+     * it invokes the "start" and "complete" call-backs found within the parameters.
+     *
+     * @return a future that can be used to cancel or await the result of the operation
+     */
+    CompletableFuture<OperationOutcome> start();
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
index c09460e..24faafd 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
@@ -21,7 +21,6 @@
 package org.onap.policy.controlloop.actorserviceprovider;
 
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import org.onap.policy.common.capabilities.Configurable;
 import org.onap.policy.common.capabilities.Startable;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
@@ -47,11 +46,10 @@
     String getName();
 
     /**
-     * Called by enforcement PDP engine to start the operation. As part of the operation,
-     * it invokes the "start" and "complete" call-backs found within the parameters.
+     * Called by enforcement PDP engine to build the operation.
      *
-     * @param params parameters needed to start the operation
-     * @return a future that can be used to cancel or await the result of the operation
+     * @param params parameters needed by the operation
+     * @return a new operation
      */
-    CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params);
+    Operation buildOperation(ControlLoopOperationParams params);
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
index c3ddd17..ba47859 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
@@ -23,9 +23,6 @@
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 import org.onap.policy.common.utils.coder.Coder;
 import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.common.utils.coder.StandardCoder;
@@ -37,6 +34,7 @@
  */
 public class Util {
     private static final Logger logger = LoggerFactory.getLogger(Util.class);
+    private static final Coder coder = new StandardCoder();
 
     private Util() {
         // do nothing
@@ -56,82 +54,6 @@
     }
 
     /**
-     * Logs a REST request. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param url request URL
-     * @param request request to be logged
-     */
-    public static <T> void logRestRequest(String url, T request) {
-        logRestRequest(new StandardCoder(), url, request);
-    }
-
-    /**
-     * Logs a REST request. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param coder coder to be used to pretty-print the request
-     * @param url request URL
-     * @param request request to be logged
-     */
-    protected static <T> void logRestRequest(Coder coder, String url, T request) {
-        String json;
-        try {
-            if (request instanceof String) {
-                json = request.toString();
-            } else {
-                json = coder.encode(request, true);
-            }
-
-        } catch (CoderException e) {
-            logger.warn("cannot pretty-print request", e);
-            json = request.toString();
-        }
-
-        NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json);
-        logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
-    }
-
-    /**
-     * Logs a REST response. If the response is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param url request URL
-     * @param response response to be logged
-     */
-    public static <T> void logRestResponse(String url, T response) {
-        logRestResponse(new StandardCoder(), url, response);
-    }
-
-    /**
-     * Logs a REST response. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param coder coder to be used to pretty-print the response
-     * @param url request URL
-     * @param response response to be logged
-     */
-    protected static <T> void logRestResponse(Coder coder, String url, T response) {
-        String json;
-        try {
-            if (response == null) {
-                json = null;
-            } else if (response instanceof String) {
-                json = response.toString();
-            } else {
-                json = coder.encode(response, true);
-            }
-
-        } catch (CoderException e) {
-            logger.warn("cannot pretty-print response", e);
-            json = response.toString();
-        }
-
-        NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json);
-        logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
-    }
-
-    /**
      * Runs a function and logs a message if it throws an exception. Does <i>not</i>
      * re-throw the exception.
      *
@@ -163,11 +85,8 @@
      * @return the translated object
      */
     public static <T> T translate(String identifier, Object source, Class<T> clazz) {
-        Coder coder = new StandardCoder();
-
         try {
-            String json = coder.encode(source);
-            return coder.decode(json, clazz);
+            return coder.convert(source, clazz);
 
         } catch (CoderException | RuntimeException e) {
             throw new IllegalArgumentException("cannot translate parameters for " + identifier, e);
@@ -184,10 +103,6 @@
      */
     @SuppressWarnings("unchecked")
     public static Map<String, Object> translateToMap(String identifier, Object source) {
-        if (source == null) {
-            return null;
-        }
-
         return translate(identifier, source, LinkedHashMap.class);
     }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
index cd4d257..3e02da6 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
@@ -23,12 +23,15 @@
 import java.io.Serializable;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
 import lombok.Setter;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 
 /**
  * Context associated with a control loop event.
@@ -47,11 +50,23 @@
      */
     private final Map<String, String> enrichment;
 
+    /**
+     * Set of properties that have been stored in the context.
+     */
     @Getter(AccessLevel.NONE)
     @Setter(AccessLevel.NONE)
     private Map<String, Serializable> properties = new ConcurrentHashMap<>();
 
     /**
+     * When {@link #obtain(String, ControlLoopOperationParams)} is invoked and the
+     * specified property is not found in {@link #properties}, it is retrieved. This holds
+     * the futures for the operations retrieving the properties.
+     */
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
+    private transient Map<String, CompletableFuture<OperationOutcome>> retrievers = new ConcurrentHashMap<>();
+
+    /**
      * Request ID extracted from the event, or a generated value if the event has no
      * request id; never {@code null}.
      */
@@ -100,4 +115,46 @@
     public void setProperty(String name, Serializable value) {
         properties.put(name, value);
     }
+
+    /**
+     * Obtains the given property.
+     *
+     * @param name name of the desired property
+     * @param params parameters needed to perform the operation to retrieve the desired
+     *        property
+     * @return a future for retrieving the property, {@code null} if the property has
+     *         already been retrieved
+     */
+    public CompletableFuture<OperationOutcome> obtain(String name, ControlLoopOperationParams params) {
+        if (properties.containsKey(name)) {
+            return null;
+        }
+
+        /*
+         * Return any existing future, if it wasn't canceled. Otherwise, start a new
+         * request.
+         */
+
+        // @formatter:off
+        CompletableFuture<OperationOutcome> oldFuture =
+            retrievers.compute(name, (key, future) -> (future == null || future.isCancelled() ? null : future));
+        // @formatter:on
+
+        if (oldFuture != null) {
+            return oldFuture;
+        }
+
+        /*
+         * Note: must NOT invoke params.start() within retrievers.compute(), as start()
+         * may invoke obtain() which would cause a recursive update to the retrievers map.
+         */
+        CompletableFuture<OperationOutcome> future = params.start();
+
+        if ((oldFuture = retrievers.putIfAbsent(name, future)) != null) {
+            future.cancel(false);
+            return oldFuture;
+        }
+
+        return future;
+    }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
index d7f322e..0c88ebe 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
@@ -91,7 +91,7 @@
     public Operator getOperator(String name) {
         Operator operator = name2operator.get(name);
         if (operator == null) {
-            throw new IllegalArgumentException("unknown operation " + getName() + "." + name);
+            throw new IllegalArgumentException("unknown operator " + getName() + "." + name);
         }
 
         return operator;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java
new file mode 100644
index 0000000..1e44a17
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java
@@ -0,0 +1,108 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+
+/**
+ * Actor that uses a bidirectional topic. The actor's parameters must be a
+ * {@link BidirectionalTopicActorParams}.
+ */
+public class BidirectionalTopicActor extends ActorImpl implements BidirectionalTopicManager {
+
+    /**
+     * Maps a pair of sink and source topic names to their bidirectional topic.
+     */
+    private final Map<Pair<String, String>, BidirectionalTopicHandler> params2topic = new ConcurrentHashMap<>();
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param name actor's name
+     */
+    public BidirectionalTopicActor(String name) {
+        super(name);
+    }
+
+    @Override
+    protected void doStart() {
+        params2topic.values().forEach(BidirectionalTopicHandler::start);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() {
+        params2topic.values().forEach(BidirectionalTopicHandler::stop);
+        super.doStop();
+    }
+
+    @Override
+    protected void doShutdown() {
+        params2topic.values().forEach(BidirectionalTopicHandler::shutdown);
+        params2topic.clear();
+        super.doShutdown();
+    }
+
+    @Override
+    public BidirectionalTopicHandler getTopicHandler(String sinkTopic, String sourceTopic) {
+        Pair<String, String> key = Pair.of(sinkTopic, sourceTopic);
+
+        return params2topic.computeIfAbsent(key, pair -> {
+            try {
+                return makeTopicHandler(sinkTopic, sourceTopic);
+            } catch (BidirectionalTopicClientException e) {
+                throw new IllegalArgumentException(e);
+            }
+        });
+    }
+
+    /**
+     * Translates the parameters to a {@link BidirectionalTopicActorParams} and then
+     * creates a function that will extract operator-specific parameters.
+     */
+    @Override
+    protected Function<String, Map<String, Object>> makeOperatorParameters(Map<String, Object> actorParameters) {
+        String actorName = getName();
+
+        // @formatter:off
+        return Util.translate(actorName, actorParameters, BidirectionalTopicActorParams.class)
+                        .doValidation(actorName)
+                        .makeOperationParameters(actorName);
+        // @formatter:on
+    }
+
+    // may be overridden by junit tests
+
+    protected BidirectionalTopicHandler makeTopicHandler(String sinkTopic, String sourceTopic)
+                    throws BidirectionalTopicClientException {
+
+        return new BidirectionalTopicHandler(sinkTopic, sourceTopic);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java
new file mode 100644
index 0000000..d1e21f8
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java
@@ -0,0 +1,265 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operation that uses a bidirectional topic.
+ *
+ * @param <S> response type
+ */
+@Getter
+public abstract class BidirectionalTopicOperation<Q, S> extends OperationPartial {
+    private static final Logger logger = LoggerFactory.getLogger(BidirectionalTopicOperation.class);
+
+    /**
+     * Response status.
+     */
+    public enum Status {
+        SUCCESS, FAILURE, STILL_WAITING
+    }
+
+    // fields extracted from the operator
+
+    private final BidirectionalTopicHandler topicHandler;
+    private final Forwarder forwarder;
+    private final BidirectionalTopicParams topicParams;
+    private final long timeoutMs;
+
+    /**
+     * Response class.
+     */
+    private final Class<S> responseClass;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     * @param clazz response class
+     */
+    public BidirectionalTopicOperation(ControlLoopOperationParams params, BidirectionalTopicOperator operator,
+                    Class<S> clazz) {
+        super(params, operator);
+        this.topicHandler = operator.getTopicHandler();
+        this.forwarder = operator.getForwarder();
+        this.topicParams = operator.getParams();
+        this.responseClass = clazz;
+        this.timeoutMs = TimeUnit.MILLISECONDS.convert(topicParams.getTimeoutSec(), TimeUnit.SECONDS);
+    }
+
+    /**
+     * If no timeout is specified, then it returns the default timeout.
+     */
+    @Override
+    protected long getTimeoutMs(Integer timeoutSec) {
+        // TODO move this method to the superclass
+        return (timeoutSec == null || timeoutSec == 0 ? this.timeoutMs : super.getTimeoutMs(timeoutSec));
+    }
+
+    /**
+     * Publishes the request and arranges to receive the response.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        final Q request = makeRequest(attempt);
+        final List<String> expectedKeyValues = getExpectedKeyValues(attempt, request);
+
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        final Executor executor = params.getExecutor();
+
+        // register a listener BEFORE publishing
+
+        BiConsumer<String, StandardCoderObject> listener = (rawResponse, scoResponse) -> {
+            OperationOutcome latestOutcome = processResponse(outcome, rawResponse, scoResponse);
+            if (latestOutcome != null) {
+                // final response - complete the controller
+                controller.completeAsync(() -> latestOutcome, executor);
+            }
+        };
+
+        forwarder.register(expectedKeyValues, listener);
+
+        // ensure listener is unregistered if the controller is canceled
+        controller.add(() -> forwarder.unregister(expectedKeyValues, listener));
+
+        // publish the request
+        try {
+            publishRequest(request);
+        } catch (RuntimeException e) {
+            logger.warn("{}: failed to publish request for {}", getFullName(), params.getRequestId());
+            forwarder.unregister(expectedKeyValues, listener);
+            throw e;
+        }
+
+        return controller;
+    }
+
+    /**
+     * Makes the request.
+     *
+     * @param attempt operation attempt
+     * @return a new request
+     */
+    protected abstract Q makeRequest(int attempt);
+
+    /**
+     * Gets values, expected in the response, that should match the selector keys.
+     *
+     * @param attempt operation attempt
+     * @param request request to be published
+     * @return a list of the values to be matched by the selector keys
+     */
+    protected abstract List<String> getExpectedKeyValues(int attempt, Q request);
+
+    /**
+     * Publishes the request. Encodes the request, if it is not already a String.
+     *
+     * @param request request to be published
+     */
+    protected void publishRequest(Q request) {
+        String json;
+        try {
+            if (request instanceof String) {
+                json = request.toString();
+            } else {
+                json = makeCoder().encode(request);
+            }
+        } catch (CoderException e) {
+            throw new IllegalArgumentException("cannot encode request", e);
+        }
+
+        if (!topicHandler.send(json)) {
+            throw new IllegalStateException("nothing published");
+        }
+
+        logMessage(EventType.OUT, topicHandler.getSinkTopicCommInfrastructure(), topicHandler.getSinkTopic(), request);
+    }
+
+    /**
+     * Processes a response.
+     *
+     * @param infra communication infrastructure on which the response was received
+     * @param outcome outcome to be populated
+     * @param response raw response to process
+     * @param scoResponse response, as a {@link StandardCoderObject}
+     * @return the outcome, or {@code null} if still waiting for completion
+     */
+    protected OperationOutcome processResponse(OperationOutcome outcome, String rawResponse,
+                    StandardCoderObject scoResponse) {
+
+        logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+        logMessage(EventType.IN, topicHandler.getSourceTopicCommInfrastructure(), topicHandler.getSourceTopic(),
+                        rawResponse);
+
+        // decode the response
+        S response;
+        if (responseClass == String.class) {
+            response = responseClass.cast(rawResponse);
+
+        } else if (responseClass == StandardCoderObject.class) {
+            response = responseClass.cast(scoResponse);
+
+        } else {
+            try {
+                response = makeCoder().decode(rawResponse, responseClass);
+            } catch (CoderException e) {
+                logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                throw new IllegalArgumentException("cannot decode response", e);
+            }
+        }
+
+        // check its status
+        switch (detmStatus(rawResponse, response)) {
+            case SUCCESS:
+                logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                setOutcome(outcome, PolicyResult.SUCCESS, response);
+                postProcessResponse(outcome, rawResponse, response);
+                return outcome;
+
+            case FAILURE:
+                logger.info("{}.{} request failed for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                return setOutcome(outcome, PolicyResult.FAILURE, response);
+
+            case STILL_WAITING:
+            default:
+                logger.info("{}.{} request incomplete for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                return null;
+        }
+    }
+
+    /**
+     * Sets an operation's outcome and default message based on the result.
+     *
+     * @param outcome operation to be updated
+     * @param result result of the operation
+     * @param response response used to populate the outcome
+     * @return the updated operation
+     */
+    public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, S response) {
+        return setOutcome(outcome, result);
+    }
+
+    /**
+     * Processes a successful response.
+     *
+     * @param outcome outcome to be populated
+     * @param rawResponse raw response
+     * @param response decoded response
+     */
+    protected void postProcessResponse(OperationOutcome outcome, String rawResponse, S response) {
+        // do nothing
+    }
+
+    /**
+     * Determines the status of the response.
+     *
+     * @param rawResponse raw response
+     * @param response decoded response
+     * @return the status of the response
+     */
+    protected abstract Status detmStatus(String rawResponse, S response);
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java
new file mode 100644
index 0000000..51689e4
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java
@@ -0,0 +1,160 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import lombok.Getter;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+
+/**
+ * Operator that uses a bidirectional topic. Topic operators may share a
+ * {@link BidirectionalTopicHandler}.
+ */
+public abstract class BidirectionalTopicOperator extends OperatorPartial {
+
+    /**
+     * Manager from which to get the topic handlers.
+     */
+    private final BidirectionalTopicManager topicManager;
+
+    /**
+     * Keys used to extract the fields used to select responses for this operator.
+     */
+    private final List<SelectorKey> selectorKeys;
+
+    /*
+     * The remaining fields are initialized when configure() is invoked, thus they may
+     * change.
+     */
+
+    /**
+     * Current parameters. While {@link params} may change, the values contained within it
+     * will not, thus operations may copy it.
+     */
+    @Getter
+    private BidirectionalTopicParams params;
+
+    /**
+     * Topic handler associated with the parameters.
+     */
+    @Getter
+    private BidirectionalTopicHandler topicHandler;
+
+    /**
+     * Forwarder associated with the parameters.
+     */
+    @Getter
+    private Forwarder forwarder;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param actorName name of the actor with which this operator is associated
+     * @param name operation name
+     * @param topicManager manager from which to get the topic handler
+     * @param selectorKeys keys used to extract the fields used to select responses for
+     *        this operator
+     */
+    public BidirectionalTopicOperator(String actorName, String name, BidirectionalTopicManager topicManager,
+                    List<SelectorKey> selectorKeys) {
+        super(actorName, name);
+        this.topicManager = topicManager;
+        this.selectorKeys = selectorKeys;
+    }
+
+    @Override
+    protected void doConfigure(Map<String, Object> parameters) {
+        params = Util.translate(getFullName(), parameters, BidirectionalTopicParams.class);
+        ValidationResult result = params.validate(getFullName());
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        topicHandler = topicManager.getTopicHandler(params.getSinkTopic(), params.getSourceTopic());
+        forwarder = topicHandler.addForwarder(selectorKeys);
+    }
+
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <Q> request type
+     * @param <S> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param topicManager manager from which to get the topic handler
+     * @param operationMaker function to make an operation
+     * @param keys keys used to extract the fields used to select responses for this
+     *        operator
+     * @return a new operator
+     */
+    // @formatter:off
+    public static <Q, S> BidirectionalTopicOperator makeOperator(String actorName, String operation,
+                    BidirectionalTopicManager topicManager,
+                    BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+                        BidirectionalTopicOperation<Q, S>> operationMaker,
+                    SelectorKey... keys) {
+        // @formatter:off
+
+        return makeOperator(actorName, operation, topicManager, Arrays.asList(keys), operationMaker);
+    }
+
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <Q> request type
+     * @param <S> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param topicManager manager from which to get the topic handler
+     * @param keys keys used to extract the fields used to select responses for
+     *        this operator
+     * @param operationMaker function to make an operation
+     * @return a new operator
+     */
+    // @formatter:off
+    public static <Q,S> BidirectionalTopicOperator makeOperator(String actorName, String operation,
+                    BidirectionalTopicManager topicManager,
+                    List<SelectorKey> keys,
+                    BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+                        BidirectionalTopicOperation<Q,S>> operationMaker) {
+        // @formatter:on
+
+        return new BidirectionalTopicOperator(actorName, operation, topicManager, keys) {
+            @Override
+            public synchronized Operation buildOperation(ControlLoopOperationParams params) {
+                return operationMaker.apply(params, this);
+            }
+        };
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
new file mode 100644
index 0000000..c3c0f6d
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
@@ -0,0 +1,245 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
+ *
+ * @param <T> response type
+ */
+@Getter
+public abstract class HttpOperation<T> extends OperationPartial {
+    private static final Logger logger = LoggerFactory.getLogger(HttpOperation.class);
+
+    /**
+     * Operator that created this operation.
+     */
+    protected final HttpOperator operator;
+
+    /**
+     * Response class.
+     */
+    private final Class<T> responseClass;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     * @param clazz response class
+     */
+    public HttpOperation(ControlLoopOperationParams params, HttpOperator operator, Class<T> clazz) {
+        super(params, operator);
+        this.operator = operator;
+        this.responseClass = clazz;
+    }
+
+    /**
+     * If no timeout is specified, then it returns the operator's configured timeout.
+     */
+    @Override
+    protected long getTimeoutMs(Integer timeoutSec) {
+        return (timeoutSec == null || timeoutSec == 0 ? operator.getTimeoutMs() : super.getTimeoutMs(timeoutSec));
+    }
+
+    /**
+     * Makes the request headers. This simply returns an empty map.
+     *
+     * @return request headers, a non-null, modifiable map
+     */
+    protected Map<String, Object> makeHeaders() {
+        return new HashMap<>();
+    }
+
+    /**
+     * Gets the path to be used when performing the request; this is typically appended to
+     * the base URL. This method simply invokes {@link #getPath()}.
+     *
+     * @return the path URI suffix
+     */
+    public String makePath() {
+        return operator.getPath();
+    }
+
+    /**
+     * Makes the URL to which the "get" request should be posted. This ir primarily used
+     * for logging purposes. This particular method returns the base URL appended with the
+     * return value from {@link #makePath()}.
+     *
+     * @return the URL to which from which to get
+     */
+    public String makeUrl() {
+        return (operator.getClient().getBaseUrl() + makePath());
+    }
+
+    /**
+     * Arranges to handle a response.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param requester function to initiate the request and invoke the given callback
+     *        when it completes
+     * @return a future for the response
+     */
+    protected CompletableFuture<OperationOutcome> handleResponse(OperationOutcome outcome, String url,
+                    Function<InvocationCallback<Response>, Future<Response>> requester) {
+
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        final CompletableFuture<Response> future = new CompletableFuture<>();
+        final Executor executor = params.getExecutor();
+
+        // arrange for the callback to complete "future"
+        InvocationCallback<Response> callback = new InvocationCallback<>() {
+            @Override
+            public void completed(Response response) {
+                future.complete(response);
+            }
+
+            @Override
+            public void failed(Throwable throwable) {
+                logger.warn("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                future.completeExceptionally(throwable);
+            }
+        };
+
+        // start the request and arrange to cancel it if the controller is canceled
+        controller.add(requester.apply(callback));
+
+        // once "future" completes, process the response, and then complete the controller
+        future.thenComposeAsync(response -> processResponse(outcome, url, response), executor)
+                        .whenCompleteAsync(controller.delayedComplete(), executor);
+
+        return controller;
+    }
+
+    /**
+     * Processes a response. This method decodes the response, sets the outcome based on
+     * the response, and then returns a completed future.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param response raw response to process
+     * @return a future to cancel or await the outcome
+     */
+    protected CompletableFuture<OperationOutcome> processResponse(OperationOutcome outcome, String url,
+                    Response rawResponse) {
+
+        logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+        String strResponse = HttpClient.getBody(rawResponse, String.class);
+
+        logMessage(EventType.IN, CommInfrastructure.REST, url, strResponse);
+
+        T response;
+        if (responseClass == String.class) {
+            response = responseClass.cast(strResponse);
+        } else {
+            try {
+                response = makeCoder().decode(strResponse, responseClass);
+            } catch (CoderException e) {
+                logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId(), e);
+                throw new IllegalArgumentException("cannot decode response");
+            }
+        }
+
+        if (!isSuccess(rawResponse, response)) {
+            logger.info("{}.{} request failed with http error code {} for {}", params.getActor(), params.getOperation(),
+                            rawResponse.getStatus(), params.getRequestId());
+            return CompletableFuture.completedFuture(setOutcome(outcome, PolicyResult.FAILURE, response));
+        }
+
+        logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(), params.getRequestId());
+        setOutcome(outcome, PolicyResult.SUCCESS, response);
+        return postProcessResponse(outcome, url, rawResponse, response);
+    }
+
+    /**
+     * Sets an operation's outcome and default message based on the result.
+     *
+     * @param outcome operation to be updated
+     * @param result result of the operation
+     * @param response response used to populate the outcome
+     * @return the updated operation
+     */
+    public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, T response) {
+        return setOutcome(outcome, result);
+    }
+
+    /**
+     * Processes a successful response. This method simply returns the outcome wrapped in
+     * a completed future.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param rawResponse raw response
+     * @param response decoded response
+     * @return a future to cancel or await the outcome
+     */
+    protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
+                    Response rawResponse, T response) {
+
+        return CompletableFuture.completedFuture(outcome);
+    }
+
+    /**
+     * Determines if the response indicates success. This method simply checks the HTTP
+     * status code.
+     *
+     * @param rawResponse raw response
+     * @param response decoded response
+     * @return {@code true} if the response indicates success, {@code false} otherwise
+     */
+    protected boolean isSuccess(Response rawResponse, T response) {
+        return (rawResponse.getStatus() == 200);
+    }
+
+    @Override
+    public <Q> String logMessage(EventType direction, CommInfrastructure infra, String sink, Q request) {
+        String json = super.logMessage(direction, infra, sink, request);
+        NetLoggerUtil.log(direction, infra, sink, json);
+        return json;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
index 5664929..add74aa 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
@@ -21,31 +21,35 @@
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
 import java.util.Map;
-import lombok.AccessLevel;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
 import lombok.Getter;
 import org.onap.policy.common.endpoints.http.client.HttpClient;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
 import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
 /**
- * Operator that uses HTTP. The operator's parameters must be a {@link HttpParams}.
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
  */
-public class HttpOperator extends OperatorPartial {
+@Getter
+public abstract class HttpOperator extends OperatorPartial {
 
-    @Getter(AccessLevel.PROTECTED)
     private HttpClient client;
 
-    @Getter
-    private long timeoutSec;
+    /**
+     * Default timeout, in milliseconds, if none specified in the request.
+     */
+    private long timeoutMs;
 
     /**
-     * URI path for this particular operation.
+     * URI path for this particular operation. Includes a leading "/".
      */
-    @Getter
     private String path;
 
 
@@ -60,6 +64,26 @@
     }
 
     /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <T> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param operationMaker function to make an operation
+     * @return a new operator
+     */
+    public static <T> HttpOperator makeOperator(String actorName, String operation,
+                    BiFunction<ControlLoopOperationParams, HttpOperator, HttpOperation<T>> operationMaker) {
+
+        return new HttpOperator(actorName, operation) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return operationMaker.apply(params, this);
+            }
+        };
+    }
+
+    /**
      * Translates the parameters to an {@link HttpParams} and then extracts the relevant
      * values.
      */
@@ -73,10 +97,10 @@
 
         client = getClientFactory().get(params.getClientName());
         path = params.getPath();
-        timeoutSec = params.getTimeoutSec();
+        timeoutMs = TimeUnit.MILLISECONDS.convert(params.getTimeoutSec(), TimeUnit.SECONDS);
     }
 
-    // these may be overridden by junits
+    // these may be overridden by junit tests
 
     protected HttpClientFactory getClientFactory() {
         return HttpClientFactoryInstance.getClientFactory();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
new file mode 100644
index 0000000..680a56f
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
@@ -0,0 +1,983 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Partial implementation of an operator. In general, it's preferable that subclasses
+ * would override {@link #startOperationAsync(int, OperationOutcome)
+ * startOperationAsync()}. However, if that proves to be too difficult, then they can
+ * simply override {@link #doOperation(int, OperationOutcome) doOperation()}. In addition,
+ * if the operation requires any preprocessor steps, the subclass may choose to override
+ * {@link #startPreprocessorAsync()}.
+ * <p/>
+ * The futures returned by the methods within this class can be canceled, and will
+ * propagate the cancellation to any subtasks. Thus it is also expected that any futures
+ * returned by overridden methods will do the same. Of course, if a class overrides
+ * {@link #doOperation(int, OperationOutcome) doOperation()}, then there's little that can
+ * be done to cancel that particular operation.
+ */
+public abstract class OperationPartial implements Operation {
+    private static final Logger logger = LoggerFactory.getLogger(OperationPartial.class);
+    private static final Coder coder = new StandardCoder();
+
+    public static final long DEFAULT_RETRY_WAIT_MS = 1000L;
+
+    // values extracted from the operator
+
+    private final OperatorPartial operator;
+
+    /**
+     * Operation parameters.
+     */
+    protected final ControlLoopOperationParams params;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public OperationPartial(ControlLoopOperationParams params, OperatorPartial operator) {
+        this.params = params;
+        this.operator = operator;
+    }
+
+    public Executor getBlockingExecutor() {
+        return operator.getBlockingExecutor();
+    }
+
+    public String getFullName() {
+        return operator.getFullName();
+    }
+
+    public String getActorName() {
+        return operator.getActorName();
+    }
+
+    public String getName() {
+        return operator.getName();
+    }
+
+    @Override
+    public final CompletableFuture<OperationOutcome> start() {
+        if (!operator.isAlive()) {
+            throw new IllegalStateException("operation is not running: " + getFullName());
+        }
+
+        // allocate a controller for the entire operation
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync();
+        if (preproc == null) {
+            // no preprocessor required - just start the operation
+            return startOperationAttempt(controller, 1);
+        }
+
+        /*
+         * Do preprocessor first and then, if successful, start the operation. Note:
+         * operations create their own outcome, ignoring the outcome from any previous
+         * steps.
+         *
+         * Wrap the preprocessor to ensure "stop" is propagated to it.
+         */
+        // @formatter:off
+        controller.wrap(preproc)
+                        .exceptionally(fromException("preprocessor of operation"))
+                        .thenCompose(handlePreprocessorFailure(controller))
+                        .thenCompose(unusedOutcome -> startOperationAttempt(controller, 1))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
+     * invokes the call-backs, marks the controller complete, and returns an incomplete
+     * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
+     * received.
+     * <p/>
+     * Assumes that no callbacks have been invoked yet.
+     *
+     * @param controller pipeline controller
+     * @return a function that checks the outcome status and continues, if successful, or
+     *         indicates a failure otherwise
+     */
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
+                    PipelineControllerFuture<OperationOutcome> controller) {
+
+        return outcome -> {
+
+            if (outcome != null && isSuccess(outcome)) {
+                logger.info("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
+                return CompletableFuture.completedFuture(outcome);
+            }
+
+            logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
+
+            final Executor executor = params.getExecutor();
+            final CallbackManager callbacks = new CallbackManager();
+
+            // propagate "stop" to the callbacks
+            controller.add(callbacks);
+
+            final OperationOutcome outcome2 = params.makeOutcome();
+
+            // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
+
+            outcome2.setResult(PolicyResult.FAILURE_GUARD);
+            outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
+
+            // @formatter:off
+            CompletableFuture.completedFuture(outcome2)
+                            .whenCompleteAsync(callbackStarted(callbacks), executor)
+                            .whenCompleteAsync(callbackCompleted(callbacks), executor)
+                            .whenCompleteAsync(controller.delayedComplete(), executor);
+            // @formatter:on
+
+            return new CompletableFuture<>();
+        };
+    }
+
+    /**
+     * Invokes the operation's preprocessor step(s) as a "future". This method simply
+     * invokes {@link #startGuardAsync()}.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @return a function that will start the preprocessor and returns its outcome, or
+     *         {@code null} if this operation needs no preprocessor
+     */
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        return startGuardAsync();
+    }
+
+    /**
+     * Invokes the operation's guard step(s) as a "future". This method simply returns
+     * {@code null}.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @return a function that will start the guard checks and returns its outcome, or
+     *         {@code null} if this operation has no guard
+     */
+    protected CompletableFuture<OperationOutcome> startGuardAsync() {
+        return null;
+    }
+
+    /**
+     * Starts the operation attempt, with no preprocessor. When all retries complete, it
+     * will complete the controller.
+     *
+     * @param controller controller for all operation attempts
+     * @param attempt attempt number, typically starting with 1
+     * @return a future that will return the final result of all attempts
+     */
+    private CompletableFuture<OperationOutcome> startOperationAttempt(
+                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+        // propagate "stop" to the operation attempt
+        controller.wrap(startAttemptWithoutRetries(attempt)).thenCompose(retryOnFailure(controller, attempt))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+
+        return controller;
+    }
+
+    /**
+     * Starts the operation attempt, without doing any retries.
+     *
+     * @param params operation parameters
+     * @param attempt attempt number, typically starting with 1
+     * @return a future that will return the result of a single operation attempt
+     */
+    private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(int attempt) {
+
+        logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
+
+        final Executor executor = params.getExecutor();
+        final OperationOutcome outcome = params.makeOutcome();
+        final CallbackManager callbacks = new CallbackManager();
+
+        // this operation attempt gets its own controller
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        // propagate "stop" to the callbacks
+        controller.add(callbacks);
+
+        // @formatter:off
+        CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
+                        .whenCompleteAsync(callbackStarted(callbacks), executor)
+                        .thenCompose(controller.wrap(outcome2 -> startOperationAsync(attempt, outcome2)));
+        // @formatter:on
+
+        // handle timeouts, if specified
+        long timeoutMillis = getTimeoutMs(params.getTimeoutSec());
+        if (timeoutMillis > 0) {
+            logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
+            future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+        }
+
+        /*
+         * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
+         * before callbackCompleted() is invoked.
+         *
+         * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
+         * the pipeline as the last step anyway.
+         */
+
+        // @formatter:off
+        future.exceptionally(fromException("operation"))
+                    .thenApply(setRetryFlag(attempt))
+                    .whenCompleteAsync(callbackStarted(callbacks), executor)
+                    .whenCompleteAsync(callbackCompleted(callbacks), executor)
+                    .whenCompleteAsync(controller.delayedComplete(), executor);
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Determines if the outcome was successful.
+     *
+     * @param outcome outcome to examine
+     * @return {@code true} if the outcome was successful
+     */
+    protected boolean isSuccess(OperationOutcome outcome) {
+        return (outcome.getResult() == PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Determines if the outcome was a failure for this operator.
+     *
+     * @param outcome outcome to examine, or {@code null}
+     * @return {@code true} if the outcome is not {@code null} and was a failure
+     *         <i>and</i> was associated with this operator, {@code false} otherwise
+     */
+    protected boolean isActorFailed(OperationOutcome outcome) {
+        return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
+    }
+
+    /**
+     * Determines if the given outcome is for this operation.
+     *
+     * @param outcome outcome to examine
+     * @return {@code true} if the outcome is for this operation, {@code false} otherwise
+     */
+    protected boolean isSameOperation(OperationOutcome outcome) {
+        return OperationOutcome.isFor(outcome, getActorName(), getName());
+    }
+
+    /**
+     * Invokes the operation as a "future". This method simply invokes
+     * {@link #doOperation()} using the {@link #blockingExecutor "blocking executor"},
+     * returning the result via a "future".
+     * <p/>
+     * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
+     * the executor in the "params", as that may bring the background thread pool to a
+     * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
+     * instead.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>verifyRunning() has been invoked</li>
+     * <li>callbackStarted() has been invoked</li>
+     * <li>the invoker will perform appropriate timeout checks</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @param attempt attempt number, typically starting with 1
+     * @return a function that will start the operation and return its result when
+     *         complete
+     */
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        return CompletableFuture.supplyAsync(() -> doOperation(attempt, outcome), getBlockingExecutor());
+    }
+
+    /**
+     * Low-level method that performs the operation. This can make the same assumptions
+     * that are made by {@link #doOperationAsFuture()}. This particular method simply
+     * throws an {@link UnsupportedOperationException}.
+     *
+     * @param attempt attempt number, typically starting with 1
+     * @param operation the operation being performed
+     * @return the outcome of the operation
+     */
+    protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+        throw new UnsupportedOperationException("start operation " + getFullName());
+    }
+
+    /**
+     * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
+     * FAILURE, assuming the policy specifies retries and the retry count has been
+     * exhausted.
+     *
+     * @param attempt latest attempt number, starting with 1
+     * @return a function to get the next future to execute
+     */
+    private Function<OperationOutcome, OperationOutcome> setRetryFlag(int attempt) {
+
+        return operation -> {
+            if (operation != null && !isActorFailed(operation)) {
+                /*
+                 * wrong type or wrong operation - just leave it as is. No need to log
+                 * anything here, as retryOnFailure() will log a message
+                 */
+                return operation;
+            }
+
+            // get a non-null operation
+            OperationOutcome oper2;
+            if (operation != null) {
+                oper2 = operation;
+            } else {
+                oper2 = params.makeOutcome();
+                oper2.setResult(PolicyResult.FAILURE);
+            }
+
+            int retry = getRetry(params.getRetry());
+            if (retry > 0 && attempt > retry) {
+                /*
+                 * retries were specified and we've already tried them all - change to
+                 * FAILURE_RETRIES
+                 */
+                logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
+                oper2.setResult(PolicyResult.FAILURE_RETRIES);
+            }
+
+            return oper2;
+        };
+    }
+
+    /**
+     * Restarts the operation if it was a FAILURE. Assumes that {@link #setRetryFlag(int)}
+     * was previously invoked, and thus that the "operation" is not {@code null}.
+     *
+     * @param controller controller for all of the retries
+     * @param attempt latest attempt number, starting with 1
+     * @return a function to get the next future to execute
+     */
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
+                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+        return operation -> {
+            if (!isActorFailed(operation)) {
+                // wrong type or wrong operation - just leave it as is
+                logger.info("not retrying operation {} for {}", getFullName(), params.getRequestId());
+                controller.complete(operation);
+                return new CompletableFuture<>();
+            }
+
+            if (getRetry(params.getRetry()) <= 0) {
+                // no retries - already marked as FAILURE, so just return it
+                logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
+                controller.complete(operation);
+                return new CompletableFuture<>();
+            }
+
+            /*
+             * Retry the operation.
+             */
+            long waitMs = getRetryWaitMs();
+            logger.info("retry operation {} in {}ms for {}", getFullName(), waitMs, params.getRequestId());
+
+            return sleep(waitMs, TimeUnit.MILLISECONDS)
+                            .thenCompose(unused -> startOperationAttempt(controller, attempt + 1));
+        };
+    }
+
+    /**
+     * Convenience method that starts a sleep(), running via a future.
+     *
+     * @param sleepTime time to sleep
+     * @param unit time unit
+     * @return a future that will complete when the sleep completes
+     */
+    protected CompletableFuture<Void> sleep(long sleepTime, TimeUnit unit) {
+        if (sleepTime <= 0) {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        return new CompletableFuture<Void>().completeOnTimeout(null, sleepTime, unit);
+    }
+
+    /**
+     * Converts an exception into an operation outcome, returning a copy of the outcome to
+     * prevent background jobs from changing it.
+     *
+     * @param type type of item throwing the exception
+     * @return a function that will convert an exception into an operation outcome
+     */
+    private Function<Throwable, OperationOutcome> fromException(String type) {
+
+        return thrown -> {
+            OperationOutcome outcome = params.makeOutcome();
+
+            logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
+                            params.getRequestId(), thrown);
+
+            return setOutcome(outcome, thrown);
+        };
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+     * any outstanding futures when one completes.
+     *
+     * @param futureMakers function to make a future. If the function returns
+     *        {@code null}, then no future is created for that function. On the other
+     *        hand, if the function throws an exception, then the previously created
+     *        functions are canceled and the exception is re-thrown
+     * @return a future to cancel or await an outcome, or {@code null} if no futures were
+     *         created. If this future is canceled, then all of the futures will be
+     *         canceled
+     */
+    protected CompletableFuture<OperationOutcome> anyOf(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+        return anyOf(Arrays.asList(futureMakers));
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+     * any outstanding futures when one completes.
+     *
+     * @param futureMakers function to make a future. If the function returns
+     *        {@code null}, then no future is created for that function. On the other
+     *        hand, if the function throws an exception, then the previously created
+     *        functions are canceled and the exception is re-thrown
+     * @return a future to cancel or await an outcome, or {@code null} if no futures were
+     *         created. If this future is canceled, then all of the futures will be
+     *         canceled. Similarly, when this future completes, any incomplete futures
+     *         will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> anyOf(
+                    List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome>[] futures =
+                        attachFutures(controller, futureMakers, UnaryOperator.identity());
+
+        if (futures.length == 0) {
+            // no futures were started
+            return null;
+        }
+
+        if (futures.length == 1) {
+            return futures[0];
+        }
+
+        CompletableFuture.anyOf(futures).thenApply(outcome -> (OperationOutcome) outcome)
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+
+        return controller;
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
+     *
+     * @param futureMakers function to make a future. If the function returns
+     *        {@code null}, then no future is created for that function. On the other
+     *        hand, if the function throws an exception, then the previously created
+     *        functions are canceled and the exception is re-thrown
+     * @return a future to cancel or await an outcome, or {@code null} if no futures were
+     *         created. If this future is canceled, then all of the futures will be
+     *         canceled
+     */
+    protected CompletableFuture<OperationOutcome> allOf(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+        return allOf(Arrays.asList(futureMakers));
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
+     *
+     * @param futureMakers function to make a future. If the function returns
+     *        {@code null}, then no future is created for that function. On the other
+     *        hand, if the function throws an exception, then the previously created
+     *        functions are canceled and the exception is re-thrown
+     * @return a future to cancel or await an outcome, or {@code null} if no futures were
+     *         created. If this future is canceled, then all of the futures will be
+     *         canceled. Similarly, when this future completes, any incomplete futures
+     *         will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> allOf(
+                    List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        Queue<OperationOutcome> outcomes = new LinkedList<>();
+
+        CompletableFuture<OperationOutcome>[] futures =
+                        attachFutures(controller, futureMakers, future -> future.thenApply(outcome -> {
+                            synchronized (outcomes) {
+                                outcomes.add(outcome);
+                            }
+                            return outcome;
+                        }));
+
+        if (futures.length == 0) {
+            // no futures were started
+            return null;
+        }
+
+        if (futures.length == 1) {
+            return futures[0];
+        }
+
+        // @formatter:off
+        CompletableFuture.allOf(futures)
+                        .thenApply(unused -> combineOutcomes(outcomes))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Invokes the functions to create the futures and attaches them to the controller.
+     *
+     * @param controller master controller for all of the futures
+     * @param futureMakers futures to be attached to the controller
+     * @param adorn function that "adorns" the future, possible adding onto its pipeline.
+     *        Returns the adorned future
+     * @return an array of futures, possibly zero-length. If the array is of size one,
+     *         then that one item should be returned instead of the controller
+     */
+    private CompletableFuture<OperationOutcome>[] attachFutures(PipelineControllerFuture<OperationOutcome> controller,
+                    List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers,
+                    UnaryOperator<CompletableFuture<OperationOutcome>> adorn) {
+
+        if (futureMakers.isEmpty()) {
+            @SuppressWarnings("unchecked")
+            CompletableFuture<OperationOutcome>[] result = new CompletableFuture[0];
+            return result;
+        }
+
+        // the last, unadorned future that is created
+        CompletableFuture<OperationOutcome> lastFuture = null;
+
+        List<CompletableFuture<OperationOutcome>> futures = new ArrayList<>(futureMakers.size());
+
+        // make each future
+        for (var maker : futureMakers) {
+            try {
+                CompletableFuture<OperationOutcome> future = maker.get();
+                if (future == null) {
+                    continue;
+                }
+
+                // propagate "stop" to the future
+                controller.add(future);
+
+                futures.add(adorn.apply(future));
+
+                lastFuture = future;
+
+            } catch (RuntimeException e) {
+                logger.warn("{}: exception creating 'future' for {}", getFullName(), params.getRequestId());
+                controller.cancel(false);
+                throw e;
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        CompletableFuture<OperationOutcome>[] result = new CompletableFuture[futures.size()];
+
+        if (result.length == 1) {
+            // special case - return the unadorned future
+            result[0] = lastFuture;
+            return result;
+        }
+
+        return futures.toArray(result);
+    }
+
+    /**
+     * Combines the outcomes from a set of tasks.
+     *
+     * @param outcomes outcomes to be examined
+     * @return the combined outcome
+     */
+    private OperationOutcome combineOutcomes(Queue<OperationOutcome> outcomes) {
+
+        // identify the outcome with the highest priority
+        OperationOutcome outcome = outcomes.remove();
+        int priority = detmPriority(outcome);
+
+        for (OperationOutcome outcome2 : outcomes) {
+            int priority2 = detmPriority(outcome2);
+
+            if (priority2 > priority) {
+                outcome = outcome2;
+                priority = priority2;
+            }
+        }
+
+        logger.info("{}: combined outcome of tasks is {} for {}", getFullName(),
+                        (outcome == null ? null : outcome.getResult()), params.getRequestId());
+
+        return outcome;
+    }
+
+    /**
+     * Determines the priority of an outcome based on its result.
+     *
+     * @param outcome outcome to examine, or {@code null}
+     * @return the outcome's priority
+     */
+    protected int detmPriority(OperationOutcome outcome) {
+        if (outcome == null || outcome.getResult() == null) {
+            return 1;
+        }
+
+        switch (outcome.getResult()) {
+            case SUCCESS:
+                return 0;
+
+            case FAILURE_GUARD:
+                return 2;
+
+            case FAILURE_RETRIES:
+                return 3;
+
+            case FAILURE:
+                return 4;
+
+            case FAILURE_TIMEOUT:
+                return 5;
+
+            case FAILURE_EXCEPTION:
+            default:
+                return 6;
+        }
+    }
+
+    /**
+     * Performs a sequence of tasks, stopping if a task fails. A given task's future is
+     * not created until the previous task completes. The pipeline returns the outcome of
+     * the last task executed.
+     *
+     * @param futureMakers functions to make the futures
+     * @return a future to cancel the sequence or await the outcome
+     */
+    protected CompletableFuture<OperationOutcome> sequence(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+        return sequence(Arrays.asList(futureMakers));
+    }
+
+    /**
+     * Performs a sequence of tasks, stopping if a task fails. A given task's future is
+     * not created until the previous task completes. The pipeline returns the outcome of
+     * the last task executed.
+     *
+     * @param futureMakers functions to make the futures
+     * @return a future to cancel the sequence or await the outcome, or {@code null} if
+     *         there were no tasks to perform
+     */
+    protected CompletableFuture<OperationOutcome> sequence(
+                    List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+
+        Queue<Supplier<CompletableFuture<OperationOutcome>>> queue = new ArrayDeque<>(futureMakers);
+
+        CompletableFuture<OperationOutcome> nextTask = getNextTask(queue);
+        if (nextTask == null) {
+            // no tasks
+            return null;
+        }
+
+        if (queue.isEmpty()) {
+            // only one task - just return it rather than wrapping it in a controller
+            return nextTask;
+        }
+
+        /*
+         * multiple tasks - need a controller to stop whichever task is currently
+         * executing
+         */
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        final Executor executor = params.getExecutor();
+
+        // @formatter:off
+        controller.wrap(nextTask)
+                    .thenComposeAsync(nextTaskOnSuccess(controller, queue), executor)
+                    .whenCompleteAsync(controller.delayedComplete(), executor);
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Executes the next task in the queue, if the previous outcome was successful.
+     *
+     * @param controller pipeline controller
+     * @param taskQueue queue of tasks to be performed
+     * @return a future to execute the remaining tasks, or the current outcome, if it's a
+     *         failure, or if there are no more tasks
+     */
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> nextTaskOnSuccess(
+                    PipelineControllerFuture<OperationOutcome> controller,
+                    Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) {
+
+        return outcome -> {
+            if (!isSuccess(outcome)) {
+                // return the failure
+                return CompletableFuture.completedFuture(outcome);
+            }
+
+            CompletableFuture<OperationOutcome> nextTask = getNextTask(taskQueue);
+            if (nextTask == null) {
+                // no tasks - just return the success
+                return CompletableFuture.completedFuture(outcome);
+            }
+
+            // @formatter:off
+            return controller
+                        .wrap(nextTask)
+                        .thenComposeAsync(nextTaskOnSuccess(controller, taskQueue), params.getExecutor());
+            // @formatter:on
+        };
+    }
+
+    /**
+     * Gets the next task from the queue, skipping those that are {@code null}.
+     *
+     * @param taskQueue task queue
+     * @return the next task, or {@code null} if the queue is now empty
+     */
+    private CompletableFuture<OperationOutcome> getNextTask(
+                    Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) {
+
+        Supplier<CompletableFuture<OperationOutcome>> maker;
+
+        while ((maker = taskQueue.poll()) != null) {
+            CompletableFuture<OperationOutcome> future = maker.get();
+            if (future != null) {
+                return future;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the start time of the operation and invokes the callback to indicate that the
+     * operation has started. Does nothing if the pipeline has been stopped.
+     * <p/>
+     * This assumes that the "outcome" is not {@code null}.
+     *
+     * @param callbacks used to determine if the start callback can be invoked
+     * @return a function that sets the start time and invokes the callback
+     */
+    private BiConsumer<OperationOutcome, Throwable> callbackStarted(CallbackManager callbacks) {
+
+        return (outcome, thrown) -> {
+
+            if (callbacks.canStart()) {
+                // haven't invoked "start" callback yet
+                outcome.setStart(callbacks.getStartTime());
+                outcome.setEnd(null);
+                params.callbackStarted(outcome);
+            }
+        };
+    }
+
+    /**
+     * Sets the end time of the operation and invokes the callback to indicate that the
+     * operation has completed. Does nothing if the pipeline has been stopped.
+     * <p/>
+     * This assumes that the "outcome" is not {@code null}.
+     * <p/>
+     * Note: the start time must be a reference rather than a plain value, because it's
+     * value must be gotten on-demand, when the returned function is executed at a later
+     * time.
+     *
+     * @param callbacks used to determine if the end callback can be invoked
+     * @return a function that sets the end time and invokes the callback
+     */
+    private BiConsumer<OperationOutcome, Throwable> callbackCompleted(CallbackManager callbacks) {
+
+        return (outcome, thrown) -> {
+
+            if (callbacks.canEnd()) {
+                outcome.setStart(callbacks.getStartTime());
+                outcome.setEnd(callbacks.getEndTime());
+                params.callbackCompleted(outcome);
+            }
+        };
+    }
+
+    /**
+     * Sets an operation's outcome and message, based on a throwable.
+     *
+     * @param operation operation to be updated
+     * @return the updated operation
+     */
+    protected OperationOutcome setOutcome(OperationOutcome operation, Throwable thrown) {
+        PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
+        return setOutcome(operation, result);
+    }
+
+    /**
+     * Sets an operation's outcome and default message based on the result.
+     *
+     * @param operation operation to be updated
+     * @param result result of the operation
+     * @return the updated operation
+     */
+    public OperationOutcome setOutcome(OperationOutcome operation, PolicyResult result) {
+        logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
+        operation.setResult(result);
+        operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
+                        : ControlLoopOperation.FAILED_MSG);
+
+        return operation;
+    }
+
+    /**
+     * Determines if a throwable is due to a timeout.
+     *
+     * @param thrown throwable of interest
+     * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
+     */
+    protected boolean isTimeout(Throwable thrown) {
+        if (thrown instanceof CompletionException) {
+            thrown = thrown.getCause();
+        }
+
+        return (thrown instanceof TimeoutException);
+    }
+
+    /**
+     * Logs a response. If the response is not of type, String, then it attempts to
+     * pretty-print it into JSON before logging.
+     *
+     * @param direction IN or OUT
+     * @param infra communication infrastructure on which it was published
+     * @param source source name (e.g., the URL or Topic name)
+     * @param response response to be logged
+     * @return the JSON text that was logged
+     */
+    public <T> String logMessage(EventType direction, CommInfrastructure infra, String source, T response) {
+        String json;
+        try {
+            if (response == null) {
+                json = null;
+            } else if (response instanceof String) {
+                json = response.toString();
+            } else {
+                json = makeCoder().encode(response, true);
+            }
+
+        } catch (CoderException e) {
+            String type = (direction == EventType.IN ? "response" : "request");
+            logger.warn("cannot pretty-print {}", type, e);
+            json = response.toString();
+        }
+
+        logger.info("[{}|{}|{}|]{}{}", direction, infra, source, NetLoggerUtil.SYSTEM_LS, json);
+
+        return json;
+    }
+
+    // these may be overridden by subclasses or junit tests
+
+    /**
+     * Gets the retry count.
+     *
+     * @param retry retry, extracted from the parameters, or {@code null}
+     * @return the number of retries, or {@code 0} if no retries were specified
+     */
+    protected int getRetry(Integer retry) {
+        return (retry == null ? 0 : retry);
+    }
+
+    /**
+     * Gets the retry wait, in milliseconds.
+     *
+     * @return the retry wait, in milliseconds
+     */
+    protected long getRetryWaitMs() {
+        return DEFAULT_RETRY_WAIT_MS;
+    }
+
+    /**
+     * Gets the operation timeout.
+     *
+     * @param timeoutSec timeout, in seconds, extracted from the parameters, or
+     *        {@code null}
+     * @return the operation timeout, in milliseconds, or {@code 0} if no timeout was
+     *         specified
+     */
+    protected long getTimeoutMs(Integer timeoutSec) {
+        return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
+    }
+
+    // these may be overridden by junit tests
+
+    protected Coder makeCoder() {
+        return coder;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
index df5258d..3e15c1b 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
@@ -20,57 +20,24 @@
 
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
-import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-import lombok.AccessLevel;
 import lombok.Getter;
-import lombok.Setter;
-import org.onap.policy.controlloop.ControlLoopOperation;
-import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.Operator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * Partial implementation of an operator. In general, it's preferable that subclasses
- * would override
- * {@link #startOperationAsync(ControlLoopOperationParams, int, OperationOutcome)
- * startOperationAsync()}. However, if that proves to be too difficult, then they can
- * simply override {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome)
- * doOperation()}. In addition, if the operation requires any preprocessor steps, the
- * subclass may choose to override
- * {@link #startPreprocessorAsync(ControlLoopOperationParams) startPreprocessorAsync()}.
- * <p/>
- * The futures returned by the methods within this class can be canceled, and will
- * propagate the cancellation to any subtasks. Thus it is also expected that any futures
- * returned by overridden methods will do the same. Of course, if a class overrides
- * {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome) doOperation()},
- * then there's little that can be done to cancel that particular operation.
+ * Partial implementation of an operator.
  */
 public abstract class OperatorPartial extends StartConfigPartial<Map<String, Object>> implements Operator {
 
-    private static final Logger logger = LoggerFactory.getLogger(OperatorPartial.class);
-
     /**
      * Executor to be used for tasks that may perform blocking I/O. The default executor
      * simply launches a new thread for each command that is submitted to it.
      * <p/>
-     * May be overridden by junit tests.
+     * The "get" method may be overridden by junit tests.
      */
-    @Getter(AccessLevel.PROTECTED)
-    @Setter(AccessLevel.PROTECTED)
-    private Executor blockingExecutor = command -> {
+    @Getter
+    private final Executor blockingExecutor = command -> {
         Thread thread = new Thread(command);
         thread.setDaemon(true);
         thread.start();
@@ -125,721 +92,4 @@
     protected void doShutdown() {
         // do nothing
     }
-
-    @Override
-    public final CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params) {
-        if (!isAlive()) {
-            throw new IllegalStateException("operation is not running: " + getFullName());
-        }
-
-        // allocate a controller for the entire operation
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync(params);
-        if (preproc == null) {
-            // no preprocessor required - just start the operation
-            return startOperationAttempt(params, controller, 1);
-        }
-
-        /*
-         * Do preprocessor first and then, if successful, start the operation. Note:
-         * operations create their own outcome, ignoring the outcome from any previous
-         * steps.
-         *
-         * Wrap the preprocessor to ensure "stop" is propagated to it.
-         */
-        // @formatter:off
-        controller.wrap(preproc)
-                        .exceptionally(fromException(params, "preprocessor of operation"))
-                        .thenCompose(handlePreprocessorFailure(params, controller))
-                        .thenCompose(unusedOutcome -> startOperationAttempt(params, controller, 1));
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
-     * invokes the call-backs, marks the controller complete, and returns an incomplete
-     * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
-     * received.
-     * <p/>
-     * Assumes that no callbacks have been invoked yet.
-     *
-     * @param params operation parameters
-     * @param controller pipeline controller
-     * @return a function that checks the outcome status and continues, if successful, or
-     *         indicates a failure otherwise
-     */
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
-                    ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller) {
-
-        return outcome -> {
-
-            if (outcome != null && isSuccess(outcome)) {
-                logger.trace("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
-                return CompletableFuture.completedFuture(outcome);
-            }
-
-            logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
-
-            final Executor executor = params.getExecutor();
-            final CallbackManager callbacks = new CallbackManager();
-
-            // propagate "stop" to the callbacks
-            controller.add(callbacks);
-
-            final OperationOutcome outcome2 = params.makeOutcome();
-
-            // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
-
-            outcome2.setResult(PolicyResult.FAILURE_GUARD);
-            outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
-
-            // @formatter:off
-            CompletableFuture.completedFuture(outcome2)
-                            .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                            .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
-                            .whenCompleteAsync(controller.delayedComplete(), executor);
-            // @formatter:on
-
-            return new CompletableFuture<>();
-        };
-    }
-
-    /**
-     * Invokes the operation's preprocessor step(s) as a "future". This method simply
-     * returns {@code null}.
-     * <p/>
-     * This method assumes the following:
-     * <ul>
-     * <li>the operator is alive</li>
-     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
-     * </ul>
-     *
-     * @param params operation parameters
-     * @return a function that will start the preprocessor and returns its outcome, or
-     *         {@code null} if this operation needs no preprocessor
-     */
-    protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-        return null;
-    }
-
-    /**
-     * Starts the operation attempt, with no preprocessor. When all retries complete, it
-     * will complete the controller.
-     *
-     * @param params operation parameters
-     * @param controller controller for all operation attempts
-     * @param attempt attempt number, typically starting with 1
-     * @return a future that will return the final result of all attempts
-     */
-    private CompletableFuture<OperationOutcome> startOperationAttempt(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
-
-        // propagate "stop" to the operation attempt
-        controller.wrap(startAttemptWithoutRetries(params, attempt))
-                        .thenCompose(retryOnFailure(params, controller, attempt));
-
-        return controller;
-    }
-
-    /**
-     * Starts the operation attempt, without doing any retries.
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @return a future that will return the result of a single operation attempt
-     */
-    private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(ControlLoopOperationParams params,
-                    int attempt) {
-
-        logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
-
-        final Executor executor = params.getExecutor();
-        final OperationOutcome outcome = params.makeOutcome();
-        final CallbackManager callbacks = new CallbackManager();
-
-        // this operation attempt gets its own controller
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        // propagate "stop" to the callbacks
-        controller.add(callbacks);
-
-        // @formatter:off
-        CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
-                        .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                        .thenCompose(controller.wrap(outcome2 -> startOperationAsync(params, attempt, outcome2)));
-        // @formatter:on
-
-        // handle timeouts, if specified
-        long timeoutMillis = getTimeOutMillis(params.getTimeoutSec());
-        if (timeoutMillis > 0) {
-            logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
-            future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
-        }
-
-        /*
-         * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
-         * before callbackCompleted() is invoked.
-         *
-         * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
-         * the pipeline as the last step anyway.
-         */
-
-        // @formatter:off
-        future.exceptionally(fromException(params, "operation"))
-                    .thenApply(setRetryFlag(params, attempt))
-                    .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                    .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
-                    .whenCompleteAsync(controller.delayedComplete(), executor);
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Determines if the outcome was successful.
-     *
-     * @param outcome outcome to examine
-     * @return {@code true} if the outcome was successful
-     */
-    protected boolean isSuccess(OperationOutcome outcome) {
-        return (outcome.getResult() == PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Determines if the outcome was a failure for this operator.
-     *
-     * @param outcome outcome to examine, or {@code null}
-     * @return {@code true} if the outcome is not {@code null} and was a failure
-     *         <i>and</i> was associated with this operator, {@code false} otherwise
-     */
-    protected boolean isActorFailed(OperationOutcome outcome) {
-        return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
-    }
-
-    /**
-     * Determines if the given outcome is for this operation.
-     *
-     * @param outcome outcome to examine
-     * @return {@code true} if the outcome is for this operation, {@code false} otherwise
-     */
-    protected boolean isSameOperation(OperationOutcome outcome) {
-        return OperationOutcome.isFor(outcome, getActorName(), getName());
-    }
-
-    /**
-     * Invokes the operation as a "future". This method simply invokes
-     * {@link #doOperation(ControlLoopOperationParams)} using the {@link #blockingExecutor
-     * "blocking executor"}, returning the result via a "future".
-     * <p/>
-     * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
-     * the executor in the "params", as that may bring the background thread pool to a
-     * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
-     * instead.
-     * <p/>
-     * This method assumes the following:
-     * <ul>
-     * <li>the operator is alive</li>
-     * <li>verifyRunning() has been invoked</li>
-     * <li>callbackStarted() has been invoked</li>
-     * <li>the invoker will perform appropriate timeout checks</li>
-     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
-     * </ul>
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @return a function that will start the operation and return its result when
-     *         complete
-     */
-    protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
-                    OperationOutcome outcome) {
-
-        return CompletableFuture.supplyAsync(() -> doOperation(params, attempt, outcome), getBlockingExecutor());
-    }
-
-    /**
-     * Low-level method that performs the operation. This can make the same assumptions
-     * that are made by {@link #doOperationAsFuture(ControlLoopOperationParams)}. This
-     * particular method simply throws an {@link UnsupportedOperationException}.
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @param operation the operation being performed
-     * @return the outcome of the operation
-     */
-    protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, OperationOutcome operation) {
-
-        throw new UnsupportedOperationException("start operation " + getFullName());
-    }
-
-    /**
-     * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
-     * FAILURE, assuming the policy specifies retries and the retry count has been
-     * exhausted.
-     *
-     * @param params operation parameters
-     * @param attempt latest attempt number, starting with 1
-     * @return a function to get the next future to execute
-     */
-    private Function<OperationOutcome, OperationOutcome> setRetryFlag(ControlLoopOperationParams params, int attempt) {
-
-        return operation -> {
-            if (operation != null && !isActorFailed(operation)) {
-                /*
-                 * wrong type or wrong operation - just leave it as is. No need to log
-                 * anything here, as retryOnFailure() will log a message
-                 */
-                return operation;
-            }
-
-            // get a non-null operation
-            OperationOutcome oper2;
-            if (operation != null) {
-                oper2 = operation;
-            } else {
-                oper2 = params.makeOutcome();
-                oper2.setResult(PolicyResult.FAILURE);
-            }
-
-            Integer retry = params.getRetry();
-            if (retry != null && retry > 0 && attempt > retry) {
-                /*
-                 * retries were specified and we've already tried them all - change to
-                 * FAILURE_RETRIES
-                 */
-                logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
-                oper2.setResult(PolicyResult.FAILURE_RETRIES);
-            }
-
-            return oper2;
-        };
-    }
-
-    /**
-     * Restarts the operation if it was a FAILURE. Assumes that
-     * {@link #setRetryFlag(ControlLoopOperationParams, int)} was previously invoked, and
-     * thus that the "operation" is not {@code null}.
-     *
-     * @param params operation parameters
-     * @param controller controller for all of the retries
-     * @param attempt latest attempt number, starting with 1
-     * @return a function to get the next future to execute
-     */
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
-                    ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller,
-                    int attempt) {
-
-        return operation -> {
-            if (!isActorFailed(operation)) {
-                // wrong type or wrong operation - just leave it as is
-                logger.trace("not retrying operation {} for {}", getFullName(), params.getRequestId());
-                controller.complete(operation);
-                return new CompletableFuture<>();
-            }
-
-            Integer retry = params.getRetry();
-            if (retry == null || retry <= 0) {
-                // no retries - already marked as FAILURE, so just return it
-                logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
-                controller.complete(operation);
-                return new CompletableFuture<>();
-            }
-
-
-            /*
-             * Retry the operation.
-             */
-            logger.info("retry operation {} for {}", getFullName(), params.getRequestId());
-
-            return startOperationAttempt(params, controller, attempt + 1);
-        };
-    }
-
-    /**
-     * Converts an exception into an operation outcome, returning a copy of the outcome to
-     * prevent background jobs from changing it.
-     *
-     * @param params operation parameters
-     * @param type type of item throwing the exception
-     * @return a function that will convert an exception into an operation outcome
-     */
-    private Function<Throwable, OperationOutcome> fromException(ControlLoopOperationParams params, String type) {
-
-        return thrown -> {
-            OperationOutcome outcome = params.makeOutcome();
-
-            logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
-                            params.getRequestId(), thrown);
-
-            return setOutcome(params, outcome, thrown);
-        };
-    }
-
-    /**
-     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
-     * any outstanding futures when one completes.
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
-                    List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = anyOf(params, arrFutures);
-        return result;
-    }
-
-    /**
-     * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any
-     * outstanding futures when one completes.
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        final Executor executor = params.getExecutor();
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        attachFutures(controller, futures);
-
-        // @formatter:off
-        CompletableFuture.anyOf(futures)
-                            .thenApply(object -> (OperationOutcome) object)
-                            .whenCompleteAsync(controller.delayedComplete(), executor);
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels
-     * the futures if returned future is canceled. The future returns the "worst" outcome,
-     * based on priority (see {@link #detmPriority(OperationOutcome)}).
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
-                    List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = allOf(params, arrFutures);
-        return result;
-    }
-
-    /**
-     * Same as {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels the
-     * futures if returned future is canceled. The future returns the "worst" outcome,
-     * based on priority (see {@link #detmPriority(OperationOutcome)}).
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        attachFutures(controller, futures);
-
-        OperationOutcome[] outcomes = new OperationOutcome[futures.length];
-
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] futures2 = new CompletableFuture[futures.length];
-
-        // record the outcomes of each future when it completes
-        for (int count = 0; count < futures2.length; ++count) {
-            final int count2 = count;
-            futures2[count] = futures[count].whenComplete((outcome2, thrown) -> outcomes[count2] = outcome2);
-        }
-
-        CompletableFuture.allOf(futures2).whenComplete(combineOutcomes(params, controller, outcomes));
-
-        return controller;
-    }
-
-    /**
-     * Attaches the given futures to the controller.
-     *
-     * @param controller master controller for all of the futures
-     * @param futures futures to be attached to the controller
-     */
-    private void attachFutures(PipelineControllerFuture<OperationOutcome> controller,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        // attach each task
-        for (CompletableFuture<OperationOutcome> future : futures) {
-            controller.add(future);
-        }
-    }
-
-    /**
-     * Combines the outcomes from a set of tasks.
-     *
-     * @param params operation parameters
-     * @param future future to be completed with the combined result
-     * @param outcomes outcomes to be examined
-     */
-    private BiConsumer<Void, Throwable> combineOutcomes(ControlLoopOperationParams params,
-                    CompletableFuture<OperationOutcome> future, OperationOutcome[] outcomes) {
-
-        return (unused, thrown) -> {
-            if (thrown != null) {
-                future.completeExceptionally(thrown);
-                return;
-            }
-
-            // identify the outcome with the highest priority
-            OperationOutcome outcome = outcomes[0];
-            int priority = detmPriority(outcome);
-
-            // start with "1", as we've already dealt with "0"
-            for (int count = 1; count < outcomes.length; ++count) {
-                OperationOutcome outcome2 = outcomes[count];
-                int priority2 = detmPriority(outcome2);
-
-                if (priority2 > priority) {
-                    outcome = outcome2;
-                    priority = priority2;
-                }
-            }
-
-            logger.trace("{}: combined outcome of tasks is {} for {}", getFullName(),
-                            (outcome == null ? null : outcome.getResult()), params.getRequestId());
-
-            future.complete(outcome);
-        };
-    }
-
-    /**
-     * Determines the priority of an outcome based on its result.
-     *
-     * @param outcome outcome to examine, or {@code null}
-     * @return the outcome's priority
-     */
-    protected int detmPriority(OperationOutcome outcome) {
-        if (outcome == null) {
-            return 1;
-        }
-
-        switch (outcome.getResult()) {
-            case SUCCESS:
-                return 0;
-
-            case FAILURE_GUARD:
-                return 2;
-
-            case FAILURE_RETRIES:
-                return 3;
-
-            case FAILURE:
-                return 4;
-
-            case FAILURE_TIMEOUT:
-                return 5;
-
-            case FAILURE_EXCEPTION:
-            default:
-                return 6;
-        }
-    }
-
-    /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
-     *
-     * @param params operation parameters
-     * @param controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param outcome outcome of the previous task
-     * @param tasks tasks to be performed
-     * @return a function to perform the task. If everything checks out, then it returns
-     *         the task's future. Otherwise, it returns an incomplete future and completes
-     *         the controller instead.
-     */
-    // @formatter:off
-    protected CompletableFuture<OperationOutcome> doTask(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess, OperationOutcome outcome,
-                    CompletableFuture<OperationOutcome> task) {
-        // @formatter:on
-
-        if (checkSuccess && !isSuccess(outcome)) {
-            /*
-             * must complete before canceling so that cancel() doesn't cause controller to
-             * complete
-             */
-            controller.complete(outcome);
-            task.cancel(false);
-            return new CompletableFuture<>();
-        }
-
-        return controller.wrap(task);
-    }
-
-    /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
-     *
-     * @param params operation parameters
-     * @param controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param tasks tasks to be performed
-     * @return a function to perform the task. If everything checks out, then it returns
-     *         the task's future. Otherwise, it returns an incomplete future and completes
-     *         the controller instead.
-     */
-    // @formatter:off
-    protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess,
-                    Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) {
-        // @formatter:on
-
-        return outcome -> {
-
-            if (!controller.isRunning()) {
-                return new CompletableFuture<>();
-            }
-
-            if (checkSuccess && !isSuccess(outcome)) {
-                controller.complete(outcome);
-                return new CompletableFuture<>();
-            }
-
-            return controller.wrap(task.apply(outcome));
-        };
-    }
-
-    /**
-     * Sets the start time of the operation and invokes the callback to indicate that the
-     * operation has started. Does nothing if the pipeline has been stopped.
-     * <p/>
-     * This assumes that the "outcome" is not {@code null}.
-     *
-     * @param params operation parameters
-     * @param callbacks used to determine if the start callback can be invoked
-     * @return a function that sets the start time and invokes the callback
-     */
-    private BiConsumer<OperationOutcome, Throwable> callbackStarted(ControlLoopOperationParams params,
-                    CallbackManager callbacks) {
-
-        return (outcome, thrown) -> {
-
-            if (callbacks.canStart()) {
-                // haven't invoked "start" callback yet
-                outcome.setStart(callbacks.getStartTime());
-                outcome.setEnd(null);
-                params.callbackStarted(outcome);
-            }
-        };
-    }
-
-    /**
-     * Sets the end time of the operation and invokes the callback to indicate that the
-     * operation has completed. Does nothing if the pipeline has been stopped.
-     * <p/>
-     * This assumes that the "outcome" is not {@code null}.
-     * <p/>
-     * Note: the start time must be a reference rather than a plain value, because it's
-     * value must be gotten on-demand, when the returned function is executed at a later
-     * time.
-     *
-     * @param params operation parameters
-     * @param callbacks used to determine if the end callback can be invoked
-     * @return a function that sets the end time and invokes the callback
-     */
-    private BiConsumer<OperationOutcome, Throwable> callbackCompleted(ControlLoopOperationParams params,
-                    CallbackManager callbacks) {
-
-        return (outcome, thrown) -> {
-
-            if (callbacks.canEnd()) {
-                outcome.setStart(callbacks.getStartTime());
-                outcome.setEnd(callbacks.getEndTime());
-                params.callbackCompleted(outcome);
-            }
-        };
-    }
-
-    /**
-     * Sets an operation's outcome and message, based on a throwable.
-     *
-     * @param params operation parameters
-     * @param operation operation to be updated
-     * @return the updated operation
-     */
-    protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
-                    Throwable thrown) {
-        PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
-        return setOutcome(params, operation, result);
-    }
-
-    /**
-     * Sets an operation's outcome and default message based on the result.
-     *
-     * @param params operation parameters
-     * @param operation operation to be updated
-     * @param result result of the operation
-     * @return the updated operation
-     */
-    protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
-                    PolicyResult result) {
-        logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
-        operation.setResult(result);
-        operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
-                        : ControlLoopOperation.FAILED_MSG);
-
-        return operation;
-    }
-
-    /**
-     * Determines if a throwable is due to a timeout.
-     *
-     * @param thrown throwable of interest
-     * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
-     */
-    protected boolean isTimeout(Throwable thrown) {
-        if (thrown instanceof CompletionException) {
-            thrown = thrown.getCause();
-        }
-
-        return (thrown instanceof TimeoutException);
-    }
-
-    // these may be overridden by junit tests
-
-    /**
-     * Gets the operation timeout. Subclasses may override this method to obtain the
-     * timeout in some other way (e.g., through configuration properties).
-     *
-     * @param timeoutSec timeout, in seconds, or {@code null}
-     * @return the operation timeout, in milliseconds
-     */
-    protected long getTimeOutMillis(Integer timeoutSec) {
-        return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
-    }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java
new file mode 100644
index 0000000..291aeeb
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java
@@ -0,0 +1,57 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.annotations.Min;
+
+/**
+ * Parameters used by Actors whose Operators use bidirectional topic.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true)
+public class BidirectionalTopicActorParams extends CommonActorParams {
+
+    /*
+     * Optional, default values that are used if missing from the operation-specific
+     * parameters.
+     */
+
+    /**
+     * Sink topic name to which requests should be published.
+     */
+    private String sinkTopic;
+
+    /**
+     * Source topic name, from which to read responses.
+     */
+    private String sourceTopic;
+
+    /**
+     * Amount of time, in seconds, to wait for the HTTP request to complete. The default
+     * is 90 seconds.
+     */
+    @Min(1)
+    private int timeoutSec = 90;
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java
similarity index 75%
rename from models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java
rename to models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java
index 9e6d8a1..cafca1f 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java
@@ -29,34 +29,36 @@
 import org.onap.policy.common.parameters.annotations.NotNull;
 
 /**
- * Parameters used by Operators that connect to a server via DMaaP.
+ * Parameters used by Operators that use a bidirectional topic.
  */
 @NotNull
 @NotBlank
 @Data
 @Builder(toBuilder = true)
-public class TopicParams {
+public class BidirectionalTopicParams {
 
     /**
-     * Name of the target topic end point to which requests should be published.
+     * Sink topic name to which requests should be published.
      */
-    private String target;
+    private String sinkTopic;
 
     /**
-     * Source topic end point, from which to read responses.
+     * Source topic name, from which to read responses.
      */
-    private String source;
+    private String sourceTopic;
 
     /**
-     * Amount of time, in seconds to wait for the response, where zero indicates that it
-     * should wait forever. The default is zero.
+     * Amount of time, in seconds to wait for the response.
+     * <p/>
+     * Note: this should NOT have a default value, as it receives its default value from
+     * {@link BidirectionalTopicActorParams}.
      */
-    @Min(0)
-    @Builder.Default
-    private long timeoutSec = 0;
+    @Min(1)
+    private int timeoutSec;
+
 
     /**
-     * Validates both the publisher and the subscriber parameters.
+     * Validates the parameters.
      *
      * @param resultName name of the result
      *
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java
new file mode 100644
index 0000000..dc6f2b6
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java
@@ -0,0 +1,102 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Function;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.BeanValidator;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+/**
+ * Superclass for Actor parameters that have default values in "this" object, and
+ * operation-specific values in {@link #operation}.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode
+public class CommonActorParams {
+
+    /**
+     * Maps the operation name to its parameters.
+     */
+    @NotNull
+    protected Map<String, Map<String, Object>> operation;
+
+
+    /**
+     * Extracts a specific operation's parameters from "this".
+     *
+     * @param name name of the item containing "this"
+     * @return a function to extract an operation's parameters from "this". Note: the
+     *         returned function is not thread-safe
+     */
+    public Function<String, Map<String, Object>> makeOperationParameters(String name) {
+
+        Map<String, Object> defaultParams = Util.translateToMap(name, this);
+        defaultParams.remove("operation");
+
+        return operationName -> {
+            Map<String, Object> specificParams = operation.get(operationName);
+            if (specificParams == null) {
+                return null;
+            }
+
+            // start with a copy of defaults and overlay with specific
+            Map<String, Object> subparams = new TreeMap<>(defaultParams);
+            subparams.putAll(specificParams);
+
+            return Util.translateToMap(name + "." + operationName, subparams);
+        };
+    }
+
+    /**
+     * Validates the parameters.
+     *
+     * @param name name of the object containing these parameters
+     * @return "this"
+     * @throws IllegalArgumentException if the parameters are invalid
+     */
+    public CommonActorParams doValidation(String name) {
+        ValidationResult result = validate(name);
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        return this;
+    }
+
+    /**
+     * Validates the parameters.
+     *
+     * @param resultName name of the result
+     *
+     * @return the validation result
+     */
+    public ValidationResult validate(String resultName) {
+        return new BeanValidator().validateTop(resultName, this);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
index 57fce40..9259160 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
@@ -148,7 +148,8 @@
         return actorService
                     .getActor(getActor())
                     .getOperator(getOperation())
-                    .startOperation(this);
+                    .buildOperation(this)
+                    .start();
         // @formatter:on
     }
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
index da4fb4f..d589e1d 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
@@ -20,26 +20,23 @@
 
 package org.onap.policy.controlloop.actorserviceprovider.parameters;
 
-import java.util.Map;
-import java.util.function.Function;
-import lombok.Data;
-import org.onap.policy.common.parameters.BeanValidationResult;
-import org.onap.policy.common.parameters.BeanValidator;
-import org.onap.policy.common.parameters.ValidationResult;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
 import org.onap.policy.common.parameters.annotations.Min;
-import org.onap.policy.common.parameters.annotations.NotBlank;
-import org.onap.policy.common.parameters.annotations.NotNull;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
 
 /**
- * Parameters used by Actors that connect to a server via HTTP. This contains the
- * parameters that are common to all of the operations. Only the path changes for each
- * operation, thus it includes a mapping from operation name to path.
+ * Parameters used by Actors that connect to a server via HTTP.
  */
-@Data
-@NotNull
-@NotBlank
-public class HttpActorParams {
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true)
+public class HttpActorParams extends CommonActorParams {
+
+    /*
+     * Optional, default values that are used if missing from the operation-specific
+     * parameters.
+     */
 
     /**
      * Name of the HttpClient, as found in the HttpClientFactory.
@@ -47,66 +44,9 @@
     private String clientName;
 
     /**
-     * Amount of time, in seconds to wait for the HTTP request to complete, where zero
-     * indicates that it should wait forever. The default is zero.
+     * Amount of time, in seconds, to wait for the HTTP request to complete. The default
+     * is 90 seconds.
      */
-    @Min(0)
-    private long timeoutSec = 0;
-
-    /**
-     * Maps the operation name to its URI path.
-     */
-    private Map<String, String> path;
-
-    /**
-     * Extracts a specific operation's parameters from "this".
-     *
-     * @param name name of the item containing "this"
-     * @return a function to extract an operation's parameters from "this". Note: the
-     *         returned function is not thread-safe
-     */
-    public Function<String, Map<String, Object>> makeOperationParameters(String name) {
-        HttpParams subparams = HttpParams.builder().clientName(getClientName()).timeoutSec(getTimeoutSec()).build();
-
-        return operation -> {
-            String subpath = path.get(operation);
-            if (subpath == null) {
-                return null;
-            }
-
-            subparams.setPath(subpath);
-            return Util.translateToMap(name + "." + operation, subparams);
-        };
-    }
-
-    /**
-     * Validates the parameters.
-     *
-     * @param name name of the object containing these parameters
-     * @return "this"
-     * @throws IllegalArgumentException if the parameters are invalid
-     */
-    public HttpActorParams doValidation(String name) {
-        ValidationResult result = validate(name);
-        if (!result.isValid()) {
-            throw new ParameterValidationRuntimeException("invalid parameters", result);
-        }
-
-        return this;
-    }
-
-    /**
-     * Validates the parameters.
-     *
-     * @param resultName name of the result
-     *
-     * @return the validation result
-     */
-    public ValidationResult validate(String resultName) {
-        BeanValidationResult result = new BeanValidator().validateTop(resultName, this);
-
-        result.validateMap("path", path, (result2, entry) -> result2.validateNotNull(entry.getKey(), entry.getValue()));
-
-        return result;
-    }
+    @Min(1)
+    private int timeoutSec = 90;
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
index 695ffe4..2d3ab8b 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
@@ -48,12 +48,13 @@
     private String path;
 
     /**
-     * Amount of time, in seconds to wait for the HTTP request to complete, where zero
-     * indicates that it should wait forever. The default is zero.
+     * Amount of time, in seconds, to wait for the HTTP request to complete.
+     * <p/>
+     * Note: this should NOT have a default value, as it receives its default value from
+     * {@link HttpActorParams}.
      */
-    @Min(0)
-    @Builder.Default
-    private long timeoutSec = 0;
+    @Min(1)
+    private int timeoutSec;
 
 
     /**
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
index 620950a..53bee5f 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
@@ -22,7 +22,6 @@
 package org.onap.policy.controlloop.actorserviceprovider.spi;
 
 import java.util.Collection;
-
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java
new file mode 100644
index 0000000..30ee1e2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java
@@ -0,0 +1,79 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.List;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClient;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+
+/**
+ * Handler for a bidirectional topic, supporting both publishing and forwarding of
+ * incoming messages.
+ */
+public class BidirectionalTopicHandler extends BidirectionalTopicClient {
+
+    /**
+     * Listener that will be attached to the topic to receive responses.
+     */
+    private final TopicListenerImpl listener = new TopicListenerImpl();
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param sinkTopic sink topic name
+     * @param sourceTopic source topic name
+     * @throws BidirectionalTopicClientException if an error occurs
+     */
+    public BidirectionalTopicHandler(String sinkTopic, String sourceTopic) throws BidirectionalTopicClientException {
+        super(sinkTopic, sourceTopic);
+    }
+
+    /**
+     * Starts listening on the source topic(s).
+     */
+    public void start() {
+        getSource().register(listener);
+    }
+
+    /**
+     * Stops listening on the source topic(s).
+     */
+    public void stop() {
+        getSource().unregister(listener);
+    }
+
+    /**
+     * Stops listening on the source topic(s) and clears all of the forwarders.
+     */
+    public void shutdown() {
+        stop();
+        listener.shutdown();
+    }
+
+    public Forwarder addForwarder(SelectorKey... keys) {
+        return listener.addForwarder(keys);
+    }
+
+    public Forwarder addForwarder(List<SelectorKey> keys) {
+        return listener.addForwarder(keys);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java
new file mode 100644
index 0000000..1041187
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+/**
+ * Manages bidirectional topics.
+ */
+@FunctionalInterface
+public interface BidirectionalTopicManager {
+
+    /**
+     * Gets the topic handler for the given parameters, creating one if it does not exist.
+     *
+     * @param sinkTopic sink topic name
+     * @param sourceTopic source topic name
+     * @return the topic handler associated with the given sink and source topic names
+     */
+    BidirectionalTopicHandler getTopicHandler(String sinkTopic, String sourceTopic);
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java
new file mode 100644
index 0000000..2d98b66
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java
@@ -0,0 +1,138 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Forwarder that selectively forwards message to listeners based on the content of the
+ * message. Each forwarder is associated with a single set of selector keys. Listeners are
+ * then registered with that forwarder for a particular set of values for the given keys.
+ */
+public class Forwarder {
+    private static final Logger logger = LoggerFactory.getLogger(Forwarder.class);
+
+    /**
+     * Maps a set of field values to one or more listeners.
+     */
+    // @formatter:off
+    private final Map<List<String>, Map<BiConsumer<String, StandardCoderObject>, String>>
+                values2listeners = new ConcurrentHashMap<>();
+    // @formatter:on
+
+    /**
+     * Keys used to extract the field values from the {@link StandardCoderObject}.
+     */
+    private final List<SelectorKey> keys;
+
+    /**
+     * Constructs the object.
+     *
+     * @param keys keys used to extract the field's value from the
+     *        {@link StandardCoderObject}
+     */
+    public Forwarder(List<SelectorKey> keys) {
+        this.keys = keys;
+    }
+
+    /**
+     * Registers a listener for messages containing the given field values.
+     *
+     * @param values field values of interest, in one-to-one correspondence with the keys
+     * @param listener listener to register
+     */
+    public void register(List<String> values, BiConsumer<String, StandardCoderObject> listener) {
+        if (keys.size() != values.size()) {
+            throw new IllegalArgumentException("key/value mismatch");
+        }
+
+        values2listeners.compute(values, (key, listeners) -> {
+            Map<BiConsumer<String, StandardCoderObject>, String> map = listeners;
+            if (map == null) {
+                map = new ConcurrentHashMap<>();
+            }
+
+            map.put(listener, "");
+            return map;
+        });
+    }
+
+    /**
+     * Unregisters a listener for messages containing the given field values.
+     *
+     * @param values field values of interest, in one-to-one correspondence with the keys
+     * @param listener listener to unregister
+     */
+    public void unregister(List<String> values, BiConsumer<String, StandardCoderObject> listener) {
+        values2listeners.computeIfPresent(values, (key, listeners) -> {
+            listeners.remove(listener);
+            return (listeners.isEmpty() ? null : listeners);
+        });
+    }
+
+    /**
+     * Processes a message, forwarding it to the appropriate listeners, if any.
+     *
+     * @param textMessage original text message that was received
+     * @param scoMessage decoded text message
+     */
+    public void onMessage(String textMessage, StandardCoderObject scoMessage) {
+        // extract the key values from the message
+        List<String> values = new ArrayList<>(keys.size());
+        for (SelectorKey key : keys) {
+            String value = key.extractField(scoMessage);
+            if (value == null) {
+                /*
+                 * No value for this field, so this message is not relevant to this
+                 * forwarder.
+                 */
+                return;
+            }
+
+            values.add(value);
+        }
+
+        // get the listeners for this set of values
+        Map<BiConsumer<String, StandardCoderObject>, String> listeners = values2listeners.get(values);
+        if (listeners == null) {
+            // no listeners for this particular list of values
+            return;
+        }
+
+
+        // forward the message to each listener
+        for (BiConsumer<String, StandardCoderObject> listener : listeners.keySet()) {
+            try {
+                listener.accept(textMessage, scoMessage);
+            } catch (RuntimeException e) {
+                logger.warn("exception thrown by listener {}", Util.ident(listener), e);
+            }
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java
new file mode 100644
index 0000000..fc57273
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java
@@ -0,0 +1,57 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import lombok.EqualsAndHashCode;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+/**
+ * Selector key, which contains a hierarchical list of Strings and Integers that are used
+ * to extract the content of a field, typically from a {@link StandardCoderObject}.
+ */
+@EqualsAndHashCode
+public class SelectorKey {
+
+    /**
+     * Names and indices used to extract the field's value.
+     */
+    private final Object[] fieldIdentifiers;
+
+    /**
+     * Constructs the object.
+     *
+     * @param fieldIdentifiers names and indices used to extract the field's value
+     */
+    public SelectorKey(Object... fieldIdentifiers) {
+        this.fieldIdentifiers = fieldIdentifiers;
+    }
+
+    /**
+     * Extracts the given field from an object.
+     *
+     * @param object object from which to extract the field
+     * @return the extracted value, or {@code null} if the object does not contain the
+     *         field
+     */
+    public String extractField(StandardCoderObject object) {
+        return object.getString(fieldIdentifiers);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java
new file mode 100644
index 0000000..93beab1
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java
@@ -0,0 +1,104 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicListener;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A topic listener. When a message arrives on a topic, it is forwarded to listeners based
+ * on the content of fields found within the message. However, depending on the message
+ * type, the relevant fields might be found in different places within the message's
+ * object hierarchy. For each different list of keys, this class maintains a
+ * {@link Forwarder}, which is used to forward the message to all relevant listeners.
+ * <p/>
+ * Once a selector has been added, it is not removed until {@link #shutdown()} is invoked.
+ * As selectors are typically only added by Operators, and not by individual Operations,
+ * this should not pose a problem.
+ */
+public class TopicListenerImpl implements TopicListener {
+    private static final Logger logger = LoggerFactory.getLogger(TopicListenerImpl.class);
+    private static final StandardCoder coder = new StandardCoder();
+
+    /**
+     * Maps selector to a forwarder.
+     */
+    private final Map<List<SelectorKey>, Forwarder> selector2forwarder = new ConcurrentHashMap<>();
+
+
+    /**
+     * Removes all forwarders.
+     */
+    public void shutdown() {
+        selector2forwarder.clear();
+    }
+
+    /**
+     * Adds a forwarder, if it doesn't already exist.
+     *
+     * @param keys the selector keys
+     * @return the forwarder associated with the given selector keys
+     */
+    public Forwarder addForwarder(SelectorKey... keys) {
+        return addForwarder(Arrays.asList(keys));
+    }
+
+    /**
+     * Adds a forwarder, if it doesn't already exist.
+     *
+     * @param keys the selector keys
+     * @return the forwarder associated with the given selector keys
+     */
+    public Forwarder addForwarder(List<SelectorKey> keys) {
+        return selector2forwarder.computeIfAbsent(keys, key -> new Forwarder(keys));
+    }
+
+    /**
+     * Decodes the message and then forwards it to each forwarder for processing.
+     */
+    @Override
+    public void onTopicEvent(CommInfrastructure infra, String topic, String message) {
+        StandardCoderObject object;
+        try {
+            object = coder.decode(message, StandardCoderObject.class);
+        } catch (CoderException e) {
+            logger.warn("cannot decode message", e);
+            return;
+        }
+
+        /*
+         * We don't know which selector is appropriate for the message, so we just let
+         * them all take a crack at it.
+         */
+        for (Forwarder forwarder : selector2forwarder.values()) {
+            forwarder.onMessage(message, object);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
index 851a791..efc7bb8 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
@@ -65,7 +65,7 @@
     private Map<String, Object> sub2;
     private Map<String, Object> sub3;
     private Map<String, Object> sub4;
-    private Map<String, Object> params;
+    private Map<String, Map<String, Object>> params;
 
     private ActorService service;
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java
deleted file mode 100644
index 31c6d20..0000000
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actorserviceprovider;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicReference;
-import org.junit.Before;
-import org.junit.Test;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-
-public class AsyncResponseHandlerTest {
-
-    private static final String ACTOR = "my-actor";
-    private static final String OPERATION = "my-operation";
-    private static final UUID REQ_ID = UUID.randomUUID();
-    private static final String TEXT = "some text";
-
-    private VirtualControlLoopEvent event;
-    private ControlLoopEventContext context;
-    private ControlLoopOperationParams params;
-    private OperationOutcome outcome;
-    private MyHandler handler;
-
-    /**
-     * Initializes all fields, including {@link #handler}.
-     */
-    @Before
-    public void setUp() {
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-
-        context = new ControlLoopEventContext(event);
-        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
-        outcome = params.makeOutcome();
-
-        handler = new MyHandler(params, outcome);
-    }
-
-    @Test
-    public void testAsyncResponseHandler_testGetParams_testGetOutcome() {
-        assertSame(params, handler.getParams());
-        assertSame(outcome, handler.getOutcome());
-    }
-
-    @Test
-    public void testHandle() {
-        CompletableFuture<String> future = new CompletableFuture<>();
-        handler.handle(future).complete(outcome);
-
-        assertTrue(future.isCancelled());
-    }
-
-    @Test
-    public void testCompleted() throws Exception {
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.completed(TEXT);
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-        assertEquals(PolicyResult.FAILURE_RETRIES, outcome.getResult());
-        assertEquals(TEXT, outcome.getMessage());
-    }
-
-    /**
-     * Tests completed() when doCompleted() throws an exception.
-     */
-    @Test
-    public void testCompletedException() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        outcome = params.makeOutcome();
-        handler = new MyHandler(params, outcome) {
-            @Override
-            protected OperationOutcome doComplete(String rawResponse) {
-                throw except;
-            }
-        };
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.completed(TEXT);
-        assertTrue(result.isCompletedExceptionally());
-
-        AtomicReference<Throwable> thrown = new AtomicReference<>();
-        result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
-        assertSame(except, thrown.get());
-    }
-
-    @Test
-    public void testFailed() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.failed(except);
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-        assertEquals(PolicyResult.FAILURE_GUARD, outcome.getResult());
-    }
-
-    /**
-     * Tests failed() when doFailed() throws an exception.
-     */
-    @Test
-    public void testFailedException() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        outcome = params.makeOutcome();
-        handler = new MyHandler(params, outcome) {
-            @Override
-            protected OperationOutcome doFailed(Throwable thrown) {
-                throw except;
-            }
-        };
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.failed(except);
-        assertTrue(result.isCompletedExceptionally());
-
-        AtomicReference<Throwable> thrown = new AtomicReference<>();
-        result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
-        assertSame(except, thrown.get());
-    }
-
-    private class MyHandler extends AsyncResponseHandler<String> {
-
-        public MyHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
-            super(params, outcome);
-        }
-
-        @Override
-        protected OperationOutcome doComplete(String rawResponse) {
-            OperationOutcome outcome = getOutcome();
-            outcome.setResult(PolicyResult.FAILURE_RETRIES);
-            outcome.setMessage(rawResponse);
-            return outcome;
-        }
-
-        @Override
-        protected OperationOutcome doFailed(Throwable thrown) {
-            OperationOutcome outcome = getOutcome();
-            outcome.setResult(PolicyResult.FAILURE_GUARD);
-            return outcome;
-        }
-    }
-}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
index 4a3f321..0a2a5a9 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
@@ -39,16 +39,10 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
 import org.slf4j.LoggerFactory;
 
 public class UtilTest {
-    private static final String MY_REQUEST = "my-request";
-    private static final String URL = "my-url";
-    private static final String OUT_URL = "OUT|REST|my-url";
-    private static final String IN_URL = "IN|REST|my-url";
     protected static final String EXPECTED_EXCEPTION = "expected exception";
 
     /**
@@ -89,82 +83,6 @@
     }
 
     @Test
-    public void testLogRestRequest() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        Util.logRestRequest(URL, new Abc(10, null, null));
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(OUT_URL).contains("{\n  \"intValue\": 10\n}");
-
-        // log a plain string
-        appender.clearExtractions();
-        Util.logRestRequest(URL, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(OUT_URL).contains(MY_REQUEST);
-
-        // exception from coder
-        StandardCoder coder = new StandardCoder() {
-            @Override
-            public String encode(Object object, boolean pretty) throws CoderException {
-                throw new CoderException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        appender.clearExtractions();
-        Util.logRestRequest(coder, URL, new Abc(11, null, null));
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print request");
-        assertThat(output.get(1)).contains(OUT_URL);
-    }
-
-    @Test
-    public void testLogRestResponse() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        Util.logRestResponse(URL, new Abc(10, null, null));
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains("{\n  \"intValue\": 10\n}");
-
-        // log null response
-        appender.clearExtractions();
-        Util.logRestResponse(URL, null);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains("null");
-
-        // log a plain string
-        appender.clearExtractions();
-        Util.logRestResponse(URL, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains(MY_REQUEST);
-
-        // exception from coder
-        StandardCoder coder = new StandardCoder() {
-            @Override
-            public String encode(Object object, boolean pretty) throws CoderException {
-                throw new CoderException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        appender.clearExtractions();
-        Util.logRestResponse(coder, URL, new Abc(11, null, null));
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print response");
-        assertThat(output.get(1)).contains(IN_URL);
-    }
-
-    @Test
     public void testRunFunction() {
         // no exception, no log
         AtomicInteger count = new AtomicInteger();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
index 0d917ad..cf24262 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
@@ -24,16 +24,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 
 public class ControlLoopEventContextTest {
     private static final UUID REQ_ID = UUID.randomUUID();
+    private static final String ITEM_KEY = "obtain-C";
 
     private Map<String, String> enrichment;
     private VirtualControlLoopEvent event;
@@ -84,4 +93,54 @@
         int intValue = context.getProperty("def");
         assertEquals(100, intValue);
     }
+
+    @Test
+    public void testObtain() {
+        final ControlLoopOperationParams params = mock(ControlLoopOperationParams.class);
+
+        // property is already loaded
+        context.setProperty("obtain-A", "value-A");
+        assertNull(context.obtain("obtain-A", params));
+
+        // new property - should retrieve
+        CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+        when(params.start()).thenReturn(future);
+        assertSame(future, context.obtain("obtain-B", params));
+
+        // repeat - should get the same future, without invoking start() again
+        assertSame(future, context.obtain("obtain-B", params));
+        verify(params).start();
+
+        // arrange for another invoker to start while this one is starting
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+
+        when(params.start()).thenAnswer(args -> {
+
+            ControlLoopOperationParams params2 = mock(ControlLoopOperationParams.class);
+            when(params2.start()).thenReturn(future2);
+
+            assertSame(future2, context.obtain(ITEM_KEY, params2));
+            return future;
+        });
+
+        assertSame(future2, context.obtain(ITEM_KEY, params));
+
+        // should have canceled the interrupted future
+        assertTrue(future.isCancelled());
+
+        // return a new future next time start() is called
+        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+        when(params.start()).thenReturn(future3);
+
+        // repeat - should get the same future
+        assertSame(future2, context.obtain(ITEM_KEY, params));
+        assertSame(future2, context.obtain(ITEM_KEY, params));
+
+        // future2 should still be active
+        assertFalse(future2.isCancelled());
+
+        // cancel it - now we should get the new future
+        future2.cancel(false);
+        assertSame(future3, context.obtain(ITEM_KEY, params));
+    }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
index a209fb0..92cbbe7 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
@@ -42,7 +42,9 @@
 import org.junit.Test;
 import org.onap.policy.common.parameters.ObjectValidationResult;
 import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.Operator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
 public class ActorImplTest {
@@ -375,10 +377,15 @@
         return actor;
     }
 
-    private static class MyOper extends OperatorPartial implements Operator {
+    private static class MyOper extends OperatorPartial {
 
         public MyOper(String name) {
             super(ACTOR_NAME, name);
         }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
     }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java
new file mode 100644
index 0000000..e1606ae
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java
@@ -0,0 +1,242 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.function.Function;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+
+public class BidirectionalTopicActorTest {
+
+    private static final String ACTOR = "my-actor";
+    private static final String UNKNOWN = "unknown";
+    private static final String MY_SINK = "my-sink";
+    private static final String MY_SOURCE1 = "my-source-A";
+    private static final String MY_SOURCE2 = "my-source-B";
+    private static final int TIMEOUT = 10;
+
+    @Mock
+    private BidirectionalTopicHandler handler1;
+    @Mock
+    private BidirectionalTopicHandler handler2;
+
+    private BidirectionalTopicActor actor;
+
+
+    /**
+     * Configures the endpoints.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() {
+        Properties props = new Properties();
+        props.setProperty("noop.sink.topics", MY_SINK);
+        props.setProperty("noop.source.topics", MY_SOURCE1 + "," + MY_SOURCE2);
+
+        // clear all topics and then configure one sink and two sources
+        TopicEndpointManager.getManager().shutdown();
+        TopicEndpointManager.getManager().addTopicSinks(props);
+        TopicEndpointManager.getManager().addTopicSources(props);
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() {
+        // clear all topics after the tests
+        TopicEndpointManager.getManager().shutdown();
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        actor = new MyActor();
+        actor.configure(Util.translateToMap(ACTOR, makeParams()));
+    }
+
+    @Test
+    public void testDoStart() throws BidirectionalTopicClientException {
+        // allocate some handlers
+        actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+        actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+        // start it
+        actor.start();
+
+        verify(handler1).start();
+        verify(handler2).start();
+
+        verify(handler1, never()).stop();
+        verify(handler2, never()).stop();
+
+        verify(handler1, never()).shutdown();
+        verify(handler2, never()).shutdown();
+    }
+
+    @Test
+    public void testDoStop() throws BidirectionalTopicClientException {
+        // allocate some handlers
+        actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+        actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+        // start it
+        actor.start();
+
+        // stop it
+        actor.stop();
+
+        verify(handler1).stop();
+        verify(handler2).stop();
+
+        verify(handler1, never()).shutdown();
+        verify(handler2, never()).shutdown();
+    }
+
+    @Test
+    public void testDoShutdown() {
+        // allocate some handlers
+        actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+        actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+        // start it
+        actor.start();
+
+        // stop it
+        actor.shutdown();
+
+        verify(handler1).shutdown();
+        verify(handler2).shutdown();
+
+        verify(handler1, never()).stop();
+        verify(handler2, never()).stop();
+    }
+
+    @Test
+    public void testMakeOperatorParameters() {
+        BidirectionalTopicActorParams params = makeParams();
+
+        final BidirectionalTopicActor prov = new BidirectionalTopicActor(ACTOR);
+        Function<String, Map<String, Object>> maker =
+                        prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params));
+
+        assertNull(maker.apply(UNKNOWN));
+
+        // use a TreeMap to ensure the properties are sorted
+        assertEquals("{sinkTopic=my-sink, sourceTopic=my-source-A, timeoutSec=10}",
+                        new TreeMap<>(maker.apply("operA")).toString());
+
+        assertEquals("{sinkTopic=my-sink, sourceTopic=topicB, timeoutSec=10}",
+                        new TreeMap<>(maker.apply("operB")).toString());
+
+        // with invalid actor parameters
+        params.setOperation(null);
+        assertThatThrownBy(() -> prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params)))
+                        .isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    @Test
+    public void testBidirectionalTopicActor() {
+        assertEquals(ACTOR, actor.getName());
+        assertEquals(ACTOR, actor.getFullName());
+    }
+
+    @Test
+    public void testGetTopicHandler() {
+        assertSame(handler1, actor.getTopicHandler(MY_SINK, MY_SOURCE1));
+        assertSame(handler2, actor.getTopicHandler(MY_SINK, MY_SOURCE2));
+
+        assertThatIllegalArgumentException().isThrownBy(() -> actor.getTopicHandler(UNKNOWN, MY_SOURCE1));
+    }
+
+    @Test
+    public void testMakeTopicHandler() {
+        // use a real actor
+        actor = new BidirectionalTopicActor(ACTOR);
+
+        handler1 = actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+        handler2 = actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+        assertNotNull(handler1);
+        assertNotNull(handler2);
+        assertNotSame(handler1, handler2);
+    }
+
+
+    private BidirectionalTopicActorParams makeParams() {
+        BidirectionalTopicActorParams params = new BidirectionalTopicActorParams();
+        params.setSinkTopic(MY_SINK);
+        params.setSourceTopic(MY_SOURCE1);
+        params.setTimeoutSec(TIMEOUT);
+
+        // @formatter:off
+        params.setOperation(Map.of(
+                        "operA", Map.of(),
+                        "operB", Map.of("sourceTopic", "topicB")));
+        // @formatter:on
+        return params;
+    }
+
+    private class MyActor extends BidirectionalTopicActor {
+
+        public MyActor() {
+            super(ACTOR);
+        }
+
+        @Override
+        protected BidirectionalTopicHandler makeTopicHandler(String sinkTopic, String sourceTopic)
+                        throws BidirectionalTopicClientException {
+
+            if (MY_SINK.equals(sinkTopic)) {
+                if (MY_SOURCE1.equals(sourceTopic)) {
+                    return handler1;
+                } else if (MY_SOURCE2.equals(sourceTopic)) {
+                    return handler2;
+                }
+            }
+
+            throw new BidirectionalTopicClientException("no topic " + sinkTopic + "/" + sourceTopic);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java
new file mode 100644
index 0000000..ceb63fe
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java
@@ -0,0 +1,403 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class BidirectionalTopicOperationTest {
+    private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
+    private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String REQ_ID = "my-request-id";
+    private static final String MY_SINK = "my-sink";
+    private static final String MY_SOURCE = "my-source";
+    private static final String TEXT = "some text";
+    private static final int TIMEOUT_SEC = 10;
+    private static final long TIMEOUT_MS = 1000 * TIMEOUT_SEC;
+    private static final int MAX_REQUESTS = 100;
+
+    private static final StandardCoder coder = new StandardCoder();
+
+    @Mock
+    private BidirectionalTopicOperator operator;
+    @Mock
+    private BidirectionalTopicHandler handler;
+    @Mock
+    private Forwarder forwarder;
+
+    @Captor
+    private ArgumentCaptor<BiConsumer<String, StandardCoderObject>> listenerCaptor;
+
+    private ControlLoopOperationParams params;
+    private BidirectionalTopicParams topicParams;
+    private OperationOutcome outcome;
+    private StandardCoderObject stdResponse;
+    private String responseText;
+    private PseudoExecutor executor;
+    private int ntimes;
+    private BidirectionalTopicOperation<MyRequest, MyResponse> oper;
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws CoderException {
+        MockitoAnnotations.initMocks(this);
+
+        topicParams = BidirectionalTopicParams.builder().sourceTopic(MY_SOURCE).sinkTopic(MY_SINK)
+                        .timeoutSec(TIMEOUT_SEC).build();
+
+        when(operator.getActorName()).thenReturn(ACTOR);
+        when(operator.getName()).thenReturn(OPERATION);
+        when(operator.getTopicHandler()).thenReturn(handler);
+        when(operator.getForwarder()).thenReturn(forwarder);
+        when(operator.getParams()).thenReturn(topicParams);
+        when(operator.isAlive()).thenReturn(true);
+
+        when(handler.send(any())).thenReturn(true);
+        when(handler.getSinkTopicCommInfrastructure()).thenReturn(SINK_INFRA);
+
+        executor = new PseudoExecutor();
+
+        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).executor(executor).build();
+        outcome = params.makeOutcome();
+
+        responseText = coder.encode(new MyResponse());
+        stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+        ntimes = 1;
+
+        oper = new MyOperation();
+    }
+
+    @Test
+    public void testConstructor_testGetTopicHandler_testGetForwarder_testGetTopicParams() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertSame(handler, oper.getTopicHandler());
+        assertSame(forwarder, oper.getForwarder());
+        assertSame(topicParams, oper.getTopicParams());
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs());
+        assertSame(MyResponse.class, oper.getResponseClass());
+    }
+
+    @Test
+    public void testStartOperationAsync() throws Exception {
+
+        // tell it to expect three responses
+        ntimes = 3;
+
+        CompletableFuture<OperationOutcome> future = oper.startOperationAsync(1, outcome);
+        assertFalse(future.isDone());
+
+        verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+        verify(forwarder, never()).unregister(any(), any());
+
+        verify(handler).send(any());
+
+        // provide first response
+        listenerCaptor.getValue().accept(responseText, stdResponse);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(future.isDone());
+
+        // provide second response
+        listenerCaptor.getValue().accept(responseText, stdResponse);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(future.isDone());
+
+        // provide final response
+        listenerCaptor.getValue().accept(responseText, stdResponse);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(future.isDone());
+
+        assertSame(outcome, future.get());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+        verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+    }
+
+    /**
+     * Tests startOperationAsync() when the publisher throws an exception.
+     */
+    @Test
+    public void testStartOperationAsyncException() throws Exception {
+        // indicate that nothing was published
+        when(handler.send(any())).thenReturn(false);
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.startOperationAsync(1, outcome));
+
+        verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+        // must still unregister
+        verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+    }
+
+    @Test
+    public void testGetTimeoutMsInteger() {
+        // use default
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs(null));
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs(0));
+
+        // use provided value
+        assertEquals(5000, oper.getTimeoutMs(5));
+    }
+
+    @Test
+    public void testPublishRequest() {
+        assertThatCode(() -> oper.publishRequest(new MyRequest())).doesNotThrowAnyException();
+    }
+
+    /**
+     * Tests publishRequest() when nothing is published.
+     */
+    @Test
+    public void testPublishRequestUnpublished() {
+        when(handler.send(any())).thenReturn(false);
+        assertThatIllegalStateException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+    }
+
+    /**
+     * Tests publishRequest() when the request type is a String.
+     */
+    @Test
+    public void testPublishRequestString() {
+        MyStringOperation oper2 = new MyStringOperation();
+        assertThatCode(() -> oper2.publishRequest(TEXT)).doesNotThrowAnyException();
+    }
+
+    /**
+     * Tests publishRequest() when the coder throws an exception.
+     */
+    @Test
+    public void testPublishRequestException() {
+        setOperCoderException();
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a String.
+     */
+    @Test
+    public void testProcessResponseSuccessString() {
+        MyStringOperation oper2 = new MyStringOperation();
+
+        assertSame(outcome, oper2.processResponse(outcome, TEXT, null));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a
+     * StandardCoderObject.
+     */
+    @Test
+    public void testProcessResponseSuccessSco() {
+        MyScoOperation oper2 = new MyScoOperation();
+
+        assertSame(outcome, oper2.processResponse(outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a failure.
+     */
+    @Test
+    public void testProcessResponseFailure() throws CoderException {
+        // indicate error in the response
+        MyResponse resp = new MyResponse();
+        resp.setOutput("error");
+
+        responseText = coder.encode(resp);
+        stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+        assertSame(outcome, oper.processResponse(outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder succeeds.
+     */
+    @Test
+    public void testProcessResponseDecodeOk() throws CoderException {
+        assertSame(outcome, oper.processResponse(outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder throws an exception.
+     */
+    @Test
+    public void testProcessResponseDecodeExcept() throws CoderException {
+        // @formatter:off
+        assertThatIllegalArgumentException().isThrownBy(
+            () -> oper.processResponse(outcome, "{invalid json", stdResponse));
+        // @formatter:on
+    }
+
+    @Test
+    public void testPostProcessResponse() {
+        assertThatCode(() -> oper.postProcessResponse(outcome, null, null)).doesNotThrowAnyException();
+    }
+
+    @Test
+    public void testMakeCoder() {
+        assertNotNull(oper.makeCoder());
+    }
+
+    /**
+     * Creates a new {@link #oper} whose coder will throw an exception.
+     */
+    private void setOperCoderException() {
+        oper = new MyOperation() {
+            @Override
+            protected Coder makeCoder() {
+                return new StandardCoder() {
+                    @Override
+                    public String encode(Object object, boolean pretty) throws CoderException {
+                        throw new CoderException(EXPECTED_EXCEPTION);
+                    }
+                };
+            }
+        };
+    }
+
+    @Getter
+    @Setter
+    public static class MyRequest {
+        private String theRequestId = REQ_ID;
+        private String input;
+    }
+
+    @Getter
+    @Setter
+    public static class MyResponse {
+        private String requestId = REQ_ID;
+        private String output;
+    }
+
+
+    private class MyStringOperation extends BidirectionalTopicOperation<String, String> {
+        public MyStringOperation() {
+            super(BidirectionalTopicOperationTest.this.params, operator, String.class);
+        }
+
+        @Override
+        protected String makeRequest(int attempt) {
+            return TEXT;
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, String request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected Status detmStatus(String rawResponse, String response) {
+            return (response != null ? Status.SUCCESS : Status.FAILURE);
+        }
+    }
+
+
+    private class MyScoOperation extends BidirectionalTopicOperation<MyRequest, StandardCoderObject> {
+        public MyScoOperation() {
+            super(BidirectionalTopicOperationTest.this.params, operator, StandardCoderObject.class);
+        }
+
+        @Override
+        protected MyRequest makeRequest(int attempt) {
+            return new MyRequest();
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected Status detmStatus(String rawResponse, StandardCoderObject response) {
+            return (response.getString("output") == null ? Status.SUCCESS : Status.FAILURE);
+        }
+    }
+
+
+    private class MyOperation extends BidirectionalTopicOperation<MyRequest, MyResponse> {
+        public MyOperation() {
+            super(BidirectionalTopicOperationTest.this.params, operator, MyResponse.class);
+        }
+
+        @Override
+        protected MyRequest makeRequest(int attempt) {
+            return new MyRequest();
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected Status detmStatus(String rawResponse, MyResponse response) {
+            if (--ntimes <= 0) {
+                return (response.getOutput() == null ? Status.SUCCESS : Status.FAILURE);
+            }
+
+            return Status.STILL_WAITING;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java
new file mode 100644
index 0000000..4fae782
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java
@@ -0,0 +1,143 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+
+public class BidirectionalTopicOperatorTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String MY_SOURCE = "my-source";
+    private static final String MY_SINK = "my-target";
+    private static final int TIMEOUT_SEC = 10;
+
+    @Mock
+    private BidirectionalTopicManager mgr;
+    @Mock
+    private BidirectionalTopicHandler handler;
+    @Mock
+    private Forwarder forwarder;
+    @Mock
+    private BidirectionalTopicOperation<String, Integer> operation;
+
+    private List<SelectorKey> keys;
+    private BidirectionalTopicParams params;
+    private MyOperator oper;
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        keys = List.of(new SelectorKey(""));
+
+        when(mgr.getTopicHandler(MY_SINK, MY_SOURCE)).thenReturn(handler);
+        when(handler.addForwarder(keys)).thenReturn(forwarder);
+
+        oper = new MyOperator(keys);
+
+        params = BidirectionalTopicParams.builder().sourceTopic(MY_SOURCE).sinkTopic(MY_SINK).timeoutSec(TIMEOUT_SEC)
+                        .build();
+        oper.configure(Util.translateToMap(OPERATION, params));
+        oper.start();
+    }
+
+    @Test
+    public void testConstructor_testGetParams_testGetTopicHandler_testGetForwarder() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(params, oper.getParams());
+        assertSame(handler, oper.getTopicHandler());
+        assertSame(forwarder, oper.getForwarder());
+    }
+
+    @Test
+    public void testDoConfigure() {
+        oper.stop();
+
+        // invalid parameters
+        params.setSourceTopic(null);
+        assertThatThrownBy(() -> oper.configure(Util.translateToMap(OPERATION, params)))
+                        .isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    @Test
+    public void testMakeOperator() {
+        AtomicReference<ControlLoopOperationParams> paramsRef = new AtomicReference<>();
+        AtomicReference<BidirectionalTopicOperator> operRef = new AtomicReference<>();
+
+        // @formatter:off
+        BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+                    BidirectionalTopicOperation<String, Integer>> maker =
+                        (params, operator) -> {
+                            paramsRef.set(params);
+                            operRef.set(operator);
+                            return operation;
+                        };
+        // @formatter:on
+
+        BidirectionalTopicOperator oper2 =
+                        BidirectionalTopicOperator.makeOperator(ACTOR, OPERATION, mgr, maker, new SelectorKey(""));
+
+        assertEquals(ACTOR, oper2.getActorName());
+        assertEquals(OPERATION, oper2.getName());
+
+        ControlLoopOperationParams params2 = ControlLoopOperationParams.builder().build();
+
+        assertSame(operation, oper2.buildOperation(params2));
+        assertSame(params2, paramsRef.get());
+        assertSame(oper2, operRef.get());
+    }
+
+
+    private class MyOperator extends BidirectionalTopicOperator {
+        public MyOperator(List<SelectorKey> selectorKeys) {
+            super(ACTOR, OPERATION, mgr, selectorKeys);
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
index 2da7899..80b1d42 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
@@ -38,7 +38,7 @@
     private static final String ACTOR = "my-actor";
     private static final String UNKNOWN = "unknown";
     private static final String CLIENT = "my-client";
-    private static final long TIMEOUT = 10L;
+    private static final int TIMEOUT = 10;
 
     private HttpActor actor;
 
@@ -52,7 +52,12 @@
         HttpActorParams params = new HttpActorParams();
         params.setClientName(CLIENT);
         params.setTimeoutSec(TIMEOUT);
-        params.setPath(Map.of("operA", "urlA", "operB", "urlB"));
+
+        // @formatter:off
+        params.setOperation(Map.of(
+                        "operA", Map.of("path", "urlA"),
+                        "operB", Map.of("path", "urlB")));
+        // @formatter:on
 
         final HttpActor prov = new HttpActor(ACTOR);
         Function<String, Map<String, Object>> maker =
@@ -68,7 +73,7 @@
                         new TreeMap<>(maker.apply("operB")).toString());
 
         // with invalid actor parameters
-        params.setClientName(null);
+        params.setOperation(null);
         assertThatThrownBy(() -> prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params)))
                         .isInstanceOf(ParameterValidationRuntimeException.class);
     }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
new file mode 100644
index 0000000..8189c74
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
@@ -0,0 +1,674 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
+import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
+import org.onap.policy.common.endpoints.http.server.HttpServletServer;
+import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
+import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.gson.GsonMessageBodyHandler;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.network.NetworkUtil;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class HttpOperationTest {
+
+    private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-name";
+    private static final String HTTP_CLIENT = "my-client";
+    private static final String HTTP_NO_SERVER = "my-http-no-server-client";
+    private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
+    private static final String BASE_URI = "oper";
+    private static final String PATH = "/my-path";
+    private static final String TEXT = "my-text";
+    private static final UUID REQ_ID = UUID.randomUUID();
+
+    /**
+     * {@code True} if the server should reject the request, {@code false} otherwise.
+     */
+    private static boolean rejectRequest;
+
+    // call counts of each method type in the server
+    private static int nget;
+    private static int npost;
+    private static int nput;
+    private static int ndelete;
+
+    @Mock
+    private HttpClient client;
+
+    @Mock
+    private Response response;
+
+    private VirtualControlLoopEvent event;
+    private ControlLoopEventContext context;
+    private ControlLoopOperationParams params;
+    private OperationOutcome outcome;
+    private AtomicReference<InvocationCallback<Response>> callback;
+    private Future<Response> future;
+    private HttpOperator operator;
+    private MyGetOperation<String> oper;
+
+    /**
+     * Starts the simulator.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        // allocate a port
+        int port = NetworkUtil.allocPort();
+
+        /*
+         * Start the simulator. Must use "Properties" to configure it, otherwise the
+         * server will use the wrong serialization provider.
+         */
+        Properties svrprops = getServerProperties("my-server", port);
+        HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
+
+        if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
+            HttpServletServerFactoryInstance.getServerFactory().destroy();
+            throw new IllegalStateException("server is not running");
+        }
+
+        /*
+         * Start the clients, one to the server, and one to a non-existent server.
+         */
+        TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI)
+                        .serializationProvider(GsonMessageBodyHandler.class.getName());
+
+        HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
+
+        HttpClientFactoryInstance.getClientFactory()
+                        .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
+    }
+
+    /**
+     * Destroys the Http factories and stops the appender.
+     */
+    @AfterClass
+    public static void tearDownAfterClass() {
+        HttpClientFactoryInstance.getClientFactory().destroy();
+        HttpServletServerFactoryInstance.getServerFactory().destroy();
+    }
+
+    /**
+     * Initializes fields, including {@link #oper}, and resets the static fields used by
+     * the REST server.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        rejectRequest = false;
+        nget = 0;
+        npost = 0;
+        nput = 0;
+        ndelete = 0;
+
+        when(response.readEntity(String.class)).thenReturn(TEXT);
+        when(response.getStatus()).thenReturn(200);
+
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+
+        context = new ControlLoopEventContext(event);
+        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+        outcome = params.makeOutcome();
+
+        callback = new AtomicReference<>();
+        future = new CompletableFuture<>();
+
+        operator = new HttpOperator(ACTOR, OPERATION) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+
+            @Override
+            public HttpClient getClient() {
+                return client;
+            }
+        };
+
+        initOper(operator, HTTP_CLIENT);
+
+        oper = new MyGetOperation<>(String.class);
+    }
+
+    @Test
+    public void testHttpOperator() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        assertEquals(Collections.emptyMap(), oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakePath() {
+        assertEquals(PATH, oper.makePath());
+    }
+
+    @Test
+    public void testMakeUrl() {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH);
+    }
+
+    @Test
+    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+
+        // use value from operator
+        assertEquals(1000L, oper.getTimeoutMs(null));
+        assertEquals(1000L, oper.getTimeoutMs(0));
+
+        // should use given value
+        assertEquals(20 * 1000L, oper.getTimeoutMs(20));
+
+        // indicate we have a timeout value
+        operator = spy(operator);
+        when(operator.getTimeoutMs()).thenReturn(30L);
+
+        oper = new MyGetOperation<String>(String.class);
+
+        // should use default
+        assertEquals(30L, oper.getTimeoutMs(null));
+        assertEquals(30L, oper.getTimeoutMs(0));
+
+        // should use given value
+        assertEquals(40 * 1000L, oper.getTimeoutMs(40));
+    }
+
+    /**
+     * Tests handleResponse() when it completes.
+     */
+    @Test
+    public void testHandleResponseComplete() throws Exception {
+        CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+            callback.set(cb);
+            return future;
+        });
+
+        assertFalse(future2.isDone());
+        assertNotNull(callback.get());
+        callback.get().completed(response);
+
+        assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
+
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests handleResponse() when it fails.
+     */
+    @Test
+    public void testHandleResponseFailed() throws Exception {
+        CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+            callback.set(cb);
+            return future;
+        });
+
+        assertFalse(future2.isDone());
+        assertNotNull(callback.get());
+        callback.get().failed(EXPECTED_EXCEPTION);
+
+        assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
+
+        // future and future2 may be completed in parallel so we must wait again
+        assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
+        assertTrue(future.isCancelled());
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a String.
+     */
+    @Test
+    public void testProcessResponseSuccessString() throws Exception {
+        CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a failure.
+     */
+    @Test
+    public void testProcessResponseFailure() throws Exception {
+        when(response.getStatus()).thenReturn(555);
+        CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder succeeds.
+     */
+    @Test
+    public void testProcessResponseDecodeOk() throws Exception {
+        when(response.readEntity(String.class)).thenReturn("10");
+
+        MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+        CompletableFuture<OperationOutcome> result = oper2.processResponse(outcome, PATH, response);
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder throws an exception.
+     */
+    @Test
+    public void testProcessResponseDecodeExcept() throws CoderException {
+        MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+        assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
+    }
+
+    @Test
+    public void testPostProcessResponse() {
+        assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
+    }
+
+    @Test
+    public void testIsSuccess() {
+        when(response.getStatus()).thenReturn(200);
+        assertTrue(oper.isSuccess(response, null));
+
+        when(response.getStatus()).thenReturn(555);
+        assertFalse(oper.isSuccess(response, null));
+    }
+
+    /**
+     * Tests a GET.
+     */
+    @Test
+    public void testGet() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, nget);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a DELETE.
+     */
+    @Test
+    public void testDelete() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyDeleteOperation oper2 = new MyDeleteOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, ndelete);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a POST.
+     */
+    @Test
+    public void testPost() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyPostOperation oper2 = new MyPostOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, npost);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a PUT.
+     */
+    @Test
+    public void testPut() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyPutOperation oper2 = new MyPutOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, nput);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    @Test
+    public void testMakeDecoder() {
+        assertNotNull(oper.makeCoder());
+    }
+
+    /**
+     * Gets server properties.
+     *
+     * @param name server name
+     * @param port server port
+     * @return server properties
+     */
+    private static Properties getServerProperties(String name, int port) {
+        final Properties props = new Properties();
+        props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
+
+        final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
+
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
+
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
+                        GsonMessageBodyHandler.class.getName());
+        return props;
+    }
+
+    /**
+     * Initializes the given operator.
+     *
+     * @param operator operator to be initialized
+     * @param clientName name of the client which it should use
+     */
+    private void initOper(HttpOperator operator, String clientName) {
+        operator.stop();
+
+        HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build();
+        Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
+        operator.configure(mapParams);
+        operator.start();
+    }
+
+    /**
+     * Runs the operation.
+     *
+     * @param operator operator on which to start the operation
+     * @return the outcome of the operation, or {@code null} if it does not complete in
+     *         time
+     */
+    private <T> OperationOutcome runOperation(HttpOperation<T> operator)
+                    throws InterruptedException, ExecutionException, TimeoutException {
+
+        CompletableFuture<OperationOutcome> future = operator.start();
+
+        return future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Getter
+    @Setter
+    public static class MyRequest {
+        private String input = "some input";
+    }
+
+    @Getter
+    @Setter
+    public static class MyResponse {
+        private String output = "some output";
+    }
+
+    private class MyGetOperation<T> extends HttpOperation<T> {
+        public MyGetOperation(Class<T> responseClass) {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().get(callback, makePath(), headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyPostOperation extends HttpOperation<MyResponse> {
+        public MyPostOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+            MyRequest request = new MyRequest();
+
+            Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().post(callback, makePath(), entity, headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyPutOperation extends HttpOperation<MyResponse> {
+        public MyPutOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+            MyRequest request = new MyRequest();
+
+            Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().put(callback, makePath(), entity, headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyDeleteOperation extends HttpOperation<String> {
+        public MyDeleteOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().delete(callback, makePath(), headers));
+            // @formatter:on
+        }
+    }
+
+    /**
+     * Simulator.
+     */
+    @Path("/" + BASE_URI)
+    @Produces(MEDIA_TYPE_APPLICATION_JSON)
+    @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
+    public static class Server {
+
+        /**
+         * Generates a response to a GET.
+         *
+         * @return resulting response
+         */
+        @GET
+        @Path(PATH)
+        public Response getRequest() {
+            ++nget;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a POST.
+         *
+         * @param request incoming request
+         * @return resulting response
+         */
+        @POST
+        @Path(PATH)
+        public Response postRequest(MyRequest request) {
+            ++npost;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a PUT.
+         *
+         * @param request incoming request
+         * @return resulting response
+         */
+        @PUT
+        @Path(PATH)
+        public Response putRequest(MyRequest request) {
+            ++nput;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a DELETE.
+         *
+         * @return resulting response
+         */
+        @DELETE
+        @Path(PATH)
+        public Response deleteRequest() {
+            ++ndelete;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
index c006cf3..081bb34 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
@@ -23,19 +23,25 @@
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.onap.policy.common.endpoints.http.client.HttpClient;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
@@ -43,51 +49,33 @@
 
     private static final String ACTOR = "my-actor";
     private static final String OPERATION = "my-name";
-    private static final String CLIENT = "my-client";
-    private static final String PATH = "my-path";
-    private static final long TIMEOUT = 100;
+    private static final String HTTP_CLIENT = "my-client";
+    private static final String PATH = "/my-path";
+    private static final int TIMEOUT = 100;
 
     @Mock
     private HttpClient client;
 
-    private HttpOperator oper;
+    @Mock
+    private HttpClientFactory factory;
+
+    private MyOperator oper;
 
     /**
-     * Initializes fields, including {@link #oper}.
+     * Initializes fields, including {@link #oper}, and resets the static fields used by
+     * the REST server.
      */
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        oper = new HttpOperator(ACTOR, OPERATION);
-    }
+        when(factory.get(HTTP_CLIENT)).thenReturn(client);
 
-    @Test
-    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutSec() {
-        assertNull(oper.getClient());
-        assertNull(oper.getPath());
-        assertEquals(0L, oper.getTimeoutSec());
+        oper = new MyOperator();
 
-        oper = new HttpOperator(ACTOR, OPERATION) {
-            @Override
-            protected HttpClientFactory getClientFactory() {
-                HttpClientFactory factory = mock(HttpClientFactory.class);
-                when(factory.get(CLIENT)).thenReturn(client);
-                return factory;
-            }
-        };
-
-        HttpParams params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+        HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
         Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
         oper.configure(paramMap);
-
-        assertSame(client, oper.getClient());
-        assertEquals(PATH, oper.getPath());
-        assertEquals(TIMEOUT, oper.getTimeoutSec());
-
-        // test invalid parameters
-        paramMap.remove("path");
-        assertThatThrownBy(() -> oper.configure(paramMap)).isInstanceOf(ParameterValidationRuntimeException.class);
     }
 
     @Test
@@ -99,6 +87,78 @@
 
     @Test
     public void testGetClient() {
-        assertNotNull(oper.getClientFactory());
+        assertNotNull(oper.getClient());
+    }
+
+    @Test
+    public void testMakeOperator() {
+        HttpOperator oper2 = HttpOperator.makeOperator(ACTOR, OPERATION, MyOperation::new);
+        assertNotNull(oper2);
+
+        VirtualControlLoopEvent event = new VirtualControlLoopEvent();
+        ControlLoopEventContext context = new ControlLoopEventContext(event);
+        ControlLoopOperationParams params =
+                        ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+        Operation operation1 = oper2.buildOperation(params);
+        assertNotNull(operation1);
+
+        Operation operation2 = oper2.buildOperation(params);
+        assertNotNull(operation2);
+        assertNotSame(operation1, operation2);
+    }
+
+    @Test
+    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+        // start with an UNCONFIGURED operator
+        oper.shutdown();
+        oper = new MyOperator();
+
+        assertNull(oper.getClient());
+        assertNull(oper.getPath());
+
+        // no timeout yet
+        assertEquals(0L, oper.getTimeoutMs());
+
+        HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+        Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
+        oper.configure(paramMap);
+
+        assertSame(client, oper.getClient());
+        assertEquals(PATH, oper.getPath());
+
+        // should use given value
+        assertEquals(TIMEOUT * 1000, oper.getTimeoutMs());
+
+        // test invalid parameters
+        paramMap.remove("path");
+        assertThatThrownBy(() -> oper.configure(paramMap)).isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    private class MyOperator extends HttpOperator {
+        public MyOperator() {
+            super(ACTOR, OPERATION);
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
+
+        @Override
+        protected HttpClientFactory getClientFactory() {
+            return factory;
+        }
+    }
+
+    private class MyOperation extends HttpOperation<String> {
+        public MyOperation(ControlLoopOperationParams params, HttpOperator operator) {
+            super(params, operator, String.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            return null;
+        }
     }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
new file mode 100644
index 0000000..67ac27c
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
@@ -0,0 +1,1295 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import ch.qos.logback.classic.Logger;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
+
+public class OperationPartialTest {
+    private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
+    private static final CommInfrastructure SOURCE_INFRA = CommInfrastructure.UEB;
+    private static final int MAX_REQUESTS = 100;
+    private static final int MAX_PARALLEL = 10;
+    private static final String EXPECTED_EXCEPTION = "expected exception";
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String MY_SINK = "my-sink";
+    private static final String MY_SOURCE = "my-source";
+    private static final String TEXT = "my-text";
+    private static final int TIMEOUT = 1000;
+    private static final UUID REQ_ID = UUID.randomUUID();
+
+    private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
+                    .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
+
+    /**
+     * Used to attach an appender to the class' logger.
+     */
+    private static final Logger logger = (Logger) LoggerFactory.getLogger(OperationPartial.class);
+    private static final ExtractAppender appender = new ExtractAppender();
+
+    private VirtualControlLoopEvent event;
+    private ControlLoopEventContext context;
+    private PseudoExecutor executor;
+    private ControlLoopOperationParams params;
+
+    private MyOper oper;
+
+    private int numStart;
+    private int numEnd;
+
+    private Instant tstart;
+
+    private OperationOutcome opstart;
+    private OperationOutcome opend;
+
+    private OperatorPartial operator;
+
+    /**
+     * Attaches the appender to the logger.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        /**
+         * Attach appender to the logger.
+         */
+        appender.setContext(logger.getLoggerContext());
+        appender.start();
+
+        logger.addAppender(appender);
+    }
+
+    /**
+     * Stops the appender.
+     */
+    @AfterClass
+    public static void tearDownAfterClass() {
+        appender.stop();
+    }
+
+    /**
+     * Initializes the fields, including {@link #oper}.
+     */
+    @Before
+    public void setUp() {
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+
+        context = new ControlLoopEventContext(event);
+        executor = new PseudoExecutor();
+
+        params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
+                        .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
+                        .startCallback(this::starter).targetEntity(MY_SINK).build();
+
+        operator = new OperatorPartial(ACTOR, OPERATION) {
+            @Override
+            public Executor getBlockingExecutor() {
+                return executor;
+            }
+
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+        };
+
+        operator.configure(null);
+        operator.start();
+
+        oper = new MyOper();
+
+        tstart = null;
+
+        opstart = null;
+        opend = null;
+    }
+
+    @Test
+    public void testOperatorPartial_testGetActorName_testGetName() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testGetBlockingThread() throws Exception {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+
+        // use the real executor
+        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATION) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+        };
+
+        oper2.getBlockingExecutor().execute(() -> future.complete(null));
+
+        assertNull(future.get(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Exercises the doXxx() methods.
+     */
+    @Test
+    public void testDoXxx() {
+        assertThatCode(() -> operator.doConfigure(null)).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doStart()).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doStop()).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doShutdown()).doesNotThrowAnyException();
+
+    }
+
+    @Test
+    public void testStart() {
+        verifyRun("testStart", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests startOperation() when the operator is not running.
+     */
+    @Test
+    public void testStartNotRunning() {
+        // stop the operator
+        operator.stop();
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.start());
+    }
+
+    /**
+     * Tests startOperation() when the operation has a preprocessor.
+     */
+    @Test
+    public void testStartWithPreprocessor() {
+        AtomicInteger count = new AtomicInteger();
+
+        CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
+            count.incrementAndGet();
+            return makeSuccess();
+        }, executor);
+
+        oper.setGuard(preproc);
+
+        verifyRun("testStartWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
+
+        assertEquals(1, count.get());
+    }
+
+    /**
+     * Tests start() with multiple running requests.
+     */
+    @Test
+    public void testStartMultiple() {
+        for (int count = 0; count < MAX_PARALLEL; ++count) {
+            oper.start();
+        }
+
+        assertTrue(executor.runAll(MAX_REQUESTS * MAX_PARALLEL));
+
+        assertNotNull(opstart);
+        assertNotNull(opend);
+        assertEquals(PolicyResult.SUCCESS, opend.getResult());
+
+        assertEquals(MAX_PARALLEL, numStart);
+        assertEquals(MAX_PARALLEL, oper.getCount());
+        assertEquals(MAX_PARALLEL, numEnd);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor returns a failure.
+     */
+    @Test
+    public void testStartPreprocessorFailure() {
+        oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+
+        verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor throws an exception.
+     */
+    @Test
+    public void testStartPreprocessorException() {
+        // arrange for the preprocessor to throw an exception
+        oper.setGuard(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
+
+        verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests startPreprocessor() when the pipeline is not running.
+     */
+    @Test
+    public void testStartPreprocessorNotRunning() {
+        // arrange for the preprocessor to return success, which will be ignored
+        oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+
+        oper.start().cancel(false);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+
+        assertNull(opstart);
+        assertNull(opend);
+
+        assertEquals(0, numStart);
+        assertEquals(0, oper.getCount());
+        assertEquals(0, numEnd);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
+     */
+    @Test
+    public void testStartPreprocessorBuilderException() {
+        oper = new MyOper() {
+            @Override
+            protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+                throw new IllegalStateException(EXPECTED_EXCEPTION);
+            }
+        };
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.start());
+
+        // should be nothing in the queue
+        assertEquals(0, executor.getQueueLength());
+    }
+
+    @Test
+    public void testStartPreprocessorAsync() {
+        assertNull(oper.startPreprocessorAsync());
+    }
+
+    @Test
+    public void testStartGuardAsync() {
+        assertNull(oper.startGuardAsync());
+    }
+
+    @Test
+    public void testStartOperationAsync() {
+        oper.start();
+        assertTrue(executor.runAll(MAX_REQUESTS));
+
+        assertEquals(1, oper.getCount());
+    }
+
+    @Test
+    public void testIsSuccess() {
+        OperationOutcome outcome = new OperationOutcome();
+
+        outcome.setResult(PolicyResult.SUCCESS);
+        assertTrue(oper.isSuccess(outcome));
+
+        for (PolicyResult failure : FAILURE_RESULTS) {
+            outcome.setResult(failure);
+            assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
+        }
+    }
+
+    @Test
+    public void testIsActorFailed() {
+        assertFalse(oper.isActorFailed(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        // incorrect outcome
+        outcome.setResult(PolicyResult.SUCCESS);
+        assertFalse(oper.isActorFailed(outcome));
+
+        outcome.setResult(PolicyResult.FAILURE_RETRIES);
+        assertFalse(oper.isActorFailed(outcome));
+
+        // correct outcome
+        outcome.setResult(PolicyResult.FAILURE);
+
+        // incorrect actor
+        outcome.setActor(MY_SINK);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setActor(null);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setActor(ACTOR);
+
+        // incorrect operation
+        outcome.setOperation(MY_SINK);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setOperation(null);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setOperation(OPERATION);
+
+        // correct values
+        assertTrue(oper.isActorFailed(outcome));
+    }
+
+    @Test
+    public void testDoOperation() {
+        /*
+         * Use an operation that doesn't override doOperation().
+         */
+        OperationPartial oper2 = new OperationPartial(params, operator) {};
+
+        oper2.start();
+        assertTrue(executor.runAll(MAX_REQUESTS));
+
+        assertNotNull(opend);
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
+    }
+
+    @Test
+    public void testTimeout() throws Exception {
+
+        // use a real executor
+        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+        // trigger timeout very quickly
+        oper = new MyOper() {
+            @Override
+            protected long getTimeoutMs(Integer timeoutSec) {
+                return 1;
+            }
+
+            @Override
+            protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+                OperationOutcome outcome2 = params.makeOutcome();
+                outcome2.setResult(PolicyResult.SUCCESS);
+
+                /*
+                 * Create an incomplete future that will timeout after the operation's
+                 * timeout. If it fires before the other timer, then it will return a
+                 * SUCCESS outcome.
+                 */
+                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+                future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
+                                params.getExecutor());
+
+                return future;
+            }
+        };
+
+        assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.start().get().getResult());
+    }
+
+    /**
+     * Tests retry functions, when the count is set to zero and retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
+        params = params.toBuilder().retry(0).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
+    }
+
+    /**
+     * Tests retry functions, when the count is null and retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
+        params = params.toBuilder().retry(null).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
+    }
+
+    /**
+     * Tests retry functions, when retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
+        final int maxRetries = 3;
+        params = params.toBuilder().retry(maxRetries).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
+                        PolicyResult.FAILURE_RETRIES);
+    }
+
+    /**
+     * Tests retry functions, when a success follows some retries.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
+        params = params.toBuilder().retry(10).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        final int maxFailures = 3;
+        oper.setMaxFailures(maxFailures);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
+                        PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests retry functions, when the outcome is {@code null}.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
+
+        // arrange to return null from doOperation()
+        oper = new MyOper() {
+            @Override
+            protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+                // update counters
+                super.doOperation(attempt, operation);
+                return null;
+            }
+        };
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
+    }
+
+    @Test
+    public void testSleep() throws Exception {
+        CompletableFuture<Void> future = oper.sleep(-1, TimeUnit.SECONDS);
+        assertTrue(future.isDone());
+        assertNull(future.get());
+
+        // edge case
+        future = oper.sleep(0, TimeUnit.SECONDS);
+        assertTrue(future.isDone());
+        assertNull(future.get());
+
+        /*
+         * Start a second sleep we can use to check the first while it's running.
+         */
+        tstart = Instant.now();
+        future = oper.sleep(100, TimeUnit.MILLISECONDS);
+
+        CompletableFuture<Void> future2 = oper.sleep(10, TimeUnit.MILLISECONDS);
+
+        // wait for second to complete and verify that the first has not completed
+        future2.get();
+        assertFalse(future.isDone());
+
+        // wait for second to complete
+        future.get();
+
+        long diff = Instant.now().toEpochMilli() - tstart.toEpochMilli();
+        assertTrue(diff >= 99);
+    }
+
+    @Test
+    public void testIsSameOperation() {
+        assertFalse(oper.isSameOperation(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        // wrong actor - should be false
+        outcome.setActor(null);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setActor(MY_SINK);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setActor(ACTOR);
+
+        // wrong operation - should be null
+        outcome.setOperation(null);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setOperation(MY_SINK);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setOperation(OPERATION);
+
+        assertTrue(oper.isSameOperation(outcome));
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is a success.
+     */
+    @Test
+    public void testHandlePreprocessorFailureTrue() {
+        oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+        verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is <i>not</i> a success.
+     */
+    @Test
+    public void testHandlePreprocessorFailureFalse() throws Exception {
+        oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+        verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is {@code null}.
+     */
+    @Test
+    public void testHandlePreprocessorFailureNull() throws Exception {
+        // arrange to return null from the preprocessor
+        oper.setGuard(CompletableFuture.completedFuture(null));
+
+        verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    @Test
+    public void testFromException() {
+        // arrange to generate an exception when operation runs
+        oper.setGenException(true);
+
+        verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
+    }
+
+    /**
+     * Tests fromException() when there is no exception.
+     */
+    @Test
+    public void testFromExceptionNoExcept() {
+        verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests both flavors of anyOf(), because one invokes the other.
+     */
+    @Test
+    public void testAnyOf() throws Exception {
+        // first task completes, others do not
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        final OperationOutcome outcome = params.makeOutcome();
+
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> null);
+        tasks.add(() -> new CompletableFuture<>());
+
+        CompletableFuture<OperationOutcome> result = oper.anyOf(tasks);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // repeat using array form
+        @SuppressWarnings("unchecked")
+        Supplier<CompletableFuture<OperationOutcome>>[] taskArray = new Supplier[tasks.size()];
+        result = oper.anyOf(tasks.toArray(taskArray));
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // second task completes, others do not
+        tasks.clear();
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> new CompletableFuture<>());
+
+        result = oper.anyOf(tasks);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // third task completes, others do not
+        tasks.clear();
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+        result = oper.anyOf(tasks);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+    }
+
+    /**
+     * Tests both flavors of anyOf(), for edge cases: zero items, and one item.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAnyOfEdge() throws Exception {
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        // zero items: check both using a list and using an array
+        assertNull(oper.anyOf(tasks));
+        assertNull(oper.anyOf());
+
+        // one item: : check both using a list and using an array
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        tasks.add(() -> future1);
+
+        assertSame(future1, oper.anyOf(tasks));
+        assertSame(future1, oper.anyOf(() -> future1));
+    }
+
+    @Test
+    public void testAllOfArray() throws Exception {
+        final OperationOutcome outcome = params.makeOutcome();
+
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+
+        @SuppressWarnings("unchecked")
+        CompletableFuture<OperationOutcome> result =
+                        oper.allOf(() -> future1, () -> future2, () -> null, () -> future3);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future1.complete(outcome);
+
+        // complete 3 before 2
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future3.complete(outcome);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future2.complete(outcome);
+
+        // all of them are now done
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+    }
+
+    @Test
+    public void testAllOfList() throws Exception {
+        final OperationOutcome outcome = params.makeOutcome();
+
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+        tasks.add(() -> future1);
+        tasks.add(() -> future2);
+        tasks.add(() -> null);
+        tasks.add(() -> future3);
+
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future1.complete(outcome);
+
+        // complete 3 before 2
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future3.complete(outcome);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertFalse(result.isDone());
+        future2.complete(outcome);
+
+        // all of them are now done
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+    }
+
+    /**
+     * Tests both flavors of allOf(), for edge cases: zero items, and one item.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAllOfEdge() throws Exception {
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        // zero items: check both using a list and using an array
+        assertNull(oper.allOf(tasks));
+        assertNull(oper.allOf());
+
+        // one item: : check both using a list and using an array
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        tasks.add(() -> future1);
+
+        assertSame(future1, oper.allOf(tasks));
+        assertSame(future1, oper.allOf(() -> future1));
+    }
+
+    @Test
+    public void testAttachFutures() throws Exception {
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        // third task throws an exception during construction
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+        tasks.add(() -> future1);
+        tasks.add(() -> future2);
+        tasks.add(() -> {
+            throw new IllegalStateException(EXPECTED_EXCEPTION);
+        });
+        tasks.add(() -> future3);
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.anyOf(tasks)).withMessage(EXPECTED_EXCEPTION);
+
+        // should have canceled the first two, but not the last
+        assertTrue(future1.isCancelled());
+        assertTrue(future2.isCancelled());
+        assertFalse(future3.isCancelled());
+    }
+
+    @Test
+    public void testCombineOutcomes() throws Exception {
+        // only one outcome
+        verifyOutcomes(0, PolicyResult.SUCCESS);
+        verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
+
+        // maximum is in different positions
+        verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
+        verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
+        verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
+
+        // null outcome - takes precedence over a success
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+        tasks.add(() -> CompletableFuture.completedFuture(null));
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertNull(result.get());
+
+        // one throws an exception during execution
+        IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
+
+        tasks.clear();
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+        tasks.add(() -> CompletableFuture.failedFuture(except));
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+        result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isCompletedExceptionally());
+        result.whenComplete((unused, thrown) -> assertSame(except, thrown));
+    }
+
+    /**
+     * Tests both flavors of sequence(), because one invokes the other.
+     */
+    @Test
+    public void testSequence() throws Exception {
+        final OperationOutcome outcome = params.makeOutcome();
+
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> null);
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+        CompletableFuture<OperationOutcome> result = oper.sequence(tasks);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // repeat using array form
+        @SuppressWarnings("unchecked")
+        Supplier<CompletableFuture<OperationOutcome>>[] taskArray = new Supplier[tasks.size()];
+        result = oper.sequence(tasks.toArray(taskArray));
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // second task fails, third should not run
+        OperationOutcome failure = params.makeOutcome();
+        failure.setResult(PolicyResult.FAILURE);
+        tasks.clear();
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> CompletableFuture.completedFuture(failure));
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+        result = oper.sequence(tasks);
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(failure, result.get());
+    }
+
+    /**
+     * Tests both flavors of sequence(), for edge cases: zero items, and one item.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSequenceEdge() throws Exception {
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        // zero items: check both using a list and using an array
+        assertNull(oper.sequence(tasks));
+        assertNull(oper.sequence());
+
+        // one item: : check both using a list and using an array
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        tasks.add(() -> future1);
+
+        assertSame(future1, oper.sequence(tasks));
+        assertSame(future1, oper.sequence(() -> future1));
+    }
+
+    private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+        OperationOutcome expectedOutcome = null;
+
+        for (int count = 0; count < results.length; ++count) {
+            OperationOutcome outcome = params.makeOutcome();
+            outcome.setResult(results[count]);
+            tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+            if (count == expected) {
+                expectedOutcome = outcome;
+            }
+        }
+
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll(MAX_REQUESTS));
+        assertTrue(result.isDone());
+        assertSame(expectedOutcome, result.get());
+    }
+
+    @Test
+    public void testDetmPriority() throws CoderException {
+        assertEquals(1, oper.detmPriority(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
+                        PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
+                        PolicyResult.FAILURE_EXCEPTION, 6);
+
+        for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
+            outcome.setResult(ent.getKey());
+            assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
+        }
+
+        /*
+         * Test null result. We can't actually set it to null, because the set() method
+         * won't allow it. Instead, we decode it from a structure.
+         */
+        outcome = new StandardCoder().decode("{\"result\":null}", OperationOutcome.class);
+        assertEquals(1, oper.detmPriority(outcome));
+    }
+
+    /**
+     * Tests callbackStarted() when the pipeline has already been stopped.
+     */
+    @Test
+    public void testCallbackStartedNotRunning() {
+        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+        /*
+         * arrange to stop the controller when the start-callback is invoked, but capture
+         * the outcome
+         */
+        params = params.toBuilder().startCallback(oper -> {
+            starter(oper);
+            future.get().cancel(false);
+        }).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        future.set(oper.start());
+        assertTrue(executor.runAll(MAX_REQUESTS));
+
+        // should have only run once
+        assertEquals(1, numStart);
+    }
+
+    /**
+     * Tests callbackCompleted() when the pipeline has already been stopped.
+     */
+    @Test
+    public void testCallbackCompletedNotRunning() {
+        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+        // arrange to stop the controller when the start-callback is invoked
+        params = params.toBuilder().startCallback(oper -> {
+            future.get().cancel(false);
+        }).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        future.set(oper.start());
+        assertTrue(executor.runAll(MAX_REQUESTS));
+
+        // should not have been set
+        assertNull(opend);
+        assertEquals(0, numEnd);
+    }
+
+    @Test
+    public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
+        final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
+
+        OperationOutcome outcome;
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, timex);
+        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, new IllegalStateException(EXPECTED_EXCEPTION));
+        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+    }
+
+    @Test
+    public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
+        OperationOutcome outcome;
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, PolicyResult.SUCCESS);
+        assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+        for (PolicyResult result : FAILURE_RESULTS) {
+            outcome = new OperationOutcome();
+            oper.setOutcome(outcome, result);
+            assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+            assertEquals(result.toString(), result, outcome.getResult());
+        }
+    }
+
+    @Test
+    public void testIsTimeout() {
+        final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+
+        assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
+        assertFalse(oper.isTimeout(new IllegalStateException(timex)));
+        assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
+        assertFalse(oper.isTimeout(new CompletionException(null)));
+        assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+
+        assertTrue(oper.isTimeout(timex));
+        assertTrue(oper.isTimeout(new CompletionException(timex)));
+    }
+
+    @Test
+    public void testLogMessage() {
+        final String infraStr = SINK_INFRA.toString();
+
+        // log structured data
+        appender.clearExtractions();
+        oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, new MyData());
+        List<String> output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains("OUT")
+                        .contains("{\n  \"text\": \"my-text\"\n}");
+
+        // repeat with a response
+        appender.clearExtractions();
+        oper.logMessage(EventType.IN, SOURCE_INFRA, MY_SOURCE, new MyData());
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(SOURCE_INFRA.toString()).contains(MY_SOURCE).contains("IN")
+                        .contains("{\n  \"text\": \"my-text\"\n}");
+
+        // log a plain string
+        appender.clearExtractions();
+        oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, TEXT);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+        assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains(TEXT);
+
+        // log a null request
+        appender.clearExtractions();
+        oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, null);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains("null");
+
+        // generate exception from coder
+        setOperCoderException();
+
+        appender.clearExtractions();
+        oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, new MyData());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print request");
+        assertThat(output.get(1)).contains(infraStr).contains(MY_SINK);
+
+        // repeat with a response
+        appender.clearExtractions();
+        oper.logMessage(EventType.IN, SOURCE_INFRA, MY_SOURCE, new MyData());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print response");
+        assertThat(output.get(1)).contains(MY_SOURCE);
+    }
+
+    @Test
+    public void testGetRetry() {
+        assertEquals(0, oper.getRetry(null));
+        assertEquals(10, oper.getRetry(10));
+    }
+
+    @Test
+    public void testGetRetryWait() {
+        // need an operator that doesn't override the retry time
+        OperationPartial oper2 = new OperationPartial(params, operator) {};
+        assertEquals(OperationPartial.DEFAULT_RETRY_WAIT_MS, oper2.getRetryWaitMs());
+    }
+
+    @Test
+    public void testGetTimeOutMs() {
+        assertEquals(TIMEOUT * 1000, oper.getTimeoutMs(params.getTimeoutSec()));
+
+        params = params.toBuilder().timeoutSec(null).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        assertEquals(0, oper.getTimeoutMs(params.getTimeoutSec()));
+    }
+
+    private void starter(OperationOutcome oper) {
+        ++numStart;
+        tstart = oper.getStart();
+        opstart = oper;
+    }
+
+    private void completer(OperationOutcome oper) {
+        ++numEnd;
+        opend = oper;
+    }
+
+    /**
+     * Gets a function that does nothing.
+     *
+     * @param <T> type of input parameter expected by the function
+     * @return a function that does nothing
+     */
+    private <T> Consumer<T> noop() {
+        return unused -> {
+        };
+    }
+
+    private OperationOutcome makeSuccess() {
+        OperationOutcome outcome = params.makeOutcome();
+        outcome.setResult(PolicyResult.SUCCESS);
+
+        return outcome;
+    }
+
+    private OperationOutcome makeFailure() {
+        OperationOutcome outcome = params.makeOutcome();
+        outcome.setResult(PolicyResult.FAILURE);
+
+        return outcome;
+    }
+
+    /**
+     * Verifies a run.
+     *
+     * @param testName test name
+     * @param expectedCallbacks number of callbacks expected
+     * @param expectedOperations number of operation invocations expected
+     * @param expectedResult expected outcome
+     */
+    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
+                    PolicyResult expectedResult) {
+
+        String expectedSubRequestId =
+                        (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
+
+        verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
+    }
+
+    /**
+     * Verifies a run.
+     *
+     * @param testName test name
+     * @param expectedCallbacks number of callbacks expected
+     * @param expectedOperations number of operation invocations expected
+     * @param expectedResult expected outcome
+     * @param expectedSubRequestId expected sub request ID
+     * @param manipulator function to modify the future returned by
+     *        {@link OperationPartial#start(ControlLoopOperationParams)} before the tasks
+     *        in the executor are run
+     */
+    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
+                    String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
+
+        CompletableFuture<OperationOutcome> future = oper.start();
+
+        manipulator.accept(future);
+
+        assertTrue(testName, executor.runAll(MAX_REQUESTS));
+
+        assertEquals(testName, expectedCallbacks, numStart);
+        assertEquals(testName, expectedCallbacks, numEnd);
+
+        if (expectedCallbacks > 0) {
+            assertNotNull(testName, opstart);
+            assertNotNull(testName, opend);
+            assertEquals(testName, expectedResult, opend.getResult());
+
+            assertSame(testName, tstart, opstart.getStart());
+            assertSame(testName, tstart, opend.getStart());
+
+            try {
+                assertTrue(future.isDone());
+                assertSame(testName, opend, future.get());
+
+            } catch (InterruptedException | ExecutionException e) {
+                throw new IllegalStateException(e);
+            }
+
+            if (expectedOperations > 0) {
+                assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
+            }
+        }
+
+        assertEquals(testName, expectedOperations, oper.getCount());
+    }
+
+    /**
+     * Creates a new {@link #oper} whose coder will throw an exception.
+     */
+    private void setOperCoderException() {
+        oper = new MyOper() {
+            @Override
+            protected Coder makeCoder() {
+                return new StandardCoder() {
+                    @Override
+                    public String encode(Object object, boolean pretty) throws CoderException {
+                        throw new CoderException(EXPECTED_EXCEPTION);
+                    }
+                };
+            }
+        };
+    }
+
+
+    @Getter
+    public static class MyData {
+        private String text = TEXT;
+    }
+
+
+    private class MyOper extends OperationPartial {
+        @Getter
+        private int count = 0;
+
+        @Setter
+        private boolean genException;
+
+        @Setter
+        private int maxFailures = 0;
+
+        @Setter
+        private CompletableFuture<OperationOutcome> guard;
+
+
+        public MyOper() {
+            super(OperationPartialTest.this.params, operator);
+        }
+
+        @Override
+        protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+            ++count;
+            if (genException) {
+                throw new IllegalStateException(EXPECTED_EXCEPTION);
+            }
+
+            operation.setSubRequestId(String.valueOf(attempt));
+
+            if (count > maxFailures) {
+                operation.setResult(PolicyResult.SUCCESS);
+            } else {
+                operation.setResult(PolicyResult.FAILURE);
+            }
+
+            return operation;
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startGuardAsync() {
+            return (guard != null ? guard : super.startGuardAsync());
+        }
+
+        @Override
+        protected long getRetryWaitMs() {
+            /*
+             * Sleep timers run in the background, but we want to control things via the
+             * "executor", thus we avoid sleep timers altogether by simply returning 0.
+             */
+            return 0L;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
index 21bc656..370426f 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
@@ -20,1271 +20,88 @@
 
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Queue;
 import java.util.TreeMap;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import lombok.Getter;
-import lombok.Setter;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.controlloop.ControlLoopOperation;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
 
 public class OperatorPartialTest {
-    private static final int MAX_PARALLEL_REQUESTS = 10;
-    private static final String EXPECTED_EXCEPTION = "expected exception";
     private static final String ACTOR = "my-actor";
-    private static final String OPERATOR = "my-operator";
-    private static final String TARGET = "my-target";
-    private static final int TIMEOUT = 1000;
-    private static final UUID REQ_ID = UUID.randomUUID();
+    private static final String OPERATION = "my-name";
 
-    private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
-                    .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
-
-    private VirtualControlLoopEvent event;
-    private Map<String, Object> config;
-    private ControlLoopEventContext context;
-    private MyExec executor;
-    private ControlLoopOperationParams params;
-
-    private MyOper oper;
-
-    private int numStart;
-    private int numEnd;
-
-    private Instant tstart;
-
-    private OperationOutcome opstart;
-    private OperationOutcome opend;
+    private OperatorPartial operator;
 
     /**
-     * Initializes the fields, including {@link #oper}.
+     * Initializes {@link #operator}.
      */
     @Before
     public void setUp() {
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-
-        config = new TreeMap<>();
-        context = new ControlLoopEventContext(event);
-        executor = new MyExec();
-
-        params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
-                        .executor(executor).actor(ACTOR).operation(OPERATOR).timeoutSec(TIMEOUT)
-                        .startCallback(this::starter).targetEntity(TARGET).build();
-
-        oper = new MyOper();
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        tstart = null;
-
-        opstart = null;
-        opend = null;
+        operator = new OperatorPartial(ACTOR, OPERATION) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+        };
     }
 
     @Test
     public void testOperatorPartial_testGetActorName_testGetName() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(OPERATOR, oper.getName());
-        assertEquals(ACTOR + "." + OPERATOR, oper.getFullName());
-    }
-
-    @Test
-    public void testGetBlockingExecutor() throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        /*
-         * Use an operator that doesn't override getBlockingExecutor().
-         */
-        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {};
-        oper2.getBlockingExecutor().execute(() -> latch.countDown());
-
-        assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testDoConfigure() {
-        oper = spy(new MyOper());
-
-        oper.configure(config);
-        verify(oper).configure(config);
-
-        // repeat - SHOULD be run again
-        oper.configure(config);
-        verify(oper, times(2)).configure(config);
+        assertEquals(ACTOR, operator.getActorName());
+        assertEquals(OPERATION, operator.getName());
+        assertEquals(ACTOR + "." + OPERATION, operator.getFullName());
     }
 
     @Test
     public void testDoStart() {
-        oper = spy(new MyOper());
+        operator.configure(null);
 
-        oper.configure(config);
-        oper.start();
+        operator = spy(operator);
+        operator.start();
 
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doStop();
-        verify(oper, never()).doShutdown();
+        verify(operator).doStart();
     }
 
     @Test
     public void testDoStop() {
-        oper = spy(new MyOper());
+        operator.configure(null);
+        operator.start();
 
-        oper.configure(config);
-        oper.start();
-        oper.stop();
+        operator = spy(operator);
+        operator.stop();
 
-        verify(oper).doStop();
-
-        // should not have been re-invoked
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doShutdown();
+        verify(operator).doStop();
     }
 
     @Test
     public void testDoShutdown() {
-        oper = spy(new MyOper());
+        operator.configure(null);
+        operator.start();
 
-        oper.configure(config);
-        oper.start();
-        oper.shutdown();
+        operator = spy(operator);
+        operator.shutdown();
 
-        verify(oper).doShutdown();
-
-        // should not have been re-invoked
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doStop();
+        verify(operator).doShutdown();
     }
 
     @Test
-    public void testStartOperation() {
-        verifyRun("testStartOperation", 1, 1, PolicyResult.SUCCESS);
-    }
+    public void testDoConfigureMapOfStringObject() {
+        operator = spy(operator);
 
-    /**
-     * Tests startOperation() when the operator is not running.
-     */
-    @Test
-    public void testStartOperationNotRunning() {
-        // use a new operator, one that hasn't been started yet
-        oper = new MyOper();
-        oper.configure(new TreeMap<>());
+        Map<String, Object> params = new TreeMap<>();
+        operator.configure(params);
 
-        assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
-    }
-
-    /**
-     * Tests startOperation() when the operation has a preprocessor.
-     */
-    @Test
-    public void testStartOperationWithPreprocessor() {
-        AtomicInteger count = new AtomicInteger();
-
-        CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
-            count.incrementAndGet();
-            return makeSuccess();
-        }, executor);
-
-        oper.setPreProcessor(preproc);
-
-        verifyRun("testStartOperationWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
-
-        assertEquals(1, count.get());
-    }
-
-    /**
-     * Tests startOperation() with multiple running requests.
-     */
-    @Test
-    public void testStartOperationMultiple() {
-        for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
-            oper.startOperation(params);
-        }
-
-        assertTrue(executor.runAll());
-
-        assertNotNull(opstart);
-        assertNotNull(opend);
-        assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
-        assertEquals(MAX_PARALLEL_REQUESTS, numStart);
-        assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount());
-        assertEquals(MAX_PARALLEL_REQUESTS, numEnd);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor returns a failure.
-     */
-    @Test
-    public void testStartPreprocessorFailure() {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
-
-        verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor throws an exception.
-     */
-    @Test
-    public void testStartPreprocessorException() {
-        // arrange for the preprocessor to throw an exception
-        oper.setPreProcessor(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
-
-        verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests startPreprocessor() when the pipeline is not running.
-     */
-    @Test
-    public void testStartPreprocessorNotRunning() {
-        // arrange for the preprocessor to return success, which will be ignored
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
-
-        oper.startOperation(params).cancel(false);
-        assertTrue(executor.runAll());
-
-        assertNull(opstart);
-        assertNull(opend);
-
-        assertEquals(0, numStart);
-        assertEquals(0, oper.getCount());
-        assertEquals(0, numEnd);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
-     */
-    @Test
-    public void testStartPreprocessorBuilderException() {
-        oper = new MyOper() {
-            @Override
-            protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-                throw new IllegalStateException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
-
-        // should be nothing in the queue
-        assertEquals(0, executor.getQueueLength());
+        verify(operator).doConfigure(params);
     }
 
     @Test
-    public void testStartPreprocessorAsync() {
-        assertNull(oper.startPreprocessorAsync(params));
-    }
-
-    @Test
-    public void testStartOperationAsync() {
-        oper.startOperation(params);
-        assertTrue(executor.runAll());
-
-        assertEquals(1, oper.getCount());
-    }
-
-    @Test
-    public void testIsSuccess() {
-        OperationOutcome outcome = new OperationOutcome();
-
-        outcome.setResult(PolicyResult.SUCCESS);
-        assertTrue(oper.isSuccess(outcome));
-
-        for (PolicyResult failure : FAILURE_RESULTS) {
-            outcome.setResult(failure);
-            assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
-        }
-    }
-
-    @Test
-    public void testIsActorFailed() {
-        assertFalse(oper.isActorFailed(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        // incorrect outcome
-        outcome.setResult(PolicyResult.SUCCESS);
-        assertFalse(oper.isActorFailed(outcome));
-
-        outcome.setResult(PolicyResult.FAILURE_RETRIES);
-        assertFalse(oper.isActorFailed(outcome));
-
-        // correct outcome
-        outcome.setResult(PolicyResult.FAILURE);
-
-        // incorrect actor
-        outcome.setActor(TARGET);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setActor(null);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setActor(ACTOR);
-
-        // incorrect operation
-        outcome.setOperation(TARGET);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setOperation(null);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setOperation(OPERATOR);
-
-        // correct values
-        assertTrue(oper.isActorFailed(outcome));
-    }
-
-    @Test
-    public void testDoOperation() {
-        /*
-         * Use an operator that doesn't override doOperation().
-         */
-        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {
-            @Override
-            protected Executor getBlockingExecutor() {
-                return executor;
-            }
-        };
-
-        oper2.configure(new TreeMap<>());
-        oper2.start();
-
-        oper2.startOperation(params);
-        assertTrue(executor.runAll());
-
-        assertNotNull(opend);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
-    }
-
-    @Test
-    public void testTimeout() throws Exception {
-
-        // use a real executor
-        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
-        // trigger timeout very quickly
-        oper = new MyOper() {
-            @Override
-            protected long getTimeOutMillis(Integer timeoutSec) {
-                return 1;
-            }
-
-            @Override
-            protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params,
-                            int attempt, OperationOutcome outcome) {
-
-                OperationOutcome outcome2 = params.makeOutcome();
-                outcome2.setResult(PolicyResult.SUCCESS);
-
-                /*
-                 * Create an incomplete future that will timeout after the operation's
-                 * timeout. If it fires before the other timer, then it will return a
-                 * SUCCESS outcome.
-                 */
-                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
-                future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
-                                params.getExecutor());
-
-                return future;
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.startOperation(params).get().getResult());
-    }
-
-    /**
-     * Verifies that the timer doesn't encompass the preprocessor and doesn't stop the
-     * operation once the preprocessor completes.
-     */
-    @Test
-    public void testTimeoutInPreprocessor() throws Exception {
-
-        // use a real executor
-        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
-        // trigger timeout very quickly
-        oper = new MyOper() {
-            @Override
-            protected long getTimeOutMillis(Integer timeoutSec) {
-                return 10;
-            }
-
-            @Override
-            protected Executor getBlockingExecutor() {
-                return command -> {
-                    Thread thread = new Thread(command);
-                    thread.start();
-                };
-            }
-
-            @Override
-            protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-
-                OperationOutcome outcome = makeSuccess();
-
-                /*
-                 * Create an incomplete future that will timeout after the operation's
-                 * timeout. If it fires before the other timer, then it will return a
-                 * SUCCESS outcome.
-                 */
-                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
-                future = future.orTimeout(200, TimeUnit.MILLISECONDS).handleAsync((unused1, unused2) -> outcome,
-                                params.getExecutor());
-
-                return future;
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        OperationOutcome result = oper.startOperation(params).get();
-        assertEquals(PolicyResult.SUCCESS, result.getResult());
-
-        assertNotNull(opstart);
-        assertNotNull(opend);
-        assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
-        assertEquals(1, numStart);
-        assertEquals(1, oper.getCount());
-        assertEquals(1, numEnd);
-    }
-
-    /**
-     * Tests retry functions, when the count is set to zero and retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
-        params = params.toBuilder().retry(0).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
-    }
-
-    /**
-     * Tests retry functions, when the count is null and retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
-        params = params.toBuilder().retry(null).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
-    }
-
-    /**
-     * Tests retry functions, when retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
-        final int maxRetries = 3;
-        params = params.toBuilder().retry(maxRetries).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
-                        PolicyResult.FAILURE_RETRIES);
-    }
-
-    /**
-     * Tests retry functions, when a success follows some retries.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
-        params = params.toBuilder().retry(10).build();
-
-        final int maxFailures = 3;
-        oper.setMaxFailures(maxFailures);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
-                        PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests retry functions, when the outcome is {@code null}.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
-
-        // arrange to return null from doOperation()
-        oper = new MyOper() {
-            @Override
-            protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
-                            OperationOutcome operation) {
-
-                // update counters
-                super.doOperation(params, attempt, operation);
-                return null;
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
-    }
-
-    @Test
-    public void testIsSameOperation() {
-        assertFalse(oper.isSameOperation(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        // wrong actor - should be false
-        outcome.setActor(null);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setActor(TARGET);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setActor(ACTOR);
-
-        // wrong operation - should be null
-        outcome.setOperation(null);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setOperation(TARGET);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setOperation(OPERATOR);
-
-        assertTrue(oper.isSameOperation(outcome));
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is a success.
-     */
-    @Test
-    public void testHandlePreprocessorFailureTrue() {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
-        verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is <i>not</i> a success.
-     */
-    @Test
-    public void testHandlePreprocessorFailureFalse() throws Exception {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
-        verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is {@code null}.
-     */
-    @Test
-    public void testHandlePreprocessorFailureNull() throws Exception {
-        // arrange to return null from the preprocessor
-        oper.setPreProcessor(CompletableFuture.completedFuture(null));
-
-        verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    @Test
-    public void testFromException() {
-        // arrange to generate an exception when operation runs
-        oper.setGenException(true);
-
-        verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
-    }
-
-    /**
-     * Tests fromException() when there is no exception.
-     */
-    @Test
-    public void testFromExceptionNoExcept() {
-        verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests both flavors of anyOf(), because one invokes the other.
-     */
-    @Test
-    public void testAnyOf() throws Exception {
-        // first task completes, others do not
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-        final OperationOutcome outcome = params.makeOutcome();
-
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
-        tasks.add(new CompletableFuture<>());
-
-        CompletableFuture<OperationOutcome> result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-
-        // second task completes, others do not
-        tasks = new LinkedList<>();
-
-        tasks.add(new CompletableFuture<>());
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
-
-        result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-
-        // third task completes, others do not
-        tasks = new LinkedList<>();
-
-        tasks.add(new CompletableFuture<>());
-        tasks.add(new CompletableFuture<>());
-        tasks.add(CompletableFuture.completedFuture(outcome));
-
-        result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-    }
-
-    /**
-     * Tests both flavors of allOf(), because one invokes the other.
-     */
-    @Test
-    public void testAllOf() throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-        final OperationOutcome outcome = params.makeOutcome();
-
-        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
-        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
-        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
-
-        tasks.add(future1);
-        tasks.add(future2);
-        tasks.add(future3);
-
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future1.complete(outcome);
-
-        // complete 3 before 2
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future3.complete(outcome);
-
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future2.complete(outcome);
-
-        // all of them are now done
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-    }
-
-    @Test
-    public void testCombineOutcomes() throws Exception {
-        // only one outcome
-        verifyOutcomes(0, PolicyResult.SUCCESS);
-        verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
-
-        // maximum is in different positions
-        verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
-        verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
-        verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
-
-        // null outcome
-        final List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-        tasks.add(CompletableFuture.completedFuture(null));
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertNull(result.get());
-
-        // one throws an exception during execution
-        IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
-
-        tasks.clear();
-        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
-        tasks.add(CompletableFuture.failedFuture(except));
-        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
-        result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isCompletedExceptionally());
-        result.whenComplete((unused, thrown) -> assertSame(except, thrown));
-    }
-
-    private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-
-        OperationOutcome expectedOutcome = null;
-
-        for (int count = 0; count < results.length; ++count) {
-            OperationOutcome outcome = params.makeOutcome();
-            outcome.setResult(results[count]);
-            tasks.add(CompletableFuture.completedFuture(outcome));
-
-            if (count == expected) {
-                expectedOutcome = outcome;
-            }
-        }
-
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertSame(expectedOutcome, result.get());
-    }
-
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask(
-                    final OperationOutcome taskOutcome) {
-
-        return outcome -> CompletableFuture.completedFuture(taskOutcome);
-    }
-
-    @Test
-    public void testDetmPriority() {
-        assertEquals(1, oper.detmPriority(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
-                        PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
-                        PolicyResult.FAILURE_EXCEPTION, 6);
-
-        for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
-            outcome.setResult(ent.getKey());
-            assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
-        }
-    }
-
-    /**
-     * Tests doTask(Future) when the controller is not running.
-     */
-    @Test
-    public void testDoTaskFutureNotRunning() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-        controller.complete(params.makeOutcome());
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, false, params.makeOutcome(), taskFuture);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should have canceled the task future
-        assertTrue(taskFuture.isCancelled());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was successful.
-     */
-    @Test
-    public void testDoTaskFutureSuccess() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, true, params.makeOutcome(), taskFuture);
-
-        taskFuture.complete(taskOutcome);
-        assertTrue(executor.runAll());
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was failed.
-     */
-    @Test
-    public void testDoTaskFutureFailure() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, failedOutcome, taskFuture);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should have canceled the task future
-        assertTrue(taskFuture.isCancelled());
-
-        // controller SHOULD be done now
-        assertTrue(controller.isDone());
-        assertSame(failedOutcome, controller.get());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was failed, but not checking
-     * success.
-     */
-    @Test
-    public void testDoTaskFutureUncheckedFailure() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, failedOutcome, taskFuture);
-        assertFalse(future.isDone());
-
-        // complete the task
-        OperationOutcome taskOutcome = params.makeOutcome();
-        taskFuture.complete(taskOutcome);
-
-        assertTrue(executor.runAll());
-
-        // should have run the task
-        assertTrue(future.isDone());
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Function) when the controller is not running.
-     */
-    @Test
-    public void testDoTaskFunctionNotRunning() throws Exception {
-        AtomicBoolean invoked = new AtomicBoolean();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
-            invoked.set(true);
-            return CompletableFuture.completedFuture(params.makeOutcome());
-        };
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-        controller.complete(params.makeOutcome());
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, false, task).apply(params.makeOutcome());
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should not have even invoked the task
-        assertFalse(invoked.get());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was successful.
-     */
-    @Test
-    public void testDoTaskFunctionSuccess() throws Exception {
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        final OperationOutcome failedOutcome = params.makeOutcome();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was failed.
-     */
-    @Test
-    public void testDoTaskFunctionFailure() throws Exception {
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        AtomicBoolean invoked = new AtomicBoolean();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
-            invoked.set(true);
-            return CompletableFuture.completedFuture(params.makeOutcome());
-        };
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should not have even invoked the task
-        assertFalse(invoked.get());
-
-        // controller should have the failed task
-        assertTrue(controller.isDone());
-        assertSame(failedOutcome, controller.get());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was failed, but not checking
-     * success.
-     */
-    @Test
-    public void testDoTaskFunctionUncheckedFailure() throws Exception {
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, task).apply(failedOutcome);
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests callbackStarted() when the pipeline has already been stopped.
-     */
-    @Test
-    public void testCallbackStartedNotRunning() {
-        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
-        /*
-         * arrange to stop the controller when the start-callback is invoked, but capture
-         * the outcome
-         */
-        params = params.toBuilder().startCallback(oper -> {
-            starter(oper);
-            future.get().cancel(false);
-        }).build();
-
-        future.set(oper.startOperation(params));
-        assertTrue(executor.runAll());
-
-        // should have only run once
-        assertEquals(1, numStart);
-    }
-
-    /**
-     * Tests callbackCompleted() when the pipeline has already been stopped.
-     */
-    @Test
-    public void testCallbackCompletedNotRunning() {
-        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
-        // arrange to stop the controller when the start-callback is invoked
-        params = params.toBuilder().startCallback(oper -> {
-            future.get().cancel(false);
-        }).build();
-
-        future.set(oper.startOperation(params));
-        assertTrue(executor.runAll());
-
-        // should not have been set
-        assertNull(opend);
-        assertEquals(0, numEnd);
-    }
-
-    @Test
-    public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
-        final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
-
-        OperationOutcome outcome;
-
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, timex);
-        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
-
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, new IllegalStateException(EXPECTED_EXCEPTION));
-        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
-    }
-
-    @Test
-    public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
-        OperationOutcome outcome;
-
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, PolicyResult.SUCCESS);
-        assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
-
-        for (PolicyResult result : FAILURE_RESULTS) {
-            outcome = new OperationOutcome();
-            oper.setOutcome(params, outcome, result);
-            assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-            assertEquals(result.toString(), result, outcome.getResult());
-        }
-    }
-
-    @Test
-    public void testIsTimeout() {
-        final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
-
-        assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
-        assertFalse(oper.isTimeout(new IllegalStateException(timex)));
-        assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
-        assertFalse(oper.isTimeout(new CompletionException(null)));
-        assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
-
-        assertTrue(oper.isTimeout(timex));
-        assertTrue(oper.isTimeout(new CompletionException(timex)));
-    }
-
-    @Test
-    public void testGetTimeOutMillis() {
-        assertEquals(TIMEOUT * 1000, oper.getTimeOutMillis(params.getTimeoutSec()));
-
-        params = params.toBuilder().timeoutSec(null).build();
-        assertEquals(0, oper.getTimeOutMillis(params.getTimeoutSec()));
-    }
-
-    private void starter(OperationOutcome oper) {
-        ++numStart;
-        tstart = oper.getStart();
-        opstart = oper;
-    }
-
-    private void completer(OperationOutcome oper) {
-        ++numEnd;
-        opend = oper;
-    }
-
-    /**
-     * Gets a function that does nothing.
-     *
-     * @param <T> type of input parameter expected by the function
-     * @return a function that does nothing
-     */
-    private <T> Consumer<T> noop() {
-        return unused -> {
-        };
-    }
-
-    private OperationOutcome makeSuccess() {
-        OperationOutcome outcome = params.makeOutcome();
-        outcome.setResult(PolicyResult.SUCCESS);
-
-        return outcome;
-    }
-
-    private OperationOutcome makeFailure() {
-        OperationOutcome outcome = params.makeOutcome();
-        outcome.setResult(PolicyResult.FAILURE);
-
-        return outcome;
-    }
-
-    /**
-     * Verifies a run.
-     *
-     * @param testName test name
-     * @param expectedCallbacks number of callbacks expected
-     * @param expectedOperations number of operation invocations expected
-     * @param expectedResult expected outcome
-     */
-    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
-                    PolicyResult expectedResult) {
-
-        String expectedSubRequestId =
-                        (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
-
-        verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
-    }
-
-    /**
-     * Verifies a run.
-     *
-     * @param testName test name
-     * @param expectedCallbacks number of callbacks expected
-     * @param expectedOperations number of operation invocations expected
-     * @param expectedResult expected outcome
-     * @param expectedSubRequestId expected sub request ID
-     * @param manipulator function to modify the future returned by
-     *        {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before
-     *        the tasks in the executor are run
-     */
-    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
-                    String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
-
-        CompletableFuture<OperationOutcome> future = oper.startOperation(params);
-
-        manipulator.accept(future);
-
-        assertTrue(testName, executor.runAll());
-
-        assertEquals(testName, expectedCallbacks, numStart);
-        assertEquals(testName, expectedCallbacks, numEnd);
-
-        if (expectedCallbacks > 0) {
-            assertNotNull(testName, opstart);
-            assertNotNull(testName, opend);
-            assertEquals(testName, expectedResult, opend.getResult());
-
-            assertSame(testName, tstart, opstart.getStart());
-            assertSame(testName, tstart, opend.getStart());
-
-            try {
-                assertTrue(future.isDone());
-                assertSame(testName, opend, future.get());
-
-            } catch (InterruptedException | ExecutionException e) {
-                throw new IllegalStateException(e);
-            }
-
-            if (expectedOperations > 0) {
-                assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
-            }
-        }
-
-        assertEquals(testName, expectedOperations, oper.getCount());
-    }
-
-    private class MyOper extends OperatorPartial {
-        @Getter
-        private int count = 0;
-
-        @Setter
-        private boolean genException;
-
-        @Setter
-        private int maxFailures = 0;
-
-        @Setter
-        private CompletableFuture<OperationOutcome> preProcessor;
-
-        public MyOper() {
-            super(ACTOR, OPERATOR);
-        }
-
-        @Override
-        protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
-                        OperationOutcome operation) {
-            ++count;
-            if (genException) {
-                throw new IllegalStateException(EXPECTED_EXCEPTION);
-            }
-
-            operation.setSubRequestId(String.valueOf(attempt));
-
-            if (count > maxFailures) {
-                operation.setResult(PolicyResult.SUCCESS);
-            } else {
-                operation.setResult(PolicyResult.FAILURE);
-            }
-
-            return operation;
-        }
-
-        @Override
-        protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-            return (preProcessor != null ? preProcessor : super.startPreprocessorAsync(params));
-        }
-
-        @Override
-        protected Executor getBlockingExecutor() {
-            return executor;
-        }
-    }
-
-    /**
-     * Executor that will run tasks until the queue is empty or a maximum number of tasks
-     * have been executed.
-     */
-    private static class MyExec implements Executor {
-        private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100;
-
-        private Queue<Runnable> commands = new LinkedList<>();
-
-        public MyExec() {
-            // do nothing
-        }
-
-        public int getQueueLength() {
-            return commands.size();
-        }
-
-        @Override
-        public void execute(Runnable command) {
-            commands.add(command);
-        }
-
-        public boolean runAll() {
-            for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) {
-                commands.remove().run();
-            }
-
-            return commands.isEmpty();
-        }
+    public void testGetBlockingExecutor() {
+        assertNotNull(operator.getBlockingExecutor());
     }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java
new file mode 100644
index 0000000..1f38ad3
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java
@@ -0,0 +1,118 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class BidirectionalTopicActorParamsTest {
+    private static final String CONTAINER = "my-container";
+
+    private static final String DFLT_SOURCE = "default-source";
+    private static final String DFLT_SINK = "default-target";
+    private static final int DFLT_TIMEOUT = 10;
+
+    private static final String OPER1_NAME = "oper A";
+    private static final String OPER1_SOURCE = "source A";
+    private static final String OPER1_SINK = "target A";
+    private static final int OPER1_TIMEOUT = 20;
+
+    // oper2 uses some default values
+    private static final String OPER2_NAME = "oper B";
+    private static final String OPER2_SOURCE = "source B";
+
+    // oper3 uses default values for everything
+    private static final String OPER3_NAME = "oper C";
+
+    private Map<String, Map<String, Object>> operMap;
+    private BidirectionalTopicActorParams params;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        BidirectionalTopicParams oper1 = BidirectionalTopicParams.builder().sourceTopic(OPER1_SOURCE)
+                        .sinkTopic(OPER1_SINK).timeoutSec(OPER1_TIMEOUT).build();
+
+        Map<String, Object> oper1Map = Util.translateToMap(OPER1_NAME, oper1);
+        Map<String, Object> oper2Map = Map.of("source", OPER2_SOURCE);
+        Map<String, Object> oper3Map = Collections.emptyMap();
+        operMap = Map.of(OPER1_NAME, oper1Map, OPER2_NAME, oper2Map, OPER3_NAME, oper3Map);
+
+        params = makeBidirectionalTopicActorParams();
+    }
+
+    @Test
+    public void testValidate() {
+        assertTrue(params.validate(CONTAINER).isValid());
+
+        // only a few fields are required
+        BidirectionalTopicActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operMap, "timeoutSec", 1),
+                        BidirectionalTopicActorParams.class);
+        assertTrue(sparse.validate(CONTAINER).isValid());
+
+        testValidateField("operation", "null", params2 -> params2.setOperation(null));
+        testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
+
+        // check edge cases
+        params.setTimeoutSec(0);
+        assertFalse(params.validate(CONTAINER).isValid());
+
+        params.setTimeoutSec(1);
+        assertTrue(params.validate(CONTAINER).isValid());
+    }
+
+    private void testValidateField(String fieldName, String expected,
+                    Consumer<BidirectionalTopicActorParams> makeInvalid) {
+
+        // original params should be valid
+        ValidationResult result = params.validate(CONTAINER);
+        assertTrue(fieldName, result.isValid());
+
+        // make invalid params
+        BidirectionalTopicActorParams params2 = makeBidirectionalTopicActorParams();
+        makeInvalid.accept(params2);
+        result = params2.validate(CONTAINER);
+        assertFalse(fieldName, result.isValid());
+        assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+    }
+
+    private BidirectionalTopicActorParams makeBidirectionalTopicActorParams() {
+        BidirectionalTopicActorParams params2 = new BidirectionalTopicActorParams();
+        params2.setSinkTopic(DFLT_SINK);
+        params2.setSourceTopic(DFLT_SOURCE);
+        params2.setTimeoutSec(DFLT_TIMEOUT);
+        params2.setOperation(operMap);
+
+        return params2;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java
similarity index 74%
rename from models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java
rename to models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java
index 4834c98..7e44fa2 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java
@@ -29,44 +29,46 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.parameters.ValidationResult;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicParams.TopicParamsBuilder;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams.BidirectionalTopicParamsBuilder;
 
-public class TopicParamsTest {
+public class BidirectionalTopicParamsTest {
 
     private static final String CONTAINER = "my-container";
-    private static final String TARGET = "my-target";
+    private static final String SINK = "my-sink";
     private static final String SOURCE = "my-source";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
-    private TopicParams params;
+    private BidirectionalTopicParams params;
 
     @Before
     public void setUp() {
-        params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
+        params = BidirectionalTopicParams.builder().sinkTopic(SINK).sourceTopic(SOURCE).timeoutSec(TIMEOUT).build();
     }
 
     @Test
     public void testValidate() {
-        testValidateField("target", "null", bldr -> bldr.target(null));
-        testValidateField("source", "null", bldr -> bldr.source(null));
+        assertTrue(params.validate(CONTAINER).isValid());
+
+        testValidateField("sink", "null", bldr -> bldr.sinkTopic(null));
+        testValidateField("source", "null", bldr -> bldr.sourceTopic(null));
         testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
 
         // check edge cases
-        assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+        assertFalse(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
         assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
     }
 
     @Test
     public void testBuilder_testToBuilder() {
-        assertEquals(TARGET, params.getTarget());
-        assertEquals(SOURCE, params.getSource());
+        assertEquals(SINK, params.getSinkTopic());
+        assertEquals(SOURCE, params.getSourceTopic());
         assertEquals(TIMEOUT, params.getTimeoutSec());
 
         assertEquals(params, params.toBuilder().build());
     }
 
     private void testValidateField(String fieldName, String expected,
-                    Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) {
+                    Function<BidirectionalTopicParamsBuilder, BidirectionalTopicParamsBuilder> makeInvalid) {
 
         // original params should be valid
         ValidationResult result = params.validate(CONTAINER);
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java
new file mode 100644
index 0000000..9014203
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java
@@ -0,0 +1,137 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class CommonActorParamsTest {
+
+    private static final String CONTAINER = "my-container";
+
+    private static final String PATH1 = "path #1";
+    private static final String PATH2 = "path #2";
+    private static final String URI1 = "uri #1";
+    private static final String URI2 = "uri #2";
+    private static final String TEXT1 = "hello";
+    private static final String TEXT2 = "world";
+    private static final String TEXT2B = "bye";
+
+    private Map<String, Map<String, Object>> operations;
+    private CommonActorParams params;
+
+    /**
+     * Initializes {@link #operations} with two items and {@link params} with a fully
+     * populated object.
+     */
+    @Before
+    public void setUp() {
+        operations = new TreeMap<>();
+        operations.put(PATH1, Map.of("path", URI1));
+        operations.put(PATH2, Map.of("path", URI2, "text2", TEXT2B));
+
+        params = makeCommonActorParams();
+    }
+
+    @Test
+    public void testMakeOperationParameters() {
+        Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER);
+        assertNull(maker.apply("unknown-operation"));
+
+        Map<String, Object> subparam = maker.apply(PATH1);
+        assertNotNull(subparam);
+        assertEquals("{path=uri #1, text1=hello, text2=world}", new TreeMap<>(subparam).toString());
+
+        subparam = maker.apply(PATH2);
+        assertNotNull(subparam);
+        assertEquals("{path=uri #2, text1=hello, text2=bye}", new TreeMap<>(subparam).toString());
+    }
+
+    @Test
+    public void testDoValidation() {
+        assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException();
+
+        // invalid param
+        params.setOperation(null);
+        assertThatThrownBy(() -> params.doValidation(CONTAINER))
+                        .isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    @Test
+    public void testValidate() {
+        assertTrue(params.validate(CONTAINER).isValid());
+
+        // only a few fields are required
+        CommonActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operations, "timeoutSec", 1),
+                        CommonActorParams.class);
+        assertTrue(sparse.validate(CONTAINER).isValid());
+
+        testValidateField("operation", "null", params2 -> params2.setOperation(null));
+    }
+
+    private void testValidateField(String fieldName, String expected, Consumer<CommonActorParams> makeInvalid) {
+
+        // original params should be valid
+        ValidationResult result = params.validate(CONTAINER);
+        assertTrue(fieldName, result.isValid());
+
+        // make invalid params
+        CommonActorParams params2 = makeCommonActorParams();
+        makeInvalid.accept(params2);
+        result = params2.validate(CONTAINER);
+        assertFalse(fieldName, result.isValid());
+        assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+    }
+
+    private CommonActorParams makeCommonActorParams() {
+        MyParams params2 = new MyParams();
+        params2.setOperation(operations);
+        params2.setText1(TEXT1);
+        params2.setText2(TEXT2);
+
+        return params2;
+    }
+
+    @Setter
+    public static class MyParams extends CommonActorParams {
+        @SuppressWarnings("unused")
+        private String text1;
+
+        @SuppressWarnings("unused")
+        private String text2;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
index 9dd19d5..a5215a4 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
@@ -51,6 +51,7 @@
 import org.onap.policy.common.parameters.BeanValidationResult;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
 import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.Operator;
 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
@@ -87,12 +88,15 @@
     private Executor executor;
 
     @Mock
-    private CompletableFuture<OperationOutcome> operation;
+    private CompletableFuture<OperationOutcome> operFuture;
 
     @Mock
     private Operator operator;
 
     @Mock
+    private Operation operation;
+
+    @Mock
     private Consumer<OperationOutcome> starter;
 
     private Map<String, String> payload;
@@ -110,7 +114,8 @@
 
         when(actorService.getActor(ACTOR)).thenReturn(actor);
         when(actor.getOperator(OPERATION)).thenReturn(operator);
-        when(operator.startOperation(any())).thenReturn(operation);
+        when(operator.buildOperation(any())).thenReturn(operation);
+        when(operation.start()).thenReturn(operFuture);
 
         when(event.getRequestId()).thenReturn(REQ_ID);
 
@@ -128,7 +133,7 @@
 
     @Test
     public void testStart() {
-        assertSame(operation, params.start());
+        assertSame(operFuture, params.start());
 
         assertThatIllegalArgumentException().isThrownBy(() -> params.toBuilder().context(null).build().start());
     }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
index 6c1f538..9e70853 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
@@ -21,90 +21,62 @@
 package org.onap.policy.controlloop.actorserviceprovider.parameters;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
 
 public class HttpActorParamsTest {
 
     private static final String CONTAINER = "my-container";
     private static final String CLIENT = "my-client";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
     private static final String PATH1 = "path #1";
     private static final String PATH2 = "path #2";
     private static final String URI1 = "uri #1";
     private static final String URI2 = "uri #2";
 
-    private Map<String, String> paths;
+    private Map<String, Map<String, Object>> operations;
     private HttpActorParams params;
 
     /**
-     * Initializes {@link #paths} with two items and {@link params} with a fully populated
-     * object.
+     * Initializes {@link #operations} with two items and {@link params} with a fully
+     * populated object.
      */
     @Before
     public void setUp() {
-        paths = new TreeMap<>();
-        paths.put(PATH1, URI1);
-        paths.put(PATH2, URI2);
+        operations = new TreeMap<>();
+        operations.put(PATH1, Map.of("path", URI1));
+        operations.put(PATH2, Map.of("path", URI2));
 
         params = makeHttpActorParams();
     }
 
     @Test
-    public void testMakeOperationParameters() {
-        Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER);
-        assertNull(maker.apply("unknown-operation"));
-
-        Map<String, Object> subparam = maker.apply(PATH1);
-        assertNotNull(subparam);
-        assertEquals("{clientName=my-client, path=uri #1, timeoutSec=10}", new TreeMap<>(subparam).toString());
-
-        subparam = maker.apply(PATH2);
-        assertNotNull(subparam);
-        assertEquals("{clientName=my-client, path=uri #2, timeoutSec=10}", new TreeMap<>(subparam).toString());
-    }
-
-    @Test
-    public void testDoValidation() {
-        assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException();
-
-        // invalid param
-        params.setClientName(null);
-        assertThatThrownBy(() -> params.doValidation(CONTAINER))
-                        .isInstanceOf(ParameterValidationRuntimeException.class);
-    }
-
-    @Test
     public void testValidate() {
         assertTrue(params.validate(CONTAINER).isValid());
 
-        testValidateField("clientName", "null", params2 -> params2.setClientName(null));
-        testValidateField("path", "null", params2 -> params2.setPath(null));
+        // only a few fields are required
+        HttpActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operations, "timeoutSec", 1),
+                        HttpActorParams.class);
+        assertTrue(sparse.validate(CONTAINER).isValid());
+
+        testValidateField("operation", "null", params2 -> params2.setOperation(null));
         testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
 
         // check edge cases
         params.setTimeoutSec(0);
-        assertTrue(params.validate(CONTAINER).isValid());
+        assertFalse(params.validate(CONTAINER).isValid());
 
         params.setTimeoutSec(1);
         assertTrue(params.validate(CONTAINER).isValid());
-
-        // one path value is null
-        testValidateField(PATH2, "null", params2 -> paths.put(PATH2, null));
     }
 
     private void testValidateField(String fieldName, String expected, Consumer<HttpActorParams> makeInvalid) {
@@ -125,7 +97,7 @@
         HttpActorParams params2 = new HttpActorParams();
         params2.setClientName(CLIENT);
         params2.setTimeoutSec(TIMEOUT);
-        params2.setPath(paths);
+        params2.setOperation(operations);
 
         return params2;
     }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
index 6cf7328..fdfb4b4 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
@@ -36,7 +36,7 @@
     private static final String CONTAINER = "my-container";
     private static final String CLIENT = "my-client";
     private static final String PATH = "my-path";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
     private HttpParams params;
 
@@ -54,7 +54,7 @@
         testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
 
         // check edge cases
-        assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+        assertFalse(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
         assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
     }
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
index a6b11ef..4a00c06 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
@@ -424,7 +424,7 @@
     }
 
     /**
-     * Tests add(Function) when the controller is canceled after the future is added.
+     * Tests wrap(Function) when the controller is canceled after the future is added.
      */
     @Test
     public void testWrapFunctionCancel() throws Exception {
@@ -442,7 +442,7 @@
     }
 
     /**
-     * Tests add(Function) when the controller is not running.
+     * Tests wrap(Function) when the controller is not running.
      */
     @Test
     public void testWrapFunctionNotRunning() {
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java
new file mode 100644
index 0000000..54d56de
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java
@@ -0,0 +1,144 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+
+public class BidirectionalTopicHandlerTest {
+    private static final String UNKNOWN = "unknown";
+    private static final String MY_SOURCE = "my-source";
+    private static final String MY_SINK = "my-sink";
+    private static final String KEY1 = "requestId";
+    private static final String KEY2 = "subRequestId";
+
+    @Mock
+    private TopicSink publisher;
+
+    @Mock
+    private TopicSource subscriber;
+
+    @Mock
+    private TopicEndpoint mgr;
+
+    private MyTopicHandler handler;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws BidirectionalTopicClientException {
+        MockitoAnnotations.initMocks(this);
+
+        when(mgr.getTopicSinks(MY_SINK)).thenReturn(Arrays.asList(publisher));
+        when(mgr.getTopicSources(eq(Arrays.asList(MY_SOURCE)))).thenReturn(Arrays.asList(subscriber));
+
+        when(publisher.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.NOOP);
+
+        handler = new MyTopicHandler(MY_SINK, MY_SOURCE);
+
+        handler.start();
+    }
+
+    @Test
+    public void testBidirectionalTopicHandler_testGetSource_testGetTarget() {
+        assertEquals(MY_SOURCE, handler.getSourceTopic());
+        assertEquals(MY_SINK, handler.getSinkTopic());
+
+        verify(mgr).getTopicSinks(anyString());
+        verify(mgr).getTopicSources(any());
+
+        // source not found
+        assertThatThrownBy(() -> new MyTopicHandler(MY_SINK, UNKNOWN))
+                        .isInstanceOf(BidirectionalTopicClientException.class).hasMessageContaining("sources")
+                        .hasMessageContaining(UNKNOWN);
+
+        // target not found
+        assertThatThrownBy(() -> new MyTopicHandler(UNKNOWN, MY_SOURCE))
+                        .isInstanceOf(BidirectionalTopicClientException.class).hasMessageContaining("sinks")
+                        .hasMessageContaining(UNKNOWN);
+    }
+
+    @Test
+    public void testShutdown() {
+        handler.shutdown();
+        verify(subscriber).unregister(any());
+    }
+
+    @Test
+    public void testStart() {
+        verify(subscriber).register(any());
+    }
+
+    @Test
+    public void testStop() {
+        handler.stop();
+        verify(subscriber).unregister(any());
+    }
+
+    @Test
+    public void testAddForwarder() {
+        // array form
+        Forwarder forwarder = handler.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2));
+        assertNotNull(forwarder);
+
+        // repeat using list form
+        assertSame(forwarder, handler.addForwarder(Arrays.asList(new SelectorKey(KEY1), new SelectorKey(KEY2))));
+    }
+
+    @Test
+    public void testGetTopicEndpointManager() {
+        // setting "mgr" to null should cause it to use the superclass' method
+        mgr = null;
+        assertNotNull(handler.getTopicEndpointManager());
+    }
+
+
+    private class MyTopicHandler extends BidirectionalTopicHandler {
+        public MyTopicHandler(String sinkTopic, String sourceTopic) throws BidirectionalTopicClientException {
+            super(sinkTopic, sourceTopic);
+        }
+
+        @Override
+        protected TopicEndpoint getTopicEndpointManager() {
+            return (mgr != null ? mgr : super.getTopicEndpointManager());
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java
new file mode 100644
index 0000000..a01159b
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java
@@ -0,0 +1,199 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class ForwarderTest {
+    private static final String TEXT = "some text";
+
+    private static final String KEY1 = "requestId";
+    private static final String KEY2 = "container";
+    private static final String SUBKEY = "subRequestId";
+
+    private static final String VALUEA_REQID = "hello";
+    private static final String VALUEA_SUBREQID = "world";
+
+    // request id is shared with value A
+    private static final String VALUEB_REQID = "hello";
+    private static final String VALUEB_SUBREQID = "another world";
+
+    // unique values
+    private static final String VALUEC_REQID = "bye";
+    private static final String VALUEC_SUBREQID = "bye-bye";
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener1;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener1b;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener2;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener3;
+
+    private Forwarder forwarder;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        forwarder = new Forwarder(Arrays.asList(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+
+        forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+        forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+        forwarder.register(Arrays.asList(VALUEB_REQID, VALUEB_SUBREQID), listener2);
+        forwarder.register(Arrays.asList(VALUEC_REQID, VALUEC_SUBREQID), listener3);
+    }
+
+    @Test
+    public void testRegister() {
+        // key size mismatches
+        assertThatIllegalArgumentException().isThrownBy(() -> forwarder.register(Arrays.asList(), listener1))
+                        .withMessage("key/value mismatch");
+        assertThatIllegalArgumentException()
+                        .isThrownBy(() -> forwarder.register(Arrays.asList(VALUEA_REQID), listener1))
+                        .withMessage("key/value mismatch");
+    }
+
+    @Test
+    public void testUnregister() {
+        // remove listener1b
+        forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+
+        verify(listener1).accept(TEXT, sco);
+        verify(listener1b, never()).accept(any(), any());
+
+        // remove listener1
+        forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+        forwarder.onMessage(TEXT, sco);
+
+        // route a message to listener2
+        sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+        verify(listener2).accept(TEXT, sco);
+
+        // no more messages to listener1 or 1b
+        verify(listener1).accept(any(), any());
+        verify(listener1b, never()).accept(any(), any());
+    }
+
+    @Test
+    public void testOnMessage() {
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+
+        verify(listener1).accept(TEXT, sco);
+        verify(listener1b).accept(TEXT, sco);
+
+        // repeat - counts should increment
+        forwarder.onMessage(TEXT, sco);
+
+        verify(listener1, times(2)).accept(TEXT, sco);
+        verify(listener1b, times(2)).accept(TEXT, sco);
+
+        // should not have been invoked
+        verify(listener2, never()).accept(any(), any());
+        verify(listener3, never()).accept(any(), any());
+
+        // try other listeners now
+        sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+        verify(listener2).accept(TEXT, sco);
+
+        sco = makeMessage(Map.of(KEY1, VALUEC_REQID, KEY2, Map.of(SUBKEY, VALUEC_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+        verify(listener3).accept(TEXT, sco);
+
+        // message has no listeners
+        sco = makeMessage(Map.of(KEY1, "xyzzy", KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+
+        // message doesn't have both keys
+        sco = makeMessage(Map.of(KEY1, VALUEA_REQID));
+        forwarder.onMessage(TEXT, sco);
+
+        // counts should not have incremented
+        verify(listener1, times(2)).accept(any(), any());
+        verify(listener1b, times(2)).accept(any(), any());
+        verify(listener2).accept(any(), any());
+        verify(listener3).accept(any(), any());
+
+        // listener throws an exception
+        doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any());
+    }
+
+    /*
+     * Tests onMessage() when listener1 throws an exception.
+     */
+    @Test
+    public void testOnMessageListenerException1() {
+        doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any());
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+
+        verify(listener1b).accept(TEXT, sco);
+    }
+
+    /*
+     * Tests onMessage() when listener1b throws an exception.
+     */
+    @Test
+    public void testOnMessageListenerException1b() {
+        doThrow(new IllegalStateException("expected exception")).when(listener1b).accept(any(), any());
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(TEXT, sco);
+
+        verify(listener1).accept(TEXT, sco);
+    }
+
+    /**
+     * Makes a message from a map.
+     */
+    private StandardCoderObject makeMessage(Map<String, Object> map) {
+        return Util.translate("", map, StandardCoderObject.class);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java
new file mode 100644
index 0000000..19df9c2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java
@@ -0,0 +1,93 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class SelectorKeyTest {
+    private static final String FIELD1 = "map";
+    private static final String FIELD2 = "abc";
+    private static final String FIELDX = "abd";
+
+    private SelectorKey key;
+
+    @Before
+    public void setUp() {
+        key = new SelectorKey(FIELD1, FIELD2);
+    }
+
+    @Test
+    public void testHashCode_testEquals() {
+        SelectorKey key2 = new SelectorKey(FIELD1, FIELD2);
+        assertEquals(key, key2);
+        assertEquals(key.hashCode(), key2.hashCode());
+
+        key2 = new SelectorKey(FIELD1, FIELDX);
+        assertNotEquals(key, key2);
+        assertNotEquals(key.hashCode(), key2.hashCode());
+
+        // test empty key
+        key = new SelectorKey();
+        key2 = new SelectorKey();
+        assertEquals(key, key2);
+        assertEquals(key.hashCode(), key2.hashCode());
+    }
+
+    @Test
+    public void testExtractField() {
+        Map<String, Object> map = Map.of("hello", "world", FIELD1, Map.of("another", "", FIELD2, "value B"));
+        StandardCoderObject sco = Util.translate("", map, StandardCoderObject.class);
+
+        String result = key.extractField(sco);
+        assertNotNull(result);
+        assertEquals("value B", result);
+
+        // shorter key
+        assertEquals("world", new SelectorKey("hello").extractField(sco));
+        assertNull(new SelectorKey("bye").extractField(sco));
+
+        // not found
+        assertNull(new SelectorKey(FIELD1, "not field 2").extractField(sco));
+
+        // test with empty key
+        assertNull(new SelectorKey().extractField(sco));
+    }
+
+    @Getter
+    @Setter
+    @Builder
+    protected static class Data {
+        private String text;
+        private Map<String, String> map;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java
new file mode 100644
index 0000000..3012ff6
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java
@@ -0,0 +1,154 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+public class TopicListenerImplTest {
+    private static final StandardCoder coder = new StandardCoder();
+    private static final CommInfrastructure INFRA = CommInfrastructure.NOOP;
+    private static final String MY_TOPIC = "my-topic";
+    private static final String KEY1 = "requestId";
+    private static final String KEY2 = "container";
+    private static final String SUBKEY = "subRequestId";
+
+    private static final String VALUEA_REQID = "hello";
+    private static final String VALUEA_SUBREQID = "world";
+
+    private static final String VALUEB_REQID = "bye";
+
+    private Forwarder forwarder1;
+    private Forwarder forwarder2;
+    private TopicListenerImpl topic;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener1;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener1b;
+
+    @Mock
+    private BiConsumer<String, StandardCoderObject> listener2;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        topic = new TopicListenerImpl();
+
+        forwarder1 = topic.addForwarder(new SelectorKey(KEY1));
+        forwarder2 = topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY));
+
+        assertNotNull(forwarder1);
+        assertNotNull(forwarder2);
+        assertNotSame(forwarder1, forwarder2);
+
+        forwarder1.register(Arrays.asList(VALUEA_REQID), listener1);
+        forwarder1.register(Arrays.asList(VALUEB_REQID), listener1b);
+        forwarder2.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener2);
+    }
+
+    @Test
+    public void testShutdown() {
+        // shut it down, which should clear all forwarders
+        topic.shutdown();
+
+        // should get a new forwarder now
+        Forwarder forwarder = topic.addForwarder(new SelectorKey(KEY1));
+        assertNotSame(forwarder1, forwarder);
+        assertNotSame(forwarder2, forwarder);
+
+        // new forwarder should be unchanged
+        assertSame(forwarder, topic.addForwarder(new SelectorKey(KEY1)));
+    }
+
+    @Test
+    public void testAddForwarder() {
+        assertSame(forwarder1, topic.addForwarder(new SelectorKey(KEY1)));
+        assertSame(forwarder2, topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+    }
+
+    @Test
+    public void testOnTopicEvent() {
+        /*
+         * send a message that should go to listener1 on forwarder1 and listener2 on
+         * forwarder2
+         */
+        String msg = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+        verify(listener1).accept(eq(msg), any());
+        verify(listener2).accept(eq(msg), any());
+
+        // not to listener1b
+        verify(listener1b, never()).accept(any(), any());
+
+        /*
+         * now send a message that should only go to listener1b on forwarder1
+         */
+        msg = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+        // should route to listener1 on forwarder1 and listener2 on forwarder2
+        verify(listener1b).accept(eq(msg), any());
+
+        // try one where the coder throws an exception
+        topic.onTopicEvent(INFRA, MY_TOPIC, "{invalid-json");
+
+        // no extra invocations
+        verify(listener1).accept(any(), any());
+        verify(listener1b).accept(any(), any());
+        verify(listener2).accept(any(), any());
+    }
+
+    /**
+     * Makes a message from a map.
+     */
+    private String makeMessage(Map<String, Object> map) {
+        try {
+            return coder.encode(map);
+        } catch (CoderException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
index c7fe46e..7b5b9fc 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
@@ -39,4 +39,11 @@
     <logger name="org.onap.policy.controlloop.actorserviceprovider.Util" level="info" additivity="false">
         <appender-ref ref="STDOUT" />
     </logger>
+
+    <!-- this is required for OperationPartialTest -->
+    <logger
+            name="org.onap.policy.controlloop.actorserviceprovider.impl.OperationPartial"
+            level="info" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
 </configuration>
diff --git a/models-interactions/model-actors/pom.xml b/models-interactions/model-actors/pom.xml
index 8765eb4..3089114 100644
--- a/models-interactions/model-actors/pom.xml
+++ b/models-interactions/model-actors/pom.xml
@@ -36,6 +36,8 @@
 
   <modules>
     <module>actorServiceProvider</module>
+    <module>actor.test</module>
+    <module>actor.aai</module>
     <module>actor.appc</module>
     <module>actor.vfc</module>
     <module>actor.sdnc</module>
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java
new file mode 100644
index 0000000..084e4a5
--- /dev/null
+++ b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.aai;
+
+/**
+ * Constants used with A&AI classes.
+ */
+public class AaiConstants {
+
+    public static final String ACTOR_NAME = "AAI";
+    public static final String CONTEXT_PREFIX = ACTOR_NAME + ".";
+
+    private AaiConstants() {
+        // do nothing
+    }
+}
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiCqResponse.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiCqResponse.java
index 2010a5d..6fb42db 100644
--- a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiCqResponse.java
+++ b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiCqResponse.java
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  *
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
 
 public class AaiCqResponse implements Serializable {
     private static final long serialVersionUID = 1L;
+    public static final String CONTEXT_KEY = AaiConstants.CONTEXT_PREFIX + "AaiCqResponse";
     private static final String GENERIC_VNF = "generic-vnf";
     private static final String VF_MODULE = "vf-module";
     private static final Logger LOGGER = LoggerFactory.getLogger(AaiCqResponse.class);
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiManager.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiManager.java
index 923b8d3..41f4d3b 100644
--- a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiManager.java
+++ b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiManager.java
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * aai
  * ================================================================================
- * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
  * Modifications Copyright (C) 2019 Nordix Foundation.
  * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
  * ================================================================================
@@ -47,6 +47,8 @@
  */
 public final class AaiManager {
 
+    // TODO remove this class
+
     /** The Constant logger. */
     private static final Logger logger = LoggerFactory.getLogger(AaiManager.class);
 
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/AaiException.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/AaiException.java
index 1fe23cf..83923f1 100644
--- a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/AaiException.java
+++ b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/AaiException.java
@@ -23,6 +23,8 @@
 
 public class AaiException extends Exception {
 
+    // TODO remove this class
+
     private static final long serialVersionUID = 9220983727706207465L;
 
     public AaiException() {
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/Serialization.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/Serialization.java
index e42325f..326294a 100644
--- a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/Serialization.java
+++ b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/Serialization.java
@@ -26,6 +26,8 @@
 
 public final class Serialization {
 
+    // TODO remove this class
+
     public static final Gson gsonPretty = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
 
     private Serialization() {
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java
index 43fa6c0..06f6030 100644
--- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,16 +22,13 @@
 
 import java.io.Serializable;
 import javax.persistence.Embeddable;
-import lombok.Getter;
+import lombok.Data;
 import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.NonNull;
 import org.onap.policy.models.pdp.enums.PdpEngineWorkerState;
 
 @Embeddable
-@Getter
-@Setter
-@ToString
+@Data
 @NoArgsConstructor
 public class PdpEngineWorkerStatistics implements Serializable {
     private static final long serialVersionUID = 8262176849743624013L;
@@ -51,7 +48,7 @@
      *
      * @param source source from which to copy
      */
-    public PdpEngineWorkerStatistics(PdpEngineWorkerStatistics source) {
+    public PdpEngineWorkerStatistics(@NonNull PdpEngineWorkerStatistics source) {
         this.engineId = source.engineId;
         this.engineWorkerState = source.engineWorkerState;
         this.engineTimeStamp = source.engineTimeStamp;
diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java
index ad5547e..1ba983b 100644
--- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java
+++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java
@@ -2,6 +2,7 @@
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019 Nordix Foundation.
  *  Modifications Copyright (C) 2019 AT&T Intellectual Property.
+ *  Modifications Copyright (C) 2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,10 +24,9 @@
 
 import java.util.Date;
 import java.util.List;
-import lombok.Getter;
+import lombok.Data;
 import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.NonNull;
 import org.onap.policy.models.base.PfUtils;
 
 /**
@@ -34,9 +34,7 @@
  *
  * @author Ram Krishna Verma (ram.krishna.verma@est.tech)
  */
-@Getter
-@Setter
-@ToString
+@Data
 @NoArgsConstructor
 public class PdpStatistics {
 
@@ -57,7 +55,7 @@
      *
      * @param source source from which to copy
      */
-    public PdpStatistics(PdpStatistics source) {
+    public PdpStatistics(@NonNull PdpStatistics source) {
         this.pdpInstanceId = source.pdpInstanceId;
         this.timeStamp = source.timeStamp == null ? null : new Date(source.timeStamp.getTime());
         this.pdpGroupName = source.pdpGroupName;
diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java
new file mode 100644
index 0000000..c0d2ba6
--- /dev/null
+++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java
@@ -0,0 +1,62 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.pdp.concepts;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Date;
+import org.junit.Test;
+import org.onap.policy.models.pdp.enums.PdpEngineWorkerState;
+
+public class PdpEngineWorkerStatisticsTest {
+
+    @Test
+    public void testCopyConstructor() {
+        assertThatThrownBy(() -> new PdpEngineWorkerStatistics(null)).hasMessageContaining("source");
+
+        PdpEngineWorkerStatistics stat = createPdpEngineWorkerStatistics();
+        PdpEngineWorkerStatistics stat2 = new PdpEngineWorkerStatistics(stat);
+        assertEquals(stat, stat2);
+    }
+
+    @Test
+    public void testClean() {
+        PdpEngineWorkerStatistics stat = createPdpEngineWorkerStatistics();
+        stat.setEngineId(" Engine0 ");
+        stat.clean();
+        assertEquals("Engine0", stat.getEngineId());
+    }
+
+    private PdpEngineWorkerStatistics createPdpEngineWorkerStatistics() {
+        PdpEngineWorkerStatistics stat = new PdpEngineWorkerStatistics();
+        stat.setEngineId("Engine0");
+        stat.setEngineWorkerState(PdpEngineWorkerState.READY);
+        stat.setEngineTimeStamp(new Date().getTime());
+        stat.setEventCount(1);
+        stat.setLastExecutionTime(100);
+        stat.setAverageExecutionTime(99);
+        stat.setUpTime(1000);
+        stat.setLastEnterTime(2000);
+        stat.setLastStart(3000);
+        return stat;
+    }
+}
\ No newline at end of file
diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java
index 08098cc..adf9b9f 100644
--- a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java
+++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java
@@ -3,6 +3,7 @@
  * ONAP Policy Models
  * ================================================================================
  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,32 +23,35 @@
 
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
-import static org.onap.policy.models.pdp.concepts.PdpMessageUtils.removeVariableFields;
 
+import java.util.ArrayList;
+import java.util.Date;
 import org.junit.Test;
 
 public class PdpStatisticsTest {
 
     @Test
     public void testCopyConstructor() {
-        assertThatThrownBy(() -> new PdpStatistics(null)).isInstanceOf(NullPointerException.class);
+        assertThatThrownBy(() -> new PdpStatistics(null)).hasMessageContaining("source");
 
-        PdpStatistics orig = new PdpStatistics();
+        PdpStatistics orig = createPdpStatistics();
+        PdpStatistics copied = new PdpStatistics(orig);
+        assertEquals(orig, copied);
+    }
 
-        // verify with null values
-        assertEquals(removeVariableFields(orig.toString()), removeVariableFields(new PdpStatistics(orig).toString()));
-
-        // verify with all values
-        orig.setPdpInstanceId("my-instance");
-
-        int count = 1;
-        orig.setPolicyDeployCount(count++);
-        orig.setPolicyDeployFailCount(count++);
-        orig.setPolicyDeploySuccessCount(count++);
-        orig.setPolicyExecutedCount(count++);
-        orig.setPolicyExecutedFailCount(count++);
-        orig.setPolicyExecutedSuccessCount(count++);
-
-        assertEquals(removeVariableFields(orig.toString()), removeVariableFields(new PdpStatistics(orig).toString()));
+    private PdpStatistics createPdpStatistics() {
+        PdpStatistics pdpStat = new PdpStatistics();
+        pdpStat.setPdpInstanceId("PDP0");
+        pdpStat.setPdpGroupName("PDPGroup0");
+        pdpStat.setPdpSubGroupName("PDPSubGroup0");
+        pdpStat.setTimeStamp(new Date());
+        pdpStat.setPolicyDeployCount(3);
+        pdpStat.setPolicyDeploySuccessCount(1);
+        pdpStat.setPolicyDeployFailCount(2);
+        pdpStat.setPolicyExecutedCount(9);
+        pdpStat.setPolicyExecutedSuccessCount(4);
+        pdpStat.setPolicyExecutedFailCount(5);
+        pdpStat.setEngineStats(new ArrayList<>());
+        return pdpStat;
     }
 }
diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java
index 62f0c5b..0b22e1b 100644
--- a/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java
+++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,22 +21,123 @@
 package org.onap.policy.models.pdp.persistence.concepts;
 
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
+import java.util.Date;
 import org.junit.Test;
+import org.onap.policy.models.base.PfTimestampKey;
+import org.onap.policy.models.base.PfValidationResult;
+import org.onap.policy.models.pdp.concepts.PdpStatistics;
 
 /**
  * Test the {@link JpaPdpStatistics} class.
- *
  */
 public class JpaPdpStatisticsTest {
-    private static final String NULL_KEY_ERROR = "key is marked @NonNull but is null";
-    private static final String PDP1 = "ThePDP";
 
-    // TODO More unit test cases will be added later.
     @Test
-    public void testJpaPdpStatistics() {
-        assertThatThrownBy(() -> {
-            new JpaPdpStatistics((JpaPdpStatistics) null);
-        }).hasMessage("copyConcept is marked @NonNull but is null");
+    public void testConstructor() {
+        assertThatThrownBy(() -> new JpaPdpStatistics((PfTimestampKey) null)).hasMessageContaining("key");
+
+        assertThatThrownBy(() -> new JpaPdpStatistics((JpaPdpStatistics) null))
+            .hasMessageContaining("copyConcept");
+
+        assertThatThrownBy(() -> new JpaPdpStatistics((PdpStatistics) null))
+            .hasMessageContaining("authorativeConcept");
+
+        assertNotNull(new JpaPdpStatistics());
+        assertNotNull(new JpaPdpStatistics(new PfTimestampKey()));
+
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(createPdpStatistics());
+        checkEquals(pdpStat, jpaPdpStat);
+
+        JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(jpaPdpStat);
+        assertEquals(0, jpaPdpStat2.compareTo(jpaPdpStat));
+    }
+
+    @Test
+    public void testFromAuthorative() {
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics();
+        jpaPdpStat.fromAuthorative(pdpStat);
+        checkEquals(pdpStat, jpaPdpStat);
+    }
+
+    @Test
+    public void testToAuthorative() {
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(pdpStat);
+        PdpStatistics toPdpStat = jpaPdpStat.toAuthorative();
+        assertEquals(pdpStat, toPdpStat);
+    }
+
+    @Test
+    public void testCompareTo() {
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat1 = new JpaPdpStatistics(pdpStat);
+        assertEquals(-1, jpaPdpStat1.compareTo(null));
+
+        JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(pdpStat);
+        assertEquals(0, jpaPdpStat1.compareTo(jpaPdpStat2));
+
+        PdpStatistics pdpStat3 = createPdpStatistics();
+        pdpStat3.setPdpInstanceId("PDP3");
+        JpaPdpStatistics jpaPdpStat3 = new JpaPdpStatistics(pdpStat3);
+        assertNotEquals(0, jpaPdpStat1.compareTo(jpaPdpStat3));
+    }
+
+    @Test
+    public void testValidate() {
+        JpaPdpStatistics nullKeyJpaPdpStat = new JpaPdpStatistics();
+        assertFalse(nullKeyJpaPdpStat.validate(new PfValidationResult()).isOk());
+
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(pdpStat);
+        assertTrue(jpaPdpStat2.validate(new PfValidationResult()).isOk());
+    }
+
+    @Test
+    public void testClean() {
+        PdpStatistics pdpStat = createPdpStatistics();
+        JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(pdpStat);
+        jpaPdpStat.setPdpGroupName(" PDPGroup0 ");
+        jpaPdpStat.setPdpSubGroupName(" PDPSubGroup0 ");
+        jpaPdpStat.clean();
+        assertEquals("PDPGroup0", jpaPdpStat.getPdpGroupName());
+        assertEquals("PDPSubGroup0", jpaPdpStat.getPdpSubGroupName());
+    }
+
+    private void checkEquals(PdpStatistics pdpStat, JpaPdpStatistics jpaPdpStat) {
+        assertEquals(pdpStat.getPdpInstanceId(), jpaPdpStat.getKey().getName());
+        assertEquals(pdpStat.getPdpGroupName(), jpaPdpStat.getPdpGroupName());
+        assertEquals(pdpStat.getPdpSubGroupName(), jpaPdpStat.getPdpSubGroupName());
+        assertEquals(pdpStat.getTimeStamp(), jpaPdpStat.getKey().getTimeStamp());
+        assertEquals(pdpStat.getPolicyDeployCount(), jpaPdpStat.getPolicyDeployCount());
+        assertEquals(pdpStat.getPolicyDeploySuccessCount(), jpaPdpStat.getPolicyDeploySuccessCount());
+        assertEquals(pdpStat.getPolicyDeployFailCount(), jpaPdpStat.getPolicyDeployFailCount());
+        assertEquals(pdpStat.getPolicyExecutedCount(), jpaPdpStat.getPolicyExecutedCount());
+        assertEquals(pdpStat.getPolicyExecutedSuccessCount(), jpaPdpStat.getPolicyExecutedSuccessCount());
+        assertEquals(pdpStat.getPolicyExecutedFailCount(), jpaPdpStat.getPolicyExecutedFailCount());
+    }
+
+    private PdpStatistics createPdpStatistics() {
+        PdpStatistics pdpStat = new PdpStatistics();
+        pdpStat.setPdpInstanceId("PDP0");
+        pdpStat.setPdpGroupName("PDPGroup0");
+        pdpStat.setPdpSubGroupName("PDPSubGroup0");
+        pdpStat.setTimeStamp(new Date());
+        pdpStat.setPolicyDeployCount(3);
+        pdpStat.setPolicyDeploySuccessCount(1);
+        pdpStat.setPolicyDeployFailCount(2);
+        pdpStat.setPolicyExecutedCount(9);
+        pdpStat.setPolicyExecutedSuccessCount(4);
+        pdpStat.setPolicyExecutedFailCount(5);
+        pdpStat.setEngineStats(new ArrayList<>());
+        return pdpStat;
     }
 }
diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/provider/PdpStatisticsProviderTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/provider/PdpStatisticsProviderTest.java
new file mode 100644
index 0000000..effebe4
--- /dev/null
+++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/provider/PdpStatisticsProviderTest.java
@@ -0,0 +1,233 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.models.pdp.persistence.provider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.models.dao.DaoParameters;
+import org.onap.policy.models.dao.PfDao;
+import org.onap.policy.models.dao.PfDaoFactory;
+import org.onap.policy.models.dao.impl.DefaultPfDao;
+import org.onap.policy.models.pdp.concepts.PdpEngineWorkerStatistics;
+import org.onap.policy.models.pdp.concepts.PdpStatistics;
+
+public class PdpStatisticsProviderTest {
+    private static final String DAO_IS_NULL = "dao is marked @NonNull but is null";
+    private static final String LIST_IS_NULL = "pdpStatisticsList is marked @NonNull but is null";
+    private static final String GROUP0 = "group0";
+    private static final String NAME = "name";
+    private static final String GROUP = "group";
+    private static final String SUBGROUP = "subgroup";
+    private static final Date TIMESTAMP1 = new Date(1078884319);
+    private static final Date TIMESTAMP2 = new Date(1078884350);
+    private static final String ORDER = "DESC";
+
+    private PfDao pfDao;
+
+    private ArrayList<PdpStatistics> pdpStatisticsTestList = new ArrayList<>();
+    private List<PdpEngineWorkerStatistics> engineStats = new ArrayList<>();
+    private String testListStr;
+    private String name1ListStr;
+    private String createdListStr;
+
+    /**
+     * Set up test Dao.
+     */
+    @Before
+    public void setupDao() throws Exception {
+        final DaoParameters daoParameters = new DaoParameters();
+        daoParameters.setPluginClass(DefaultPfDao.class.getName());
+
+        daoParameters.setPersistenceUnit("ToscaConceptTest");
+
+        Properties jdbcProperties = new Properties();
+        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_USER, "policy");
+        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_PASSWORD, "P01icY");
+
+        // H2, use "org.mariadb.jdbc.Driver" and "jdbc:mariadb://localhost:3306/policy" for locally installed MariaDB
+        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_DRIVER, "org.h2.Driver");
+        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_URL, "jdbc:h2:mem:testdb");
+
+        daoParameters.setJdbcProperties(jdbcProperties);
+
+        pfDao = new PfDaoFactory().createPfDao(daoParameters);
+        pfDao.init(daoParameters);
+
+        PdpStatistics pdpStatistics = new PdpStatistics();
+        pdpStatistics.setPdpInstanceId(NAME);
+        pdpStatistics.setTimeStamp(TIMESTAMP1);
+        pdpStatistics.setPdpGroupName(GROUP);
+        pdpStatistics.setPdpSubGroupName(SUBGROUP);
+        pdpStatistics.setPolicyDeployCount(2);
+        pdpStatistics.setPolicyDeployFailCount(1);
+        pdpStatistics.setPolicyDeploySuccessCount(1);
+        pdpStatistics.setPolicyExecutedCount(2);
+        pdpStatistics.setPolicyExecutedFailCount(1);
+        pdpStatistics.setPolicyExecutedSuccessCount(1);
+        pdpStatistics.setEngineStats(engineStats);
+        pdpStatisticsTestList.add(pdpStatistics);
+        name1ListStr = pdpStatisticsTestList.toString();
+
+        PdpStatistics pdpStatistics2 = new PdpStatistics();
+        pdpStatistics2.setPdpInstanceId("name2");
+        pdpStatistics2.setTimeStamp(TIMESTAMP2);
+        pdpStatistics2.setPdpGroupName(GROUP);
+        pdpStatistics2.setPdpSubGroupName(SUBGROUP);
+        pdpStatistics2.setPolicyDeployCount(2);
+        pdpStatistics2.setPolicyDeployFailCount(1);
+        pdpStatistics2.setPolicyDeploySuccessCount(1);
+        pdpStatistics2.setPolicyExecutedCount(2);
+        pdpStatistics2.setPolicyExecutedFailCount(1);
+        pdpStatistics2.setPolicyExecutedSuccessCount(1);
+        pdpStatistics2.setEngineStats(engineStats);
+        pdpStatisticsTestList.add(pdpStatistics2);
+        testListStr = pdpStatisticsTestList.toString();
+
+        List<PdpStatistics> createdPdpStatisticsList;
+        createdPdpStatisticsList = new PdpStatisticsProvider().createPdpStatistics(pfDao, pdpStatisticsTestList);
+        createdListStr = createdPdpStatisticsList.toString();
+        assertEquals(createdListStr.replaceAll("\\s+", ""), testListStr.replaceAll("\\s+", ""));
+
+    }
+
+    @After
+    public void teardown() {
+        pfDao.close();
+    }
+
+    @Test
+    public void testNotOkPdpStatistics() {
+        PdpStatistics pdpStatisticsErr = new PdpStatistics();
+        pdpStatisticsErr.setPdpInstanceId("NULL");
+        pdpStatisticsErr.setPdpGroupName(GROUP);
+        ArrayList<PdpStatistics> pdpStatisticsNullList = new ArrayList<>();
+        pdpStatisticsNullList.add(pdpStatisticsErr);
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().createPdpStatistics(pfDao, null);
+        }).hasMessage(LIST_IS_NULL);
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().updatePdpStatistics(pfDao, null);
+        }).hasMessage(LIST_IS_NULL);
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().createPdpStatistics(pfDao, pdpStatisticsNullList);
+        }).hasMessageContaining("pdp statictics \"NULL\" is not valid");
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().updatePdpStatistics(pfDao, pdpStatisticsNullList);
+        }).hasMessageContaining("pdp statistics \"NULL:0.0.0:0\" is not valid");
+    }
+
+    @Test
+    public void testGetPdpStatistics() throws Exception {
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().createPdpStatistics(null, null);
+        }).hasMessage(DAO_IS_NULL);
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().getPdpStatistics(null, null, null);
+        }).hasMessage(DAO_IS_NULL);
+
+        List<PdpStatistics> getPdpStatisticsList;
+        getPdpStatisticsList = new PdpStatisticsProvider().getPdpStatistics(pfDao, NAME, TIMESTAMP1);
+        assertThat(getPdpStatisticsList).hasSize(1);
+        String gotListStr = getPdpStatisticsList.toString();
+        assertEquals(name1ListStr.replaceAll("\\s+", ""), gotListStr.replaceAll("\\s+", ""));
+
+        // name is null
+        getPdpStatisticsList = new PdpStatisticsProvider().getPdpStatistics(pfDao, null, TIMESTAMP1);
+        gotListStr = getPdpStatisticsList.toString();
+        assertEquals(testListStr.replaceAll("\\s+", ""), gotListStr.replaceAll("\\s+", ""));
+    }
+
+    @Test
+    public void testGetFilteredPdpStatistics() throws Exception {
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().getFilteredPdpStatistics(null, NAME, GROUP, SUBGROUP, TIMESTAMP1, TIMESTAMP2,
+                    ORDER, 1);
+        }).hasMessage(DAO_IS_NULL);
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().getFilteredPdpStatistics(pfDao, NAME, null, null, TIMESTAMP1, TIMESTAMP2, ORDER,
+                    1);
+        }).hasMessage("pdpGroupName is marked @NonNull but is null");
+
+
+        List<PdpStatistics> createdPdpStatisticsList;
+        createdPdpStatisticsList = new PdpStatisticsProvider().createPdpStatistics(pfDao, pdpStatisticsTestList);
+        createdListStr = createdPdpStatisticsList.toString();
+        assertEquals(createdListStr.replaceAll("\\s+", ""), testListStr.replaceAll("\\s+", ""));
+
+        List<PdpStatistics> getPdpStatisticsList;
+        getPdpStatisticsList = new PdpStatisticsProvider().getFilteredPdpStatistics(pfDao, NAME, GROUP, null,
+                TIMESTAMP1, TIMESTAMP2, ORDER, 0);
+        assertThat(getPdpStatisticsList).hasSize(1);
+        getPdpStatisticsList = new PdpStatisticsProvider().getFilteredPdpStatistics(pfDao, "name2", GROUP, null,
+                TIMESTAMP1, TIMESTAMP2, ORDER, 0);
+        assertThat(getPdpStatisticsList).hasSize(1);
+        getPdpStatisticsList = new PdpStatisticsProvider().getFilteredPdpStatistics(pfDao, "name2", GROUP, SUBGROUP,
+                TIMESTAMP1, TIMESTAMP2, ORDER, 0);
+        assertThat(getPdpStatisticsList).hasSize(1);
+    }
+
+    @Test
+    public void testUpdatePdpStatistics() throws Exception {
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().updatePdpStatistics(null, null);
+        }).hasMessage(DAO_IS_NULL);
+
+        pdpStatisticsTestList.get(0).setPdpGroupName(GROUP0);
+        testListStr = pdpStatisticsTestList.toString();
+        List<PdpStatistics> updatePdpStatisticsList =
+                new PdpStatisticsProvider().updatePdpStatistics(pfDao, pdpStatisticsTestList);
+        String gotListStr = updatePdpStatisticsList.toString();
+        assertEquals(testListStr.replaceAll("\\s+", ""), gotListStr.replaceAll("\\s+", ""));
+
+    }
+
+    @Test
+    public void testDeletePdpStatistics() throws Exception {
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().deletePdpStatistics(null, null, null);
+        }).hasMessage(DAO_IS_NULL);
+
+        assertThatThrownBy(() -> {
+            new PdpStatisticsProvider().deletePdpStatistics(pfDao, null, null);
+        }).hasMessage("name is marked @NonNull but is null");
+
+        List<PdpStatistics> deletedPdpStatisticsList =
+                new PdpStatisticsProvider().deletePdpStatistics(pfDao, NAME, null);
+        String gotListStr = deletedPdpStatisticsList.toString();
+        assertEquals(name1ListStr.replaceAll("\\s+", ""), gotListStr.replaceAll("\\s+", ""));
+    }
+
+}
diff --git a/models-pdp/src/test/resources/META-INF/persistence.xml b/models-pdp/src/test/resources/META-INF/persistence.xml
index b1a1795..5c7caae 100644
--- a/models-pdp/src/test/resources/META-INF/persistence.xml
+++ b/models-pdp/src/test/resources/META-INF/persistence.xml
@@ -32,6 +32,8 @@
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class>
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class>
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpStatistics</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class>
 
         <properties>
             <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/AuthorativeToscaProviderReferenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/AuthorativeToscaProviderReferenceTest.java
deleted file mode 100644
index 5f904df..0000000
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/AuthorativeToscaProviderReferenceTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.models.provider.impl;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-
-import org.eclipse.persistence.config.PersistenceUnitProperties;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.resources.ResourceUtils;
-import org.onap.policy.models.base.PfModelException;
-import org.onap.policy.models.dao.DaoParameters;
-import org.onap.policy.models.dao.PfDao;
-import org.onap.policy.models.dao.PfDaoFactory;
-import org.onap.policy.models.dao.impl.DefaultPfDao;
-import org.onap.policy.models.tosca.authorative.concepts.ToscaEntityKey;
-import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
-import org.onap.policy.models.tosca.authorative.provider.AuthorativeToscaProvider;
-import org.yaml.snakeyaml.Yaml;
-
-/**
- * Test of the {@link AuthorativeToscaProvider} class.
- *
- * @author Liam Fallon (liam.fallon@est.tech)
- */
-public class AuthorativeToscaProviderReferenceTest {
-    private static PfDao pfDao;
-    private static final StandardCoder coder = new StandardCoder();
-
-    // @formatter:off
-    private static final String[] EXAMPLE_POLICY_TYPES = {
-        "onap.policies.controlloop.guard.Blacklist#1.0.0"
-                + "#policytypes/onap.policies.controlloop.guard.Blacklist.yaml",
-        "onap.policies.controlloop.guard.coordination.FirstBlocksSecond#1.0.0"
-                + "#policytypes/onap.policies.controlloop.guard.coordination.FirstBlocksSecond.yaml",
-        "onap.policies.controlloop.guard.FrequencyLimiter#1.0.0"
-                + "#policytypes/onap.policies.controlloop.guard.FrequencyLimiter.yaml",
-        "onap.policies.controlloop.guard.MinMax#1.0.0"
-                + "#policytypes/onap.policies.controlloop.guard.MinMax.yaml",
-        "onap.policies.controlloop.operational.Common#1.0.0"
-                + "#policytypes/onap.policies.controlloop.operational.Common.yaml",
-        "onap.policies.controlloop.Operational#1.0.0"
-                + "#policytypes/onap.policies.controlloop.Operational.yaml",
-        "onap.policies.drools.Controller#1.0.0"
-                + "#policytypes/onap.policies.drools.Controller.yaml",
-        "onap.policies.monitoring.cdap.tca.hi.lo.app#1.0.0"
-                + "#policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml",
-        "onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server#1.0.0"
-                + "#policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml",
-        "onap.policies.Naming#1.0.0"
-                + "#policytypes/onap.policies.Naming.yaml",
-        "onap.policies.native.Apex#1.0.0"
-                + "#policytypes/onap.policies.native.Apex.yaml",
-        "onap.policies.native.Drools#1.0.0"
-                + "#policytypes/onap.policies.native.Drools.yaml",
-        "onap.policies.native.Xacml#1.0.0"
-                + "#policytypes/onap.policies.native.Xacml.yaml",
-        "onap.policies.optimization.resource.AffinityPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.AffinityPolicy.yaml",
-        "onap.policies.optimization.resource.DistancePolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.DistancePolicy.yaml",
-        "onap.policies.optimization.resource.HpaPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.HpaPolicy.yaml",
-        "onap.policies.optimization.resource.OptimizationPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.OptimizationPolicy.yaml",
-        "onap.policies.optimization.resource.PciPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.PciPolicy.yaml",
-        "onap.policies.optimization.resource.Vim_fit#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.Vim_fit.yaml",
-        "onap.policies.optimization.resource.VnfPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.resource.VnfPolicy.yaml",
-        "onap.policies.optimization.Resource#1.0.0"
-                + "#policytypes/onap.policies.optimization.Resource.yaml",
-        "onap.policies.optimization.service.QueryPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.service.QueryPolicy.yaml",
-        "onap.policies.optimization.service.SubscriberPolicy#1.0.0"
-                + "#policytypes/onap.policies.optimization.service.SubscriberPolicy.yaml",
-        "onap.policies.optimization.Service#1.0.0"
-                + "#policytypes/onap.policies.optimization.Service.yaml",
-        "onap.policies.Optimization#1.0.0"
-                + "#policytypes/onap.policies.Optimization.yaml"};
-    // @formatter:on
-
-    private static final Map<ToscaEntityKey, ToscaServiceTemplate> policyTypeMap = new LinkedHashMap<>();
-
-    /**
-     * Set up the DAO towards the database.
-     *
-     * @throws Exception on database errors
-     */
-    @BeforeClass
-    public static void beforeSetupDao() throws Exception {
-        final DaoParameters daoParameters = new DaoParameters();
-        daoParameters.setPluginClass(DefaultPfDao.class.getName());
-
-        daoParameters.setPersistenceUnit("ToscaConceptTest");
-
-        Properties jdbcProperties = new Properties();
-        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_USER, "policy");
-        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_PASSWORD, "P01icY");
-
-        // H2, use "org.mariadb.jdbc.Driver" and "jdbc:mariadb://localhost:3306/policy" for locally installed MariaDB
-        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_DRIVER, "org.h2.Driver");
-        jdbcProperties.setProperty(PersistenceUnitProperties.JDBC_URL, "jdbc:h2:mem:testdb");
-
-        daoParameters.setJdbcProperties(jdbcProperties);
-
-        pfDao = new PfDaoFactory().createPfDao(daoParameters);
-        pfDao.init(daoParameters);
-    }
-
-    /**
-     * Populate the database.
-     *
-     * @throws PfModelException on database exceptions
-     * @throws CoderException on JSON encoding/decoding errors
-     */
-    @BeforeClass
-    public static void beforeFillDatabase() throws PfModelException, CoderException {
-        for (String policyTypeDataString : EXAMPLE_POLICY_TYPES) {
-            String[] policyTypeDataArray = policyTypeDataString.split("#");
-            String policyTypeYamlDefinition = ResourceUtils.getResourceAsString(policyTypeDataArray[2]);
-
-            Object yamlObject = new Yaml().load(policyTypeYamlDefinition);
-            String policyTypeJsonDefinition = coder.encode(yamlObject);
-
-            ToscaServiceTemplate serviceTemplate = coder.decode(policyTypeJsonDefinition, ToscaServiceTemplate.class);
-            policyTypeMap.put(new ToscaEntityKey(policyTypeDataArray[0], policyTypeDataArray[1]), serviceTemplate);
-            new AuthorativeToscaProvider().createPolicyTypes(pfDao, serviceTemplate);
-        }
-    }
-
-    @AfterClass
-    public static void afterTeardown() {
-        pfDao.close();
-    }
-
-    @Test
-    public void testPolicyTypeRead() throws PfModelException, CoderException {
-        for (Entry<ToscaEntityKey, ToscaServiceTemplate> policyTypeMapEntry : policyTypeMap.entrySet()) {
-            ToscaServiceTemplate serviceTemplate = new AuthorativeToscaProvider().getPolicyTypes(pfDao,
-                    policyTypeMapEntry.getKey().getName(), policyTypeMapEntry.getKey().getVersion());
-
-            assertEquals(1, serviceTemplate.getPolicyTypes().size());
-
-            String originalJson = coder.encode(policyTypeMapEntry.getValue());
-            String databaseJson = coder.encode(serviceTemplate);
-
-            // TODO: This test has no chance of passing yet but must eventually pass to prove that the policy types
-            // TODO: that were retrieved are the same as the policy types that were stored
-            // assertEquals(originalJson, databaseJson);
-        }
-    }
-}
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/DatabasePolicyModelsProviderTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/DatabasePolicyModelsProviderTest.java
index fd566a5..aa6802a 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/DatabasePolicyModelsProviderTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/DatabasePolicyModelsProviderTest.java
@@ -25,14 +25,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Date;
 import java.util.List;
+
 import org.junit.Before;
 import org.junit.Test;
+import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.pdp.concepts.Pdp;
 import org.onap.policy.models.pdp.concepts.PdpGroup;
 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
@@ -49,8 +50,6 @@
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.onap.policy.models.tosca.legacy.concepts.LegacyGuardPolicyInput;
 import org.onap.policy.models.tosca.legacy.concepts.LegacyOperationalPolicy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Test the database models provider implementation.
@@ -82,8 +81,6 @@
 
     private static final String ORDER = "DESC";
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(DatabasePolicyModelsProviderTest.class);
-
     PolicyModelsProviderParameters parameters;
 
     /**
@@ -139,210 +136,210 @@
     @Test
     public void testProviderMethodsNull() throws Exception {
 
-        try (PolicyModelsProvider databaseProvider =
-                new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters)) {
+        PolicyModelsProvider databaseProvider =
+                new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPolicyTypes(null);
-            }).hasMessageMatching(FILTER_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPolicyTypes(null);
+        }).hasMessageMatching(FILTER_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPolicyTypeList(null);
-            }).hasMessageMatching(FILTER_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPolicyTypeList(null);
+        }).hasMessageMatching(FILTER_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPolicyTypes(null);
-            }).hasMessageMatching(TEMPLATE_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.createPolicyTypes(null);
+        }).hasMessageMatching(TEMPLATE_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePolicyTypes(null);
-            }).hasMessageMatching(TEMPLATE_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePolicyTypes(null);
+        }).hasMessageMatching(TEMPLATE_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicyType(null, null);
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicyType(null, null);
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicyType("aaa", null);
-            }).hasMessageMatching("^version is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicyType("aaa", null);
+        }).hasMessageMatching("^version is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicyType(null, "aaa");
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicyType(null, "aaa");
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPolicies(null);
-            }).hasMessageMatching(FILTER_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPolicies(null);
+        }).hasMessageMatching(FILTER_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPolicyList(null);
-            }).hasMessageMatching(FILTER_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPolicyList(null);
+        }).hasMessageMatching(FILTER_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPolicies(null);
-            }).hasMessageMatching(TEMPLATE_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.createPolicies(null);
+        }).hasMessageMatching(TEMPLATE_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePolicies(null);
-            }).hasMessageMatching(TEMPLATE_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePolicies(null);
+        }).hasMessageMatching(TEMPLATE_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicy(null, null);
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicy(null, null);
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicy(null, "aaa");
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicy(null, "aaa");
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePolicy("aaa", null);
-            }).hasMessageMatching("^version is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePolicy("aaa", null);
+        }).hasMessageMatching("^version is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getOperationalPolicy(null, null);
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getOperationalPolicy(null, null);
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getOperationalPolicy(null, "");
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getOperationalPolicy(null, "");
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getOperationalPolicy("", null);
-            }).hasMessage("no policy found for policy: :null");
+        assertThatThrownBy(() -> {
+            databaseProvider.getOperationalPolicy("", null);
+        }).hasMessage("no policy found for policy: :null");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createOperationalPolicy(null);
-            }).hasMessageMatching("^legacyOperationalPolicy is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.createOperationalPolicy(null);
+        }).hasMessageMatching("^legacyOperationalPolicy is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updateOperationalPolicy(null);
-            }).hasMessageMatching("^legacyOperationalPolicy is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.updateOperationalPolicy(null);
+        }).hasMessageMatching("^legacyOperationalPolicy is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteOperationalPolicy(null, null);
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteOperationalPolicy(null, null);
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteOperationalPolicy(null, "");
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteOperationalPolicy(null, "");
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteOperationalPolicy("", null);
-            }).hasMessageMatching("^policyVersion is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteOperationalPolicy("", null);
+        }).hasMessageMatching("^policyVersion is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getGuardPolicy(null, null);
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getGuardPolicy(null, null);
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getGuardPolicy(null, "");
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getGuardPolicy(null, "");
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getGuardPolicy("", null);
-            }).hasMessage("no policy found for policy: :null");
+        assertThatThrownBy(() -> {
+            databaseProvider.getGuardPolicy("", null);
+        }).hasMessage("no policy found for policy: :null");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createGuardPolicy(null);
-            }).hasMessageMatching("^legacyGuardPolicy is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.createGuardPolicy(null);
+        }).hasMessageMatching("^legacyGuardPolicy is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updateGuardPolicy(null);
-            }).hasMessageMatching("^legacyGuardPolicy is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.updateGuardPolicy(null);
+        }).hasMessageMatching("^legacyGuardPolicy is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteGuardPolicy(null, null);
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteGuardPolicy(null, null);
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteGuardPolicy(null, "");
-            }).hasMessageMatching(POLICY_ID_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteGuardPolicy(null, "");
+        }).hasMessageMatching(POLICY_ID_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteGuardPolicy("", null);
-            }).hasMessageMatching("^policyVersion is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteGuardPolicy("", null);
+        }).hasMessageMatching("^policyVersion is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPdpGroups(null);
-            }).hasMessageMatching(FILTER_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPdpGroups(null);
+        }).hasMessageMatching(FILTER_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPdpGroups(null);
-            }).hasMessageMatching("^pdpGroups is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.createPdpGroups(null);
+        }).hasMessageMatching("^pdpGroups is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpGroups(null);
-            }).hasMessageMatching("^pdpGroups is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpGroups(null);
+        }).hasMessageMatching("^pdpGroups is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpSubGroup(null, null);
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpSubGroup(null, null);
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpSubGroup(null, new PdpSubGroup());
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpSubGroup(null, new PdpSubGroup());
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpSubGroup(NAME, null);
-            }).hasMessageMatching(SUBGROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpSubGroup(NAME, null);
+        }).hasMessageMatching(SUBGROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpSubGroup(NAME, new PdpSubGroup());
-            }).hasMessage("parameter \"localName\" is null");
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpSubGroup(NAME, new PdpSubGroup());
+        }).hasMessage("parameter \"localName\" is null");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(null, null, null);
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(null, null, null);
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(null, null, new Pdp());
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(null, null, new Pdp());
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(null, "sub", null);
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(null, "sub", null);
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(null, "sub", new Pdp());
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(null, "sub", new Pdp());
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(NAME, null, null);
-            }).hasMessageMatching(SUBGROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(NAME, null, null);
+        }).hasMessageMatching(SUBGROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(NAME, null, new Pdp());
-            }).hasMessageMatching(SUBGROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(NAME, null, new Pdp());
+        }).hasMessageMatching(SUBGROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(NAME, "sub", null);
-            }).hasMessageMatching("^pdp is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(NAME, "sub", null);
+        }).hasMessageMatching("^pdp is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdp(NAME, "sub", new Pdp());
-            }).hasMessage("parameter \"localName\" is null");
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdp(NAME, "sub", new Pdp());
+        }).hasMessage("parameter \"localName\" is null");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePdpGroup(null);
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePdpGroup(null);
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getFilteredPdpStatistics(NAME, null, "sub", TIMESTAMP, TIMESTAMP, ORDER, 0);
-            }).hasMessageMatching(GROUP_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.getFilteredPdpStatistics(NAME, null, "sub", TIMESTAMP, TIMESTAMP, ORDER, 0);
+        }).hasMessageMatching(GROUP_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPdpStatistics(null);
-            }).hasMessageMatching("^pdpStatisticsList is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.createPdpStatistics(null);
+        }).hasMessageMatching("^pdpStatisticsList is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePdpStatistics(null);
-            }).hasMessageMatching("^pdpStatisticsList is marked .*on.*ull but is null$");
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePdpStatistics(null);
+        }).hasMessageMatching("^pdpStatisticsList is marked .*on.*ull but is null$");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePdpStatistics(null, TIMESTAMP);
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePdpStatistics(null, TIMESTAMP);
+        }).hasMessageMatching(NAME_IS_NULL);
 
-        }
+        databaseProvider.close();
     }
 
     @Test
@@ -358,181 +355,185 @@
     }
 
     @Test
-    public void testProviderMethods() {
-        try (PolicyModelsProvider databaseProvider =
-                new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters)) {
+    public void testProviderMethods() throws PfModelException {
+        PolicyModelsProvider databaseProvider =
+                new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters);
 
-            assertTrue(databaseProvider.getPolicyTypes(NAME, VERSION_100).getPolicyTypes().isEmpty());
-            assertTrue(databaseProvider.getPolicyTypeList(NAME, VERSION_100).isEmpty());
-            assertEquals(0, databaseProvider.getFilteredPolicyTypes(ToscaPolicyTypeFilter.builder().build())
-                    .getPolicyTypes().size());
-            assertEquals(0, databaseProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().build()).size());
+        assertThatThrownBy(() -> databaseProvider.getPolicyTypes(NAME, VERSION_100))
+                .hasMessage("service template not found in database");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPolicyTypes(new ToscaServiceTemplate());
-            }).hasMessage("no policy types specified on service template");
+        assertTrue(databaseProvider.getPolicyTypeList(NAME, VERSION_100).isEmpty());
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePolicyTypes(new ToscaServiceTemplate());
-            }).hasMessage("no policy types specified on service template");
+        assertThatThrownBy(() -> databaseProvider.getFilteredPolicyTypes(ToscaPolicyTypeFilter.builder().build()))
+                .hasMessage("service template not found in database");
 
-            assertTrue(databaseProvider.deletePolicyType(NAME, VERSION_100).getPolicyTypes().isEmpty());
+        assertTrue(databaseProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().build()).isEmpty());
 
-            assertTrue(databaseProvider.deletePolicyType(NAME, VERSION_100).getPolicyTypes().isEmpty());
+        assertThatThrownBy(() -> {
+            databaseProvider.createPolicyTypes(new ToscaServiceTemplate());
+        }).hasMessage("no policy types specified on service template");
 
-            assertTrue(
-                    databaseProvider.getPolicies(NAME, VERSION_100).getToscaTopologyTemplate().getPolicies().isEmpty());
-            assertTrue(databaseProvider.getPolicyList(NAME, VERSION_100).isEmpty());
-            assertEquals(0, databaseProvider.getFilteredPolicies(ToscaPolicyFilter.builder().build())
-                    .getToscaTopologyTemplate().getPolicies().size());
-            assertEquals(0, databaseProvider.getFilteredPolicyList(ToscaPolicyFilter.builder().build()).size());
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePolicyTypes(new ToscaServiceTemplate());
+        }).hasMessage("no policy types specified on service template");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createPolicies(new ToscaServiceTemplate());
-            }).hasMessage("topology template not specified on service template");
+        assertThatThrownBy(() -> databaseProvider.deletePolicyType(NAME, VERSION_100))
+                .hasMessage("service template not found in database");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updatePolicies(new ToscaServiceTemplate());
-            }).hasMessage("topology template not specified on service template");
+        assertThatThrownBy(() -> databaseProvider.getPolicies(NAME, VERSION_100))
+                .hasMessage("service template not found in database");
 
-            assertTrue(databaseProvider.deletePolicy("Policy", "0.0.0").getToscaTopologyTemplate().getPolicies()
-                    .isEmpty());
+        assertTrue(databaseProvider.getPolicyList(NAME, VERSION_100).isEmpty());
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getOperationalPolicy(POLICY_ID, null);
-            }).hasMessage("no policy found for policy: policy_id:null");
+        assertThatThrownBy(() -> databaseProvider.getFilteredPolicies(ToscaPolicyFilter.builder().build()))
+                .hasMessage("service template not found in database");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getOperationalPolicy(POLICY_ID, "10");
-            }).hasMessage("no policy found for policy: policy_id:10");
+        assertTrue(databaseProvider.getFilteredPolicyList(ToscaPolicyFilter.builder().build()).isEmpty());
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createOperationalPolicy(new LegacyOperationalPolicy());
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.createPolicies(new ToscaServiceTemplate());
+        }).hasMessage("topology template not specified on service template");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updateOperationalPolicy(new LegacyOperationalPolicy());
-            }).hasMessageMatching(NAME_IS_NULL);
+        assertThatThrownBy(() -> {
+            databaseProvider.updatePolicies(new ToscaServiceTemplate());
+        }).hasMessage("topology template not specified on service template");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteOperationalPolicy(POLICY_ID, "55");
-            }).hasMessage("no policy found for policy: policy_id:55");
+        assertThatThrownBy(() -> databaseProvider.deletePolicy("Policy", "0.0.0").getToscaTopologyTemplate())
+                .hasMessage("service template not found in database");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getGuardPolicy(POLICY_ID, null);
-            }).hasMessage("no policy found for policy: policy_id:null");
+        assertThatThrownBy(() -> {
+            databaseProvider.getOperationalPolicy(POLICY_ID, null);
+        }).hasMessage("no policy found for policy: policy_id:null");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.getGuardPolicy(POLICY_ID, "6");
-            }).hasMessage("no policy found for policy: policy_id:6");
+        assertThatThrownBy(() -> {
+            databaseProvider.getOperationalPolicy(POLICY_ID, "10");
+        }).hasMessage("no policy found for policy: policy_id:10");
 
-            assertThatThrownBy(() -> {
-                databaseProvider.createGuardPolicy(new LegacyGuardPolicyInput());
-            }).hasMessage("policy type for guard policy \"null\" unknown");
+        assertThatThrownBy(() -> {
+            databaseProvider.createOperationalPolicy(new LegacyOperationalPolicy());
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.updateGuardPolicy(new LegacyGuardPolicyInput());
-            }).hasMessage("policy type for guard policy \"null\" unknown");
+        assertThatThrownBy(() -> {
+            databaseProvider.updateOperationalPolicy(new LegacyOperationalPolicy());
+        }).hasMessageMatching(NAME_IS_NULL);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deleteGuardPolicy(POLICY_ID, "33");
-            }).hasMessage("no policy found for policy: policy_id:33");
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteOperationalPolicy(POLICY_ID, "55");
+        }).hasMessage("no policy found for policy: policy_id:55");
 
-            assertEquals(0, databaseProvider.getPdpGroups(NAME).size());
-            assertEquals(0, databaseProvider.getFilteredPdpGroups(PdpGroupFilter.builder().build()).size());
+        assertThatThrownBy(() -> {
+            databaseProvider.getGuardPolicy(POLICY_ID, null);
+        }).hasMessage("no policy found for policy: policy_id:null");
 
-            assertNotNull(databaseProvider.createPdpGroups(new ArrayList<>()));
-            assertNotNull(databaseProvider.updatePdpGroups(new ArrayList<>()));
+        assertThatThrownBy(() -> {
+            databaseProvider.getGuardPolicy(POLICY_ID, "6");
+        }).hasMessage("no policy found for policy: policy_id:6");
 
-            PdpGroup pdpGroup = new PdpGroup();
-            pdpGroup.setName(GROUP);
-            pdpGroup.setVersion("1.2.3");
-            pdpGroup.setPdpGroupState(PdpState.ACTIVE);
-            pdpGroup.setPdpSubgroups(new ArrayList<>());
-            List<PdpGroup> groupList = new ArrayList<>();
-            groupList.add(pdpGroup);
+        assertThatThrownBy(() -> {
+            databaseProvider.createGuardPolicy(new LegacyGuardPolicyInput());
+        }).hasMessage("policy type for guard policy \"null\" unknown");
 
-            PdpSubGroup pdpSubGroup = new PdpSubGroup();
-            pdpSubGroup.setPdpType("type");
-            pdpSubGroup.setDesiredInstanceCount(123);
-            pdpSubGroup.setSupportedPolicyTypes(new ArrayList<>());
-            pdpSubGroup.getSupportedPolicyTypes().add(new ToscaPolicyTypeIdentifier("type", "7.8.9"));
-            pdpGroup.getPdpSubgroups().add(pdpSubGroup);
+        assertThatThrownBy(() -> {
+            databaseProvider.updateGuardPolicy(new LegacyGuardPolicyInput());
+        }).hasMessage("policy type for guard policy \"null\" unknown");
 
-            Pdp pdp = new Pdp();
-            pdp.setInstanceId("type-0");
-            pdp.setMessage("Hello");
-            pdp.setPdpState(PdpState.ACTIVE);
-            pdp.setHealthy(PdpHealthStatus.UNKNOWN);
-            pdpSubGroup.setPdpInstances(new ArrayList<>());
-            pdpSubGroup.getPdpInstances().add(pdp);
+        assertThatThrownBy(() -> {
+            databaseProvider.deleteGuardPolicy(POLICY_ID, "33");
+        }).hasMessage("no policy found for policy: policy_id:33");
 
-            PdpStatistics pdpStatistics = new PdpStatistics();
-            pdpStatistics.setPdpInstanceId(NAME);
-            pdpStatistics.setTimeStamp(new Date());
-            pdpStatistics.setPdpGroupName(GROUP);
-            pdpStatistics.setPdpSubGroupName("type");
-            ArrayList<PdpStatistics> statisticsArrayList = new ArrayList<>();
-            statisticsArrayList.add(pdpStatistics);
+        assertEquals(0, databaseProvider.getPdpGroups(NAME).size());
+        assertEquals(0, databaseProvider.getFilteredPdpGroups(PdpGroupFilter.builder().build()).size());
 
-            assertEquals(123, databaseProvider.createPdpGroups(groupList).get(0).getPdpSubgroups().get(0)
-                    .getDesiredInstanceCount());
-            assertEquals(1, databaseProvider.getPdpGroups(GROUP).size());
+        assertNotNull(databaseProvider.createPdpGroups(new ArrayList<>()));
+        assertNotNull(databaseProvider.updatePdpGroups(new ArrayList<>()));
 
-            pdpSubGroup.setDesiredInstanceCount(234);
-            databaseProvider.updatePdpSubGroup(GROUP, pdpSubGroup);
-            assertEquals(234,
-                    databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getDesiredInstanceCount());
+        PdpGroup pdpGroup = new PdpGroup();
+        pdpGroup.setName(GROUP);
+        pdpGroup.setVersion("1.2.3");
+        pdpGroup.setPdpGroupState(PdpState.ACTIVE);
+        pdpGroup.setPdpSubgroups(new ArrayList<>());
+        List<PdpGroup> groupList = new ArrayList<>();
+        groupList.add(pdpGroup);
 
-            assertEquals("Hello", databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getPdpInstances()
-                    .get(0).getMessage());
-            pdp.setMessage("Howdy");
-            databaseProvider.updatePdp(GROUP, "type", pdp);
-            assertEquals("Howdy", databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getPdpInstances()
-                    .get(0).getMessage());
+        PdpSubGroup pdpSubGroup = new PdpSubGroup();
+        pdpSubGroup.setPdpType("type");
+        pdpSubGroup.setDesiredInstanceCount(123);
+        pdpSubGroup.setSupportedPolicyTypes(new ArrayList<>());
+        pdpSubGroup.getSupportedPolicyTypes().add(new ToscaPolicyTypeIdentifier("type", "7.8.9"));
+        pdpGroup.getPdpSubgroups().add(pdpSubGroup);
 
-            assertThatThrownBy(() -> {
-                databaseProvider.deletePdpGroup(NAME);
-            }).hasMessage("delete of PDP group \"name:0.0.0\" failed, PDP group does not exist");
+        Pdp pdp = new Pdp();
+        pdp.setInstanceId("type-0");
+        pdp.setMessage("Hello");
+        pdp.setPdpState(PdpState.ACTIVE);
+        pdp.setHealthy(PdpHealthStatus.UNKNOWN);
+        pdpSubGroup.setPdpInstances(new ArrayList<>());
+        pdpSubGroup.getPdpInstances().add(pdp);
 
-            assertEquals(pdpGroup.getName(), databaseProvider.deletePdpGroup(GROUP).getName());
+        PdpStatistics pdpStatistics = new PdpStatistics();
+        pdpStatistics.setPdpInstanceId(NAME);
+        pdpStatistics.setTimeStamp(new Date());
+        pdpStatistics.setPdpGroupName(GROUP);
+        pdpStatistics.setPdpSubGroupName("type");
+        ArrayList<PdpStatistics> statisticsArrayList = new ArrayList<>();
+        statisticsArrayList.add(pdpStatistics);
 
-            assertEquals(0, databaseProvider.getPdpStatistics(null, null).size());
-            assertEquals(1, databaseProvider.createPdpStatistics(statisticsArrayList).size());
-            assertEquals(1, databaseProvider.updatePdpStatistics(statisticsArrayList).size());
+        assertEquals(123,
+                databaseProvider.createPdpGroups(groupList).get(0).getPdpSubgroups().get(0).getDesiredInstanceCount());
+        assertEquals(1, databaseProvider.getPdpGroups(GROUP).size());
 
-            assertEquals(NAME, databaseProvider.getPdpStatistics(null, null).get(0).getPdpInstanceId());
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(null, GROUP, null, null, null, ORDER, 0).get(0)
-                    .getPdpInstanceId());
-            assertEquals(0,
-                    databaseProvider.getFilteredPdpStatistics(null, GROUP, null, new Date(), null, ORDER, 0).size());
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(null, GROUP, null, null, new Date(), ORDER, 0)
-                    .get(0).getPdpInstanceId());
-            assertEquals(0, databaseProvider
-                    .getFilteredPdpStatistics(null, GROUP, null, new Date(), new Date(), ORDER, 0).size());
+        pdpSubGroup.setDesiredInstanceCount(234);
+        databaseProvider.updatePdpSubGroup(GROUP, pdpSubGroup);
+        assertEquals(234,
+                databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getDesiredInstanceCount());
 
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, null, null, null, ORDER, 0).get(0)
-                    .getPdpInstanceId());
-            assertEquals(0, databaseProvider
-                    .getFilteredPdpStatistics(NAME, GROUP, null, new Date(), new Date(), ORDER, 0).size());
+        assertEquals("Hello", databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getPdpInstances()
+                .get(0).getMessage());
+        pdp.setMessage("Howdy");
+        databaseProvider.updatePdp(GROUP, "type", pdp);
+        assertEquals("Howdy", databaseProvider.getPdpGroups(GROUP).get(0).getPdpSubgroups().get(0).getPdpInstances()
+                .get(0).getMessage());
 
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 0)
-                    .get(0).getPdpInstanceId());
-            assertEquals(0, databaseProvider
-                    .getFilteredPdpStatistics(NAME, GROUP, "type", new Date(), new Date(), ORDER, 0).size());
+        assertThatThrownBy(() -> {
+            databaseProvider.deletePdpGroup(NAME);
+        }).hasMessage("delete of PDP group \"name:0.0.0\" failed, PDP group does not exist");
 
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 1)
-                    .get(0).getPdpInstanceId());
-            assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 5)
-                    .get(0).getPdpInstanceId());
-            assertEquals(0, databaseProvider
-                    .getFilteredPdpStatistics(NAME, GROUP, "type", new Date(), new Date(), ORDER, 5).size());
+        assertEquals(pdpGroup.getName(), databaseProvider.deletePdpGroup(GROUP).getName());
 
-            assertEquals(NAME, databaseProvider.deletePdpStatistics(NAME, null).get(0).getPdpInstanceId());
-            assertEquals(0, databaseProvider.getPdpStatistics(null, null).size());
-        } catch (Exception exc) {
-            LOGGER.warn("test should not throw an exception", exc);
-            fail("test should not throw an exception");
-        }
+        assertEquals(0, databaseProvider.getPdpStatistics(null, null).size());
+        assertEquals(1, databaseProvider.createPdpStatistics(statisticsArrayList).size());
+        assertEquals(1, databaseProvider.updatePdpStatistics(statisticsArrayList).size());
+
+        assertEquals(NAME, databaseProvider.getPdpStatistics(null, null).get(0).getPdpInstanceId());
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(null, GROUP, null, null, null, ORDER, 0).get(0)
+                .getPdpInstanceId());
+        assertEquals(0,
+                databaseProvider.getFilteredPdpStatistics(null, GROUP, null, new Date(), null, ORDER, 0).size());
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(null, GROUP, null, null, new Date(), ORDER, 0)
+                .get(0).getPdpInstanceId());
+        assertEquals(0,
+                databaseProvider.getFilteredPdpStatistics(null, GROUP, null, new Date(), new Date(), ORDER, 0).size());
+
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, null, null, null, ORDER, 0).get(0)
+                .getPdpInstanceId());
+        assertEquals(0,
+                databaseProvider.getFilteredPdpStatistics(NAME, GROUP, null, new Date(), new Date(), ORDER, 0).size());
+
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 0).get(0)
+                .getPdpInstanceId());
+        assertEquals(0, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", new Date(), new Date(), ORDER, 0)
+                .size());
+
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 1).get(0)
+                .getPdpInstanceId());
+        assertEquals(NAME, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", null, null, ORDER, 5).get(0)
+                .getPdpInstanceId());
+        assertEquals(0, databaseProvider.getFilteredPdpStatistics(NAME, GROUP, "type", new Date(), new Date(), ORDER, 5)
+                .size());
+
+        assertEquals(NAME, databaseProvider.deletePdpStatistics(NAME, null).get(0).getPdpInstanceId());
+        assertEquals(0, databaseProvider.getPdpStatistics(null, null).size());
+
+        databaseProvider.close();
     }
 }
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/DummyPolicyModelsProviderTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/DummyPolicyModelsProviderTest.java
index 452bbd4..500d9d4 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/DummyPolicyModelsProviderTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/DummyPolicyModelsProviderTest.java
@@ -78,47 +78,45 @@
         parameters.setDatabaseUrl("jdbc:dummy");
         parameters.setPersistenceUnit("dummy");
 
-        try (PolicyModelsProvider dummyProvider =
-                new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters)) {
-            dummyProvider.init();
+        PolicyModelsProvider dummyProvider = new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters);
+        dummyProvider.init();
 
-            assertNotNull(dummyProvider.getPolicyTypes("name", VERSION));
-            assertNotNull(dummyProvider.getFilteredPolicyTypes(ToscaPolicyTypeFilter.builder().build()));
-            assertNotNull(dummyProvider.getPolicyTypeList("name", VERSION));
-            assertNotNull(dummyProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().build()));
-            assertNotNull(dummyProvider.createPolicyTypes(new ToscaServiceTemplate()));
-            assertNotNull(dummyProvider.updatePolicyTypes(new ToscaServiceTemplate()));
-            assertNotNull(dummyProvider.deletePolicyType("name", VERSION));
+        assertNotNull(dummyProvider.getPolicyTypes("name", VERSION));
+        assertNotNull(dummyProvider.getFilteredPolicyTypes(ToscaPolicyTypeFilter.builder().build()));
+        assertNotNull(dummyProvider.getPolicyTypeList("name", VERSION));
+        assertNotNull(dummyProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().build()));
+        assertNotNull(dummyProvider.createPolicyTypes(new ToscaServiceTemplate()));
+        assertNotNull(dummyProvider.updatePolicyTypes(new ToscaServiceTemplate()));
+        assertNotNull(dummyProvider.deletePolicyType("name", VERSION));
 
-            assertNotNull(dummyProvider.getPolicies("name", VERSION));
-            assertNotNull(dummyProvider.getFilteredPolicies(ToscaPolicyFilter.builder().build()));
-            assertNotNull(dummyProvider.getPolicyList("name", VERSION));
-            assertNotNull(dummyProvider.getFilteredPolicyList(ToscaPolicyFilter.builder().build()));
-            assertNotNull(dummyProvider.createPolicies(new ToscaServiceTemplate()));
-            assertNotNull(dummyProvider.updatePolicies(new ToscaServiceTemplate()));
-            assertNotNull(dummyProvider.deletePolicy("name", VERSION));
+        assertNotNull(dummyProvider.getPolicies("name", VERSION));
+        assertNotNull(dummyProvider.getFilteredPolicies(ToscaPolicyFilter.builder().build()));
+        assertNotNull(dummyProvider.getPolicyList("name", VERSION));
+        assertNotNull(dummyProvider.getFilteredPolicyList(ToscaPolicyFilter.builder().build()));
+        assertNotNull(dummyProvider.createPolicies(new ToscaServiceTemplate()));
+        assertNotNull(dummyProvider.updatePolicies(new ToscaServiceTemplate()));
+        assertNotNull(dummyProvider.deletePolicy("name", VERSION));
 
-            assertNotNull(dummyProvider.getOperationalPolicy(POLICY_ID, "1"));
-            assertNotNull(dummyProvider.createOperationalPolicy(new LegacyOperationalPolicy()));
-            assertNotNull(dummyProvider.updateOperationalPolicy(new LegacyOperationalPolicy()));
-            assertNotNull(dummyProvider.deleteOperationalPolicy(POLICY_ID, "1"));
+        assertNotNull(dummyProvider.getOperationalPolicy(POLICY_ID, "1"));
+        assertNotNull(dummyProvider.createOperationalPolicy(new LegacyOperationalPolicy()));
+        assertNotNull(dummyProvider.updateOperationalPolicy(new LegacyOperationalPolicy()));
+        assertNotNull(dummyProvider.deleteOperationalPolicy(POLICY_ID, "1"));
 
-            assertNotNull(dummyProvider.getGuardPolicy(POLICY_ID, "1"));
-            assertNotNull(dummyProvider.createGuardPolicy(new LegacyGuardPolicyInput()));
-            assertNotNull(dummyProvider.updateGuardPolicy(new LegacyGuardPolicyInput()));
-            assertNotNull(dummyProvider.deleteGuardPolicy(POLICY_ID, "1"));
+        assertNotNull(dummyProvider.getGuardPolicy(POLICY_ID, "1"));
+        assertNotNull(dummyProvider.createGuardPolicy(new LegacyGuardPolicyInput()));
+        assertNotNull(dummyProvider.updateGuardPolicy(new LegacyGuardPolicyInput()));
+        assertNotNull(dummyProvider.deleteGuardPolicy(POLICY_ID, "1"));
 
-            assertTrue(dummyProvider.getPdpGroups("name").isEmpty());
-            assertTrue(dummyProvider.getFilteredPdpGroups(PdpGroupFilter.builder().build()).isEmpty());
-            assertTrue(dummyProvider.createPdpGroups(new ArrayList<>()).isEmpty());
-            assertTrue(dummyProvider.updatePdpGroups(new ArrayList<>()).isEmpty());
-            assertNull(dummyProvider.deletePdpGroup("name"));
+        assertTrue(dummyProvider.getPdpGroups("name").isEmpty());
+        assertTrue(dummyProvider.getFilteredPdpGroups(PdpGroupFilter.builder().build()).isEmpty());
+        assertTrue(dummyProvider.createPdpGroups(new ArrayList<>()).isEmpty());
+        assertTrue(dummyProvider.updatePdpGroups(new ArrayList<>()).isEmpty());
+        assertNull(dummyProvider.deletePdpGroup("name"));
 
-            dummyProvider.updatePdpSubGroup("name", new PdpSubGroup());
-            dummyProvider.updatePdp("name", "type", new Pdp());
-            dummyProvider.updatePdpStatistics(new ArrayList<>());
-            assertTrue(dummyProvider.getPdpStatistics("name", null).isEmpty());
-        }
+        dummyProvider.updatePdpSubGroup("name", new PdpSubGroup());
+        dummyProvider.updatePdp("name", "type", new Pdp());
+        dummyProvider.updatePdpStatistics(new ArrayList<>());
+        assertTrue(dummyProvider.getPdpStatistics("name", null).isEmpty());
     }
 
     @Test
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyGuardPersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyGuardPersistenceTest.java
index 83665b0..ecb50cd 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyGuardPersistenceTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyGuardPersistenceTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@
     }
 
     @Test
-    public void testPolicyPersistence() throws Exception {
+    public void testLegacyGuardPolicyPersistence() throws Exception {
         for (int i = 0; i < policyInputResourceNames.length; i++) {
             String policyInputString = ResourceUtils.getResourceAsString(policyInputResourceNames[i]);
             String policyOutputString = ResourceUtils.getResourceAsString(policyOutputResourceNames[i]);
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java
index 29e3b5a..30428b0 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java
@@ -1,6 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
+ *  Modifications Copyright (C) 2020 AT&T.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,15 +56,15 @@
 
     // @formatter:off
     private String[] policyInputResourceNames = {
-        "policies/vCPE.policy.operational.input.json",
-        "policies/vDNS.policy.operational.input.json",
-        "policies/vFirewall.policy.operational.input.json"
+        "policies/vCPE.policy.operational.legacy.input.json",
+        "policies/vDNS.policy.operational.legacy.input.json",
+        "policies/vFirewall.policy.operational.legacy.input.json"
     };
 
     private String[] policyOutputResourceNames = {
-        "policies/vCPE.policy.operational.output.json",
-        "policies/vDNS.policy.operational.output.json",
-        "policies/vFirewall.policy.operational.output.json"
+        "policies/vCPE.policy.operational.legacy.output.json",
+        "policies/vDNS.policy.operational.legacy.output.json",
+        "policies/vFirewall.policy.operational.legacy.output.json"
     };
     // @formatter:on
 
@@ -103,7 +104,7 @@
     }
 
     @Test
-    public void testPolicyPersistence() throws Exception {
+    public void testLegacyOperationalPolicyPersistence() throws Exception {
         for (int i = 0; i < policyInputResourceNames.length; i++) {
             String policyInputString = ResourceUtils.getResourceAsString(policyInputResourceNames[i]);
             String policyOutputString = ResourceUtils.getResourceAsString(policyOutputResourceNames[i]);
@@ -148,8 +149,8 @@
     private void createPolicyTypes() throws CoderException, PfModelException, URISyntaxException {
         Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes");
 
-        for (String policyTyoeResource : policyTypeResources) {
-            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTyoeResource));
+        for (String policyTypeResource : policyTypeResources) {
+            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource));
             String yamlAsJsonString = new StandardCoder().encode(yamlObject);
 
             ToscaServiceTemplate toscaServiceTemplatePolicyType =
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java
index f419681..3116868 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
 import org.junit.Test;
 import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.YamlJsonTranslator;
 import org.onap.policy.common.utils.resources.ResourceUtils;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.provider.PolicyModelsProvider;
@@ -44,7 +45,8 @@
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
-import org.yaml.snakeyaml.Yaml;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Test persistence of monitoring policies to and from the database.
@@ -52,7 +54,10 @@
  * @author Liam Fallon (liam.fallon@est.tech)
  */
 public class PolicyToscaPersistenceTest {
-    private StandardCoder standardCoder;
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyToscaPersistenceTest.class);
+
+    private YamlJsonTranslator yamlJsonTranslator = new YamlJsonTranslator();
+    private StandardCoder standardCoder = new StandardCoder();
 
     private PolicyModelsProvider databaseProvider;
 
@@ -78,21 +83,13 @@
         createPolicyTypes();
     }
 
-    /**
-     * Set up standard coder.
-     */
-    @Before
-    public void setupStandardCoder() {
-        standardCoder = new StandardCoder();
-    }
-
     @After
     public void teardown() throws Exception {
         databaseProvider.close();
     }
 
     @Test
-    public void testPolicyPersistence() throws Exception {
+    public void testToscaPolicyPersistence() throws Exception {
         Set<String> policyResources = ResourceUtils.getDirectoryContents("policies");
 
         for (String policyResource : policyResources) {
@@ -103,29 +100,44 @@
             String policyString = ResourceUtils.getResourceAsString(policyResource);
 
             if (policyResource.endsWith("yaml")) {
-                testYamlStringPolicyPersistence(policyString);
+                testPolicyPersistence(yamlJsonTranslator.fromYaml(policyString, ToscaServiceTemplate.class));
             } else {
-                testJsonStringPolicyPersistence(policyString);
+                testPolicyPersistence(standardCoder.decode(policyString, ToscaServiceTemplate.class));
             }
         }
     }
 
-    private void testYamlStringPolicyPersistence(final String policyString) throws Exception {
-        Object yamlObject = new Yaml().load(policyString);
-        String yamlAsJsonString = new StandardCoder().encode(yamlObject);
+    @Test
+    public void testNamingPolicyGet() throws PfModelException {
+        String policyYamlString = ResourceUtils.getResourceAsString("policies/sdnc.policy.naming.input.tosca.yaml");
+        ToscaServiceTemplate serviceTemplate =
+                yamlJsonTranslator.fromYaml(policyYamlString, ToscaServiceTemplate.class);
 
-        testJsonStringPolicyPersistence(yamlAsJsonString);
+        long createStartTime = System.currentTimeMillis();
+        databaseProvider.createPolicies(serviceTemplate);
+        LOGGER.trace("Naming policy create time (ms): {}", System.currentTimeMillis() - createStartTime);
+
+        long getStartTime = System.currentTimeMillis();
+        ToscaServiceTemplate namingServiceTemplate =
+                databaseProvider.getPolicies("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0");
+        LOGGER.trace("Naming policy get time (ms): {}", System.currentTimeMillis() - getStartTime);
+
+        assertEquals(1, namingServiceTemplate.getToscaTopologyTemplate().getPoliciesAsMap().size());
+        assertEquals(1, namingServiceTemplate.getPolicyTypesAsMap().size());
+        assertEquals(3, namingServiceTemplate.getDataTypesAsMap().size());
+
+        long deleteStartTime = System.currentTimeMillis();
+        databaseProvider.deletePolicy("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0");
+        LOGGER.trace("Naming policy delete time (ms): {}", System.currentTimeMillis() - deleteStartTime);
     }
 
     /**
      * Check persistence of a policy.
      *
-     * @param policyString the policy as a string
+     * @param serviceTemplate the service template containing the policy
      * @throws Exception any exception thrown
      */
-    public void testJsonStringPolicyPersistence(@NonNull final String policyString) throws Exception {
-        ToscaServiceTemplate serviceTemplate = standardCoder.decode(policyString, ToscaServiceTemplate.class);
-
+    public void testPolicyPersistence(@NonNull final ToscaServiceTemplate serviceTemplate) throws Exception {
         assertNotNull(serviceTemplate);
 
         databaseProvider.createPolicies(serviceTemplate);
@@ -170,11 +182,9 @@
         Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes");
 
         for (String policyTypeResource : policyTypeResources) {
-            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource));
-            String yamlAsJsonString = new StandardCoder().encode(yamlObject);
-
+            String policyTypeYamlString = ResourceUtils.getResourceAsString(policyTypeResource);
             ToscaServiceTemplate toscaServiceTemplatePolicyType =
-                    standardCoder.decode(yamlAsJsonString, ToscaServiceTemplate.class);
+                    yamlJsonTranslator.fromYaml(policyTypeYamlString, ToscaServiceTemplate.class);
 
             assertNotNull(toscaServiceTemplatePolicyType);
             databaseProvider.createPolicyTypes(toscaServiceTemplatePolicyType);
diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyTypePersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyTypePersistenceTest.java
index 0ecf8a0..2272218 100644
--- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyTypePersistenceTest.java
+++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyTypePersistenceTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,29 +20,24 @@
 
 package org.onap.policy.models.provider.impl;
 
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.Base64;
-import java.util.List;
 import java.util.Set;
 
-import lombok.NonNull;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.YamlJsonTranslator;
 import org.onap.policy.common.utils.resources.ResourceUtils;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.provider.PolicyModelsProvider;
 import org.onap.policy.models.provider.PolicyModelsProviderFactory;
 import org.onap.policy.models.provider.PolicyModelsProviderParameters;
-import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
-import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeFilter;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaEntityKey;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
-import org.yaml.snakeyaml.Yaml;
 
 /**
  * Test persistence of monitoring policies to and from the database.
@@ -50,8 +45,7 @@
  * @author Liam Fallon (liam.fallon@est.tech)
  */
 public class PolicyTypePersistenceTest {
-    private StandardCoder standardCoder;
-
+    private YamlJsonTranslator yamlTranslator = new YamlJsonTranslator();
     private PolicyModelsProvider databaseProvider;
 
     /**
@@ -73,14 +67,6 @@
         databaseProvider = new PolicyModelsProviderFactory().createPolicyModelsProvider(parameters);
     }
 
-    /**
-     * Set up standard coder.
-     */
-    @Before
-    public void setupStandardCoder() {
-        standardCoder = new StandardCoder();
-    }
-
     @After
     public void teardown() throws Exception {
         databaseProvider.close();
@@ -90,60 +76,54 @@
     public void testPolicyTypePersistence() throws Exception {
         Set<String> policyTypeDirectoryContents = ResourceUtils.getDirectoryContents("policytypes");
 
+        ToscaServiceTemplate serviceTemplate = new ToscaServiceTemplate();
+
         for (String policyTypeFilePath : policyTypeDirectoryContents) {
             String policyTypeString = ResourceUtils.getResourceAsString(policyTypeFilePath);
-            testYamlStringPolicyTypePersistence(policyTypeString);
+
+            ToscaServiceTemplate foundPolicyTypeSt =
+                    yamlTranslator.fromYaml(policyTypeString, ToscaServiceTemplate.class);
+
+            serviceTemplate.setDerivedFrom(foundPolicyTypeSt.getDerivedFrom());
+            serviceTemplate.setDescription(foundPolicyTypeSt.getDescription());
+            serviceTemplate.setMetadata(foundPolicyTypeSt.getMetadata());
+            serviceTemplate.setName(foundPolicyTypeSt.getName());
+            serviceTemplate.setToscaDefinitionsVersion(foundPolicyTypeSt.getToscaDefinitionsVersion());
+            serviceTemplate.setToscaTopologyTemplate(foundPolicyTypeSt.getToscaTopologyTemplate());
+            serviceTemplate.setVersion(foundPolicyTypeSt.getVersion());
+
+            if (foundPolicyTypeSt.getDataTypes() != null) {
+                if (serviceTemplate.getDataTypes() == null) {
+                    serviceTemplate.setDataTypes(foundPolicyTypeSt.getDataTypes());
+                } else {
+                    serviceTemplate.getDataTypes().putAll(foundPolicyTypeSt.getDataTypes());
+                }
+            }
+
+            if (serviceTemplate.getPolicyTypes() == null) {
+                serviceTemplate.setPolicyTypes(foundPolicyTypeSt.getPolicyTypes());
+            } else {
+                serviceTemplate.getPolicyTypes().putAll(foundPolicyTypeSt.getPolicyTypes());
+            }
         }
-    }
 
-    private void testYamlStringPolicyTypePersistence(final String policyTypeString) throws Exception {
-        Object yamlObject = new Yaml().load(policyTypeString);
-        String yamlAsJsonString = new StandardCoder().encode(yamlObject);
+        assertThatCode(() -> databaseProvider.createPolicyTypes(serviceTemplate)).doesNotThrowAnyException();
 
-        testJsonStringPolicyTypePersistence(yamlAsJsonString);
-    }
+        ToscaEntityKey resourceOptimizationPtKey =
+                new ToscaEntityKey("onap.policies.optimization.resource.OptimizationPolicy", "1.0.0");
 
-    /**
-     * Check persistence of a policy.
-     *
-     * @param policyTypeString the policy as a string
-     * @throws Exception any exception thrown
-     */
-    public void testJsonStringPolicyTypePersistence(@NonNull final String policyTypeString) throws Exception {
-        ToscaServiceTemplate serviceTemplate = standardCoder.decode(policyTypeString, ToscaServiceTemplate.class);
+        ToscaServiceTemplate resOptPolicyTypeSt = databaseProvider.getPolicyTypes(resourceOptimizationPtKey.getName(),
+                resourceOptimizationPtKey.getVersion());
 
-        assertNotNull(serviceTemplate);
-        ToscaPolicyType inPolicyType = serviceTemplate.getPolicyTypes().values().iterator().next();
+        assertEquals(3, resOptPolicyTypeSt.getPolicyTypesAsMap().size());
+        assertTrue(resOptPolicyTypeSt.getPolicyTypesAsMap().containsKey(resourceOptimizationPtKey));
 
-        databaseProvider.createPolicyTypes(serviceTemplate);
-        checkPolicyTypePersistence(inPolicyType);
+        ToscaEntityKey resourcePtKey = new ToscaEntityKey("onap.policies.optimization.Resource", "1.0.0");
+        assertTrue(resOptPolicyTypeSt.getPolicyTypesAsMap().containsKey(resourcePtKey));
 
-        databaseProvider.updatePolicyTypes(serviceTemplate);
-        checkPolicyTypePersistence(inPolicyType);
-    }
+        ToscaEntityKey optimizationPtKey = new ToscaEntityKey("onap.policies.Optimization", "1.0.0");
+        assertTrue(resOptPolicyTypeSt.getPolicyTypesAsMap().containsKey(optimizationPtKey));
 
-    private void checkPolicyTypePersistence(ToscaPolicyType inPolicyType) throws PfModelException {
-        List<ToscaPolicyType> policyTypeList =
-                databaseProvider.getPolicyTypeList(inPolicyType.getName(), inPolicyType.getVersion());
-
-        policyTypeList = databaseProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder()
-                .name(inPolicyType.getName()).version(inPolicyType.getVersion()).build());
-
-        assertEquals(1, policyTypeList.size());
-        assertEquals(inPolicyType.getName(), policyTypeList.get(0).getName());
-
-        policyTypeList = databaseProvider
-                .getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().name(inPolicyType.getName()).build());
-
-        assertEquals(1, policyTypeList.size());
-        assertEquals(inPolicyType.getName(), policyTypeList.get(0).getName());
-
-        policyTypeList = databaseProvider.getFilteredPolicyTypeList(ToscaPolicyTypeFilter.builder().build());
-        assertTrue(policyTypeList.size() <= 3);
-        assertEquals(inPolicyType.getName(), policyTypeList.get(0).getName());
-
-        for (ToscaPolicyType policyType : databaseProvider.getPolicyTypeList(null, null)) {
-            databaseProvider.deletePolicyType(policyType.getName(), policyType.getVersion());
-        }
+        assertEquals(2, resOptPolicyTypeSt.getDataTypesAsMap().size());
     }
 }
diff --git a/models-provider/src/test/resources/META-INF/persistence.xml b/models-provider/src/test/resources/META-INF/persistence.xml
index 4306413..d63c415 100644
--- a/models-provider/src/test/resources/META-INF/persistence.xml
+++ b/models-provider/src/test/resources/META-INF/persistence.xml
@@ -27,8 +27,15 @@
         <class>org.onap.policy.models.dao.converters.Uuid2String</class>
         <class>org.onap.policy.models.base.PfConceptKey</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaDataType</class>
-        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaDataTypes</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicies</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicy</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyTypes</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTopologyTemplate</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class>
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpGroup</class>
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class>
         <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class>
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java
index 7999f62..a65cdbe 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java
@@ -22,15 +22,19 @@
 package org.onap.policy.models.tosca.authorative.provider;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
+import javax.ws.rs.core.Response.Status;
+
 import lombok.NonNull;
 
 import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.base.PfModelException;
+import org.onap.policy.models.base.PfModelRuntimeException;
 import org.onap.policy.models.dao.PfDao;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaEntity;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
@@ -87,8 +91,14 @@
 
         LOGGER.debug("->getPolicyTypeList: name={}, version={}", name, version);
 
-        List<ToscaPolicyType> policyTypeList = new ArrayList<>(
-                new SimpleToscaProvider().getPolicyTypes(dao, name, version).toAuthorative().getPolicyTypes().values());
+        List<ToscaPolicyType> policyTypeList;
+
+        try {
+            policyTypeList = new ArrayList<>(new SimpleToscaProvider().getPolicyTypes(dao, name, version)
+                    .toAuthorative().getPolicyTypes().values());
+        } catch (PfModelRuntimeException pfme) {
+            return handlePfModelRuntimeException(pfme);
+        }
 
         LOGGER.debug("<-getPolicyTypeList: name={}, version={}, policyTypeList={}", name, version, policyTypeList);
         return policyTypeList;
@@ -136,6 +146,7 @@
 
         LOGGER.debug("<-getFilteredPolicyTypeList: filter={}, filteredPolicyTypeList={}", filter,
                 filteredPolicyTypeList);
+
         return filteredPolicyTypeList;
     }
 
@@ -234,8 +245,14 @@
             throws PfModelException {
         LOGGER.debug("->getPolicyList: name={}, version={}", name, version);
 
-        List<ToscaPolicy> policyList = asConceptList(new SimpleToscaProvider().getPolicies(dao, name, version)
-                .toAuthorative().getToscaTopologyTemplate().getPolicies());
+        List<ToscaPolicy> policyList;
+
+        try {
+            policyList = asConceptList(new SimpleToscaProvider().getPolicies(dao, name, version).toAuthorative()
+                    .getToscaTopologyTemplate().getPolicies());
+        } catch (PfModelRuntimeException pfme) {
+            return handlePfModelRuntimeException(pfme);
+        }
 
         LOGGER.debug("<-getPolicyList: name={}, version={}, policyTypeList={}", name, version, policyList);
         return policyList;
@@ -397,4 +414,19 @@
 
         return conceptMap;
     }
+
+    /**
+     * Handle a PfModelRuntimeException on a list call.
+     *
+     * @param pfme the model exception
+     * @return an empty list on 404
+     */
+    private <T extends ToscaEntity> List<T> handlePfModelRuntimeException(final PfModelRuntimeException pfme) {
+        if (Status.NOT_FOUND.equals(pfme.getErrorResponse().getResponseCode())) {
+            LOGGER.trace("request did not find any results", pfme);
+            return Collections.emptyList();
+        } else {
+            throw pfme;
+        }
+    }
 }
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java
index f5335fe..09cc6c0 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java
@@ -93,13 +93,13 @@
 
         LOGGER.debug("->createOperationalPolicy: legacyOperationalPolicy={}", legacyOperationalPolicy);
 
-        JpaToscaServiceTemplate incomingServiceTemplate =
+        JpaToscaServiceTemplate legacyOperationalServiceTemplate =
                 new LegacyOperationalPolicyMapper().toToscaServiceTemplate(legacyOperationalPolicy);
-        JpaToscaServiceTemplate outgoingingServiceTemplate =
-                new SimpleToscaProvider().createPolicies(dao, incomingServiceTemplate);
+
+        new SimpleToscaProvider().createPolicies(dao, legacyOperationalServiceTemplate);
 
         LegacyOperationalPolicy createdLegacyOperationalPolicy =
-                new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(outgoingingServiceTemplate);
+                new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(legacyOperationalServiceTemplate);
 
         LOGGER.debug("<-createOperationalPolicy: createdLegacyOperationalPolicy={}", createdLegacyOperationalPolicy);
         return createdLegacyOperationalPolicy;
@@ -223,7 +223,6 @@
         return updatedLegacyGuardPolicyMap;
     }
 
-
     /**
      * Delete legacy guard policy.
      *
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataType.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataType.java
index 86d67e4..a44d765 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataType.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataType.java
@@ -3,7 +3,7 @@
  * ONAP Policy Model
  * ================================================================================
  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
- * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,10 +24,13 @@
 package org.onap.policy.models.tosca.simple.concepts;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.persistence.ElementCollection;
 import javax.persistence.Entity;
@@ -40,6 +43,7 @@
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
 
+import org.apache.commons.collections4.CollectionUtils;
 import org.onap.policy.models.base.PfAuthorative;
 import org.onap.policy.models.base.PfConcept;
 import org.onap.policy.models.base.PfConceptKey;
@@ -52,6 +56,7 @@
 import org.onap.policy.models.tosca.authorative.concepts.ToscaConstraint;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
+import org.onap.policy.models.tosca.utils.ToscaUtils;
 
 /**
  * Class to represent custom data type in TOSCA definition.
@@ -68,11 +73,11 @@
     private static final long serialVersionUID = -3922690413436539164L;
 
     @ElementCollection
-    private List<JpaToscaConstraint> constraints;
+    private List<JpaToscaConstraint> constraints = new ArrayList<>();
 
     @ElementCollection
     @Lob
-    private Map<String, JpaToscaProperty> properties;
+    private Map<String, JpaToscaProperty> properties = new LinkedHashMap<>();
 
     /**
      * The Default Constructor creates a {@link JpaToscaDataType} object with a null key.
@@ -268,4 +273,29 @@
 
         return 0;
     }
+
+    /**
+     * Get the data types referenced in a data type.
+     *
+     * @return the data types referenced in a data type
+     */
+    public Collection<PfConceptKey> getReferencedDataTypes() {
+        if (properties == null) {
+            return CollectionUtils.emptyCollection();
+        }
+
+        Set<PfConceptKey> referencedDataTypes = new LinkedHashSet<>();
+
+        for (JpaToscaProperty property : properties.values()) {
+            referencedDataTypes.add(property.getType());
+
+            if (property.getEntrySchema() != null) {
+                referencedDataTypes.add(property.getEntrySchema().getType());
+            }
+        }
+
+        referencedDataTypes.removeAll(ToscaUtils.getPredefinedDataTypes());
+
+        return referencedDataTypes;
+    }
 }
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaEntityType.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaEntityType.java
index 4823efc..6544e72 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaEntityType.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaEntityType.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,15 +26,18 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
+
 import javax.persistence.AttributeOverride;
 import javax.persistence.AttributeOverrides;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
 import javax.persistence.EmbeddedId;
 import javax.persistence.MappedSuperclass;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
+
 import org.apache.commons.lang3.ObjectUtils;
 import org.onap.policy.common.utils.validation.ParameterValidationUtils;
 import org.onap.policy.models.base.PfAuthorative;
@@ -70,7 +73,7 @@
     private PfConceptKey derivedFrom;
 
     @ElementCollection
-    private Map<String, String> metadata;
+    private Map<String, String> metadata = new TreeMap<>();
 
     @Column
     private String description;
@@ -154,7 +157,6 @@
             key.setVersion(toscaEntity.getVersion());
         }
 
-
         if (toscaEntity.getDerivedFrom() != null) {
             // CHeck if the derived from field contains a name-version ID
             if (toscaEntity.getDerivedFrom().contains(":")) {
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java
index 36acec0..b500a8b 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java
@@ -3,7 +3,7 @@
  * ONAP Policy Model
  * ================================================================================
  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
- * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,10 +23,12 @@
 
 package org.onap.policy.models.tosca.simple.concepts;
 
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+
 import javax.persistence.AttributeOverride;
 import javax.persistence.AttributeOverrides;
 import javax.persistence.Column;
@@ -37,9 +39,11 @@
 import javax.persistence.Lob;
 import javax.persistence.Table;
 import javax.ws.rs.core.Response;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
+
 import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.common.utils.validation.ParameterValidationUtils;
@@ -87,7 +91,7 @@
     private Map<String, String> properties;
 
     @ElementCollection
-    private List<PfConceptKey> targets;
+    private List<PfConceptKey> targets = new ArrayList<>();
     // @formatter:on
 
     /**
@@ -186,15 +190,13 @@
 
         if (toscaPolicy.getType() != null) {
             type.setName(toscaPolicy.getType());
-        }
-        else {
+        } else {
             type.setName(PfKey.NULL_KEY_NAME);
         }
 
         if (toscaPolicy.getTypeVersion() != null) {
             type.setVersion(toscaPolicy.getTypeVersion());
-        }
-        else {
+        } else {
             type.setVersion(PfKey.NULL_KEY_VERSION);
         }
 
@@ -259,6 +261,11 @@
     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
         PfValidationResult result = super.validate(resultIn);
 
+        if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) {
+            result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
+                    "key version is a null version"));
+        }
+
         if (type == null || type.isNullKey()) {
             result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
                     "type is null or a null key"));
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java
index 43d7ad6..423620d 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java
@@ -23,6 +23,7 @@
 
 package org.onap.policy.models.tosca.simple.concepts;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -73,13 +74,13 @@
 
     @ElementCollection
     @Lob
-    private Map<String, JpaToscaProperty> properties;
+    private Map<String, JpaToscaProperty> properties = new LinkedHashMap<>();
 
     @ElementCollection
-    private List<PfConceptKey> targets;
+    private List<PfConceptKey> targets = new ArrayList<>();
 
     @ElementCollection
-    private List<JpaToscaTrigger> triggers;
+    private List<JpaToscaTrigger> triggers = new ArrayList<>();
 
     /**
      * The Default Constructor creates a {@link JpaToscaPolicyType} object with a null key.
@@ -202,6 +203,11 @@
     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
         PfValidationResult result = super.validate(resultIn);
 
+        if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) {
+            result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
+                    "key version is a null version"));
+        }
+
         if (properties != null) {
             result = validateProperties(result);
         }
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyTypes.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyTypes.java
index 9c059b4..00a6d6b 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyTypes.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyTypes.java
@@ -109,6 +109,7 @@
     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
         PfValidationResult result = super.validate(resultIn);
 
+        // Check that all ancestors of this policy type exist
         for (JpaToscaPolicyType policyType : this.getConceptMap().values()) {
             ToscaUtils.getEntityTypeAncestors(this, policyType, result);
         }
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplate.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplate.java
index 786784c..aa4f231 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplate.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplate.java
@@ -23,6 +23,7 @@
 
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -101,7 +102,15 @@
     @SerializedName("policy_types")
     private JpaToscaPolicyTypes policyTypes;
 
-    @Column
+    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
+    @JoinColumns(
+            {
+                @JoinColumn(name = "topologyTemplateParentKeyName",    referencedColumnName = "parentKeyName"),
+                @JoinColumn(name = "topologyTemplateParentKeyVersion", referencedColumnName = "parentKeyVersion"),
+                @JoinColumn(name = "topologyTemplateParentLocalName",  referencedColumnName = "parentLocalName"),
+                @JoinColumn(name = "topologyTemplateLocalName",        referencedColumnName = "localName")
+            }
+        )
     @SerializedName("topology_template")
     private JpaToscaTopologyTemplate topologyTemplate;
     // @formatter:on
@@ -281,7 +290,7 @@
             return result;
         }
 
-        validateDatatypesInPolicyTypes(result);
+        validateReferencedDataTypes(result);
 
         return validatePolicyTypesInPolicies(result);
     }
@@ -339,36 +348,54 @@
      * @param result the validation result object to use for the validation result
      * @return the validation result object
      */
-    private PfValidationResult validateDatatypesInPolicyTypes(final PfValidationResult result) {
+    private PfValidationResult validateReferencedDataTypes(final PfValidationResult result) {
         if (policyTypes == null) {
             return result;
         }
 
-        for (JpaToscaPolicyType policyType : policyTypes.getAll(null)) {
-            for (PfConceptKey datatypeKey : policyType.getReferencedDataTypes()) {
-                if (dataTypes == null || dataTypes.get(datatypeKey) == null) {
-                    result.addValidationMessage(
-                            new PfValidationMessage(policyType.getKey(), this.getClass(), ValidationResult.INVALID,
-                                    "data type " + datatypeKey + " referenced in policy type not found"));
-                }
+        if (dataTypes != null) {
+            for (JpaToscaDataType dataType : dataTypes.getAll(null)) {
+                validateReferencedDataTypesExists(dataType.getKey(), dataType.getReferencedDataTypes(), result);
             }
         }
 
+        for (JpaToscaPolicyType policyType : policyTypes.getAll(null)) {
+            validateReferencedDataTypesExists(policyType.getKey(), policyType.getReferencedDataTypes(), result);
+        }
+
         return result;
     }
 
     /**
+     * Validate that the referenced data types exist for a collection of data type keys.
+     *
+     * @param referencingEntityKey the key of the referencing entity
+     * @param dataTypeKeyCollection the data type key collection
+     * @param result the result of the validation
+     */
+    private void validateReferencedDataTypesExists(final PfConceptKey referencingEntityKey,
+            final Collection<PfConceptKey> dataTypeKeyCollection, final PfValidationResult result) {
+        for (PfConceptKey dataTypeKey : dataTypeKeyCollection) {
+            if (dataTypes == null || dataTypes.get(dataTypeKey) == null) {
+                result.addValidationMessage(new PfValidationMessage(referencingEntityKey, this.getClass(),
+                        ValidationResult.INVALID, "referenced data type " + dataTypeKey.getId() + " not found"));
+            }
+        }
+    }
+
+    /**
      * Validate that all policy types referenced in policies exist.
      *
      * @param result the validation result object to use for the validation result
      * @return the validation result object
      */
     private PfValidationResult validatePolicyTypesInPolicies(PfValidationResult result) {
-        if (topologyTemplate == null || topologyTemplate.getPolicies() == null) {
+        if (topologyTemplate == null || topologyTemplate.getPolicies() == null
+                || topologyTemplate.getPolicies().getConceptMap().isEmpty()) {
             return result;
         }
 
-        if (policyTypes == null) {
+        if (policyTypes == null || policyTypes.getConceptMap().isEmpty()) {
             result.addValidationMessage(new PfValidationMessage(this.getKey(), this.getClass(),
                     ValidationResult.INVALID,
                     "no policy types are defined on the service template for the policies in the topology template"));
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaTopologyTemplate.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaTopologyTemplate.java
index c8dfa5a..1766087 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaTopologyTemplate.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaTopologyTemplate.java
@@ -30,6 +30,8 @@
 import javax.persistence.FetchType;
 import javax.persistence.Inheritance;
 import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
 import javax.persistence.OneToOne;
 import javax.persistence.Table;
 
@@ -69,7 +71,15 @@
     @Column(name = "description")
     private String description;
 
+    // @formatter:off
     @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
+    @JoinColumns(
+            {
+                @JoinColumn(name = "policyName",    referencedColumnName = "name"),
+                @JoinColumn(name = "policyVersion", referencedColumnName = "version")
+            }
+        )
+    // @formatter:on
     private JpaToscaPolicies policies;
 
     /**
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java
index 508b470..c537bbc 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java
@@ -21,9 +21,8 @@
 package org.onap.policy.models.tosca.simple.provider;
 
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
+import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 import javax.ws.rs.core.Response;
 
@@ -36,6 +35,7 @@
 import org.onap.policy.models.base.PfKey;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.base.PfModelRuntimeException;
+import org.onap.policy.models.base.PfValidationResult;
 import org.onap.policy.models.dao.PfDao;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaDataType;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaDataTypes;
@@ -44,7 +44,7 @@
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyTypes;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
-import org.onap.policy.models.tosca.simple.concepts.JpaToscaTopologyTemplate;
+import org.onap.policy.models.tosca.utils.ToscaServiceTemplateUtils;
 import org.onap.policy.models.tosca.utils.ToscaUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,6 +57,64 @@
 public class SimpleToscaProvider {
     private static final Logger LOGGER = LoggerFactory.getLogger(SimpleToscaProvider.class);
 
+    // Recurring string constants
+    private static final String SERVICE_TEMPLATE_NOT_FOUND_IN_DATABASE = "service template not found in database";
+    private static final String DO_NOT_EXIST = " do not exist";
+
+    /**
+     * Get Service Template.
+     *
+     * @param dao the DAO to use to access the database
+     * @return the service template
+     * @throws PfModelException on errors getting the service template
+     */
+    public JpaToscaServiceTemplate getServiceTemplate(@NonNull final PfDao dao) throws PfModelException {
+        LOGGER.debug("->getServiceTemplate");
+
+        JpaToscaServiceTemplate serviceTemplate = new SimpleToscaServiceTemplateProvider().read(dao);
+        if (serviceTemplate == null) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND, SERVICE_TEMPLATE_NOT_FOUND_IN_DATABASE);
+        }
+
+        LOGGER.debug("<-getServiceTemplate: serviceTemplate={}", serviceTemplate);
+        return serviceTemplate;
+    }
+
+    /**
+     * Append a service template fragment to the service template in the database.
+     *
+     * @param dao the DAO to use to access the database
+     * @param incomingServiceTemplateFragment the service template containing the definition of the entities to be
+     *        created
+     * @return the TOSCA service template in the database after the operation
+     * @throws PfModelException on errors appending a service template to the template in the database
+     */
+    public JpaToscaServiceTemplate appendToServiceTemplate(@NonNull final PfDao dao,
+            @NonNull final JpaToscaServiceTemplate incomingServiceTemplateFragment) throws PfModelException {
+        LOGGER.debug("->appendServiceTemplateFragment: incomingServiceTemplateFragment={}",
+                incomingServiceTemplateFragment);
+
+        JpaToscaServiceTemplate dbServiceTemplate = new SimpleToscaServiceTemplateProvider().read(dao);
+
+        JpaToscaServiceTemplate serviceTemplateToWrite;
+        if (dbServiceTemplate == null) {
+            serviceTemplateToWrite = incomingServiceTemplateFragment;
+        } else {
+            serviceTemplateToWrite =
+                    ToscaServiceTemplateUtils.addFragment(dbServiceTemplate, incomingServiceTemplateFragment);
+        }
+
+        PfValidationResult result = serviceTemplateToWrite.validate(new PfValidationResult());
+        if (!result.isValid()) {
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
+        }
+
+        new SimpleToscaServiceTemplateProvider().write(dao, serviceTemplateToWrite);
+
+        LOGGER.debug("<-appendServiceTemplateFragment: returnServiceTempalate={}", serviceTemplateToWrite);
+        return serviceTemplateToWrite;
+    }
+
     /**
      * Get data types.
      *
@@ -70,13 +128,34 @@
             throws PfModelException {
         LOGGER.debug("->getDataTypes: name={}, version={}", name, version);
 
-        // Create the structure of the TOSCA service template to contain the data type
-        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate();
-        serviceTemplate.setDataTypes(new JpaToscaDataTypes());
+        JpaToscaServiceTemplate serviceTemplate = getServiceTemplate(dao);
 
-        // Add the data type to the TOSCA service template
-        List<JpaToscaDataType> jpaDataTypeList = dao.getFiltered(JpaToscaDataType.class, name, version);
-        serviceTemplate.getDataTypes().getConceptMap().putAll(asConceptMap(jpaDataTypeList));
+        if (!ToscaUtils.doDataTypesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "data types for " + name + ":" + version + DO_NOT_EXIST);
+        }
+
+        serviceTemplate.setPolicyTypes(null);
+        serviceTemplate.setTopologyTemplate(null);
+
+        ToscaUtils.getEntityTree(serviceTemplate.getDataTypes(), name, version);
+
+        if (!ToscaUtils.doDataTypesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "data types for " + name + ":" + version + DO_NOT_EXIST);
+        }
+
+        for (JpaToscaDataType dataType : serviceTemplate.getDataTypes().getConceptMap().values()) {
+            Collection<PfConceptKey> referencedDataTypeKeys = dataType.getReferencedDataTypes();
+
+            for (PfConceptKey referencedDataTypeKey : referencedDataTypeKeys) {
+                JpaToscaServiceTemplate dataTypeEntityTreeServiceTemplate =
+                        getDataTypes(dao, referencedDataTypeKey.getName(), referencedDataTypeKey.getVersion());
+
+                serviceTemplate =
+                        ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeEntityTreeServiceTemplate);
+            }
+        }
 
         LOGGER.debug("<-getDataTypes: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
         return serviceTemplate;
@@ -86,32 +165,20 @@
      * Create data types.
      *
      * @param dao the DAO to use to access the database
-     * @param serviceTemplate the service template containing the definition of the data types to be created
+     * @param incomingServiceTemplate the service template containing the definition of the data types to be created
      * @return the TOSCA service template containing the created data types
      * @throws PfModelException on errors creating data types
      */
     public JpaToscaServiceTemplate createDataTypes(@NonNull final PfDao dao,
-            @NonNull final JpaToscaServiceTemplate serviceTemplate) throws PfModelException {
-        LOGGER.debug("->createDataTypes: serviceTempalate={}", serviceTemplate);
+            @NonNull final JpaToscaServiceTemplate incomingServiceTemplate) throws PfModelException {
+        LOGGER.debug("->createDataTypes: incomingServiceTemplate={}", incomingServiceTemplate);
 
-        ToscaUtils.assertDataTypesExist(serviceTemplate);
+        ToscaUtils.assertDataTypesExist(incomingServiceTemplate);
 
-        for (JpaToscaDataType dataType : serviceTemplate.getDataTypes().getAll(null)) {
-            dao.create(dataType);
-        }
+        JpaToscaServiceTemplate writtenServiceTemplate = appendToServiceTemplate(dao, incomingServiceTemplate);
 
-        // Return the created Data types
-        JpaToscaDataTypes returnDataTypes = new JpaToscaDataTypes();
-
-        for (PfConceptKey dataTypeKey : serviceTemplate.getDataTypes().getConceptMap().keySet()) {
-            returnDataTypes.getConceptMap().put(dataTypeKey, dao.get(JpaToscaDataType.class, dataTypeKey));
-        }
-
-        JpaToscaServiceTemplate returnServiceTemplate = new JpaToscaServiceTemplate();
-        returnServiceTemplate.setDataTypes(returnDataTypes);
-
-        LOGGER.debug("<-createDataTypes: returnServiceTempalate={}", returnServiceTemplate);
-        return returnServiceTemplate;
+        LOGGER.debug("<-createDataTypes: returnServiceTempalate={}", writtenServiceTemplate);
+        return writtenServiceTemplate;
     }
 
     /**
@@ -180,22 +247,40 @@
             throws PfModelException {
         LOGGER.debug("->getPolicyTypes: name={}, version={}", name, version);
 
-        // Create the structure of the TOSCA service template to contain the policy type
-        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate();
-        serviceTemplate.setPolicyTypes(new JpaToscaPolicyTypes());
+        JpaToscaServiceTemplate serviceTemplate = getServiceTemplate(dao);
 
-        // Add the policy type to the TOSCA service template
-        List<JpaToscaPolicyType> jpaPolicyTypeList = dao.getFiltered(JpaToscaPolicyType.class, name, version);
-        serviceTemplate.getPolicyTypes().getConceptMap().putAll(asConceptMap(jpaPolicyTypeList));
+        serviceTemplate.setDataTypes(null);
+        serviceTemplate.setTopologyTemplate(null);
 
-        // Return all data types
-        // TODO: In an upcoming review, return just the data types used by the policy types on the policy type list
-        List<JpaToscaDataType> jpaDataTypeList = dao.getFiltered(JpaToscaDataType.class, null, null);
-        if (!CollectionUtils.isEmpty(jpaDataTypeList)) {
-            serviceTemplate.setDataTypes(new JpaToscaDataTypes());
-            serviceTemplate.getDataTypes().getConceptMap().putAll(asConceptMap(jpaDataTypeList));
+        if (!ToscaUtils.doPolicyTypesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "policy types for " + name + ":" + version + DO_NOT_EXIST);
         }
 
+        ToscaUtils.getEntityTree(serviceTemplate.getPolicyTypes(), name, version);
+
+        if (!ToscaUtils.doPolicyTypesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "policy types for " + name + ":" + version + DO_NOT_EXIST);
+        }
+
+        JpaToscaServiceTemplate dataTypeServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate);
+        dataTypeServiceTemplate.setPolicyTypes(null);
+
+        for (JpaToscaPolicyType policyType : serviceTemplate.getPolicyTypes().getConceptMap().values()) {
+            Collection<PfConceptKey> referencedDataTypeKeys = policyType.getReferencedDataTypes();
+
+            for (PfConceptKey referencedDataTypeKey : referencedDataTypeKeys) {
+                JpaToscaServiceTemplate dataTypeEntityTreeServiceTemplate =
+                        getDataTypes(dao, referencedDataTypeKey.getName(), referencedDataTypeKey.getVersion());
+
+                dataTypeServiceTemplate = ToscaServiceTemplateUtils.addFragment(dataTypeServiceTemplate,
+                        dataTypeEntityTreeServiceTemplate);
+            }
+        }
+
+        serviceTemplate = ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeServiceTemplate);
+
         LOGGER.debug("<-getPolicyTypes: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
         return serviceTemplate;
     }
@@ -204,37 +289,20 @@
      * Create policy types.
      *
      * @param dao the DAO to use to access the database
-     * @param serviceTemplate the service template containing the definition of the policy types to be created
+     * @param incomingServiceTemplate the service template containing the definition of the policy types to be created
      * @return the TOSCA service template containing the created policy types
      * @throws PfModelException on errors creating policy types
      */
     public JpaToscaServiceTemplate createPolicyTypes(@NonNull final PfDao dao,
-            @NonNull final JpaToscaServiceTemplate serviceTemplate) throws PfModelException {
-        LOGGER.debug("->createPolicyTypes: serviceTempalate={}", serviceTemplate);
+            @NonNull final JpaToscaServiceTemplate incomingServiceTemplate) throws PfModelException {
+        LOGGER.debug("->createPolicyTypes: serviceTempalate={}", incomingServiceTemplate);
 
-        ToscaUtils.assertPolicyTypesExist(serviceTemplate);
+        ToscaUtils.assertPolicyTypesExist(incomingServiceTemplate);
 
-        // Create the data types on the policy type
-        if (ToscaUtils.doDataTypesExist(serviceTemplate)) {
-            createDataTypes(dao, serviceTemplate);
-        }
+        JpaToscaServiceTemplate writtenServiceTemplate = appendToServiceTemplate(dao, incomingServiceTemplate);
 
-        for (JpaToscaPolicyType policyType : serviceTemplate.getPolicyTypes().getAll(null)) {
-            dao.create(policyType);
-        }
-
-        // Return the created policy types
-        JpaToscaPolicyTypes returnPolicyTypes = new JpaToscaPolicyTypes();
-
-        for (PfConceptKey policyTypeKey : serviceTemplate.getPolicyTypes().getConceptMap().keySet()) {
-            returnPolicyTypes.getConceptMap().put(policyTypeKey, dao.get(JpaToscaPolicyType.class, policyTypeKey));
-        }
-
-        JpaToscaServiceTemplate returnServiceTemplate = new JpaToscaServiceTemplate();
-        returnServiceTemplate.setPolicyTypes(returnPolicyTypes);
-
-        LOGGER.debug("<-createPolicyTypes: returnServiceTempalate={}", returnServiceTemplate);
-        return returnServiceTemplate;
+        LOGGER.debug("<-createPolicyTypes: returnServiceTempalate={}", writtenServiceTemplate);
+        return writtenServiceTemplate;
     }
 
     /**
@@ -309,50 +377,58 @@
             throws PfModelException {
         LOGGER.debug("->getPolicies: name={}, version={}", name, version);
 
-        // Create the structure of the TOSCA service template to contain the policy type
-        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate();
-        serviceTemplate.setTopologyTemplate(new JpaToscaTopologyTemplate());
-        serviceTemplate.getTopologyTemplate().setPolicies(new JpaToscaPolicies());
+        JpaToscaServiceTemplate dbServiceTemplate = getServiceTemplate(dao);
 
-        // Add the policy type to the TOSCA service template
-        List<JpaToscaPolicy> jpaPolicyList = dao.getFiltered(JpaToscaPolicy.class, name, version);
-        serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().putAll(asConceptMap(jpaPolicyList));
+        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate(dbServiceTemplate);
+        serviceTemplate.setDataTypes(new JpaToscaDataTypes());
+        serviceTemplate.setPolicyTypes(new JpaToscaPolicyTypes());
 
-        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
-        return serviceTemplate;
+        if (!ToscaUtils.doPoliciesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "policies for " + name + ":" + version + DO_NOT_EXIST);
+        }
+
+        ToscaUtils.getEntityTree(serviceTemplate.getTopologyTemplate().getPolicies(), name, version);
+
+        if (!ToscaUtils.doPoliciesExist(serviceTemplate)) {
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
+                    "policies for " + name + ":" + version + DO_NOT_EXIST);
+        }
+
+        JpaToscaServiceTemplate returnServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate);
+        returnServiceTemplate.getTopologyTemplate().setPolicies(new JpaToscaPolicies());
+
+        for (JpaToscaPolicy policy : serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().values()) {
+            JpaToscaServiceTemplate referencedEntitiesServiceTemplate =
+                    getPolicyTypes(dao, policy.getType().getName(), policy.getType().getVersion());
+
+            returnServiceTemplate.getTopologyTemplate().getPolicies().getConceptMap().put(policy.getKey(), policy);
+            returnServiceTemplate =
+                    ToscaServiceTemplateUtils.addFragment(returnServiceTemplate, referencedEntitiesServiceTemplate);
+        }
+
+        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, returnServiceTemplate);
+        return returnServiceTemplate;
     }
 
     /**
      * Create policies.
      *
      * @param dao the DAO to use to access the database
-     * @param serviceTemplate the service template containing the definitions of the new policies to be created.
+     * @param incomingServiceTemplate the service template containing the definitions of the new policies to be created.
      * @return the TOSCA service template containing the policy types that were created
      * @throws PfModelException on errors creating policies
      */
     public JpaToscaServiceTemplate createPolicies(@NonNull final PfDao dao,
-            @NonNull final JpaToscaServiceTemplate serviceTemplate) throws PfModelException {
-        LOGGER.debug("->createPolicies: serviceTempalate={}", serviceTemplate);
+            @NonNull final JpaToscaServiceTemplate incomingServiceTemplate) throws PfModelException {
+        LOGGER.debug("->createPolicies: incomingServiceTemplate={}", incomingServiceTemplate);
 
-        ToscaUtils.assertPoliciesExist(serviceTemplate);
+        ToscaUtils.assertPoliciesExist(incomingServiceTemplate);
 
-        for (JpaToscaPolicy policy : serviceTemplate.getTopologyTemplate().getPolicies().getAll(null)) {
-            verifyPolicyTypeForPolicy(dao, policy);
-            dao.create(policy);
-        }
+        JpaToscaServiceTemplate writtenServiceTemplate = appendToServiceTemplate(dao, incomingServiceTemplate);
 
-        // Return the created policy types
-        JpaToscaPolicies returnPolicies = new JpaToscaPolicies();
-        returnPolicies.setKey(serviceTemplate.getTopologyTemplate().getPolicies().getKey());
-
-        for (PfConceptKey policyKey : serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().keySet()) {
-            returnPolicies.getConceptMap().put(policyKey, dao.get(JpaToscaPolicy.class, policyKey));
-        }
-
-        serviceTemplate.getTopologyTemplate().setPolicies(returnPolicies);
-
-        LOGGER.debug("<-createPolicies: serviceTemplate={}", serviceTemplate);
-        return serviceTemplate;
+        LOGGER.debug("<-createPolicies: serviceTemplate={}", writtenServiceTemplate);
+        return writtenServiceTemplate;
     }
 
     /**
@@ -409,21 +485,6 @@
     }
 
     /**
-     * Convert a list of concepts to a map of concepts.
-     *
-     * @param conceptList the concept list
-     * @return the concept map
-     */
-    private <T extends PfConcept> Map<PfConceptKey, T> asConceptMap(List<T> conceptList) {
-        Map<PfConceptKey, T> conceptMap = new LinkedHashMap<>();
-        for (T concept : conceptList) {
-            conceptMap.put((PfConceptKey) concept.getKey(), concept);
-        }
-
-        return conceptMap;
-    }
-
-    /**
      * Verify the policy type for a policy exists.
      *
      * @param dao the DAO to use to access policy types in the database
@@ -447,8 +508,7 @@
         if (policyType == null) {
             String errorMessage =
                     "policy type " + policyTypeKey.getId() + " for policy " + policy.getId() + " does not exist";
-            LOGGER.warn(errorMessage);
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, errorMessage);
         }
     }
 
@@ -476,9 +536,8 @@
 
         // We should have one and only one returned entry
         if (filterdPolicyTypeList.size() != 1) {
-            String errorMessage = "search for lates policy type " + policyTypeName + " returned more than one entry";
-            LOGGER.warn(errorMessage);
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            String errorMessage = "search for latest policy type " + policyTypeName + " returned more than one entry";
+            throw new PfModelRuntimeException(Response.Status.CONFLICT, errorMessage);
         }
 
         return (JpaToscaPolicyType) filterdPolicyTypeList.get(0);
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java
index 87b499b..6f21c19 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java
@@ -74,7 +74,7 @@
         compositeTemplate.setPolicyTypes(
                 addFragmentEntitites(compositeTemplate.getPolicyTypes(), fragmentTemplate.getPolicyTypes(), result));
 
-        if (originalTemplate.getTopologyTemplate() != null) {
+        if (originalTemplate.getTopologyTemplate() != null && fragmentTemplate.getTopologyTemplate() != null) {
             if (originalTemplate.getTopologyTemplate()
                     .compareToWithoutEntities(fragmentTemplate.getTopologyTemplate()) == 0) {
                 compositeTemplate.getTopologyTemplate()
@@ -95,7 +95,7 @@
 
         if (!result.isValid()) {
             String message = result.toString();
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message);
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, message);
         }
 
         return compositeTemplate;
diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java
index 3906922..b75273e 100644
--- a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java
+++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java
@@ -52,14 +52,18 @@
 
     // @formatter:off
     private static final Set<PfConceptKey> PREDEFINED_TOSCA_DATA_TYPES = Set.of(
-            new PfConceptKey("string",    PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("integer",   PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("float",     PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("boolean",   PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("timestamp", PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("null",      PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("list",      PfKey.NULL_KEY_VERSION),
-            new PfConceptKey("map",       PfKey.NULL_KEY_VERSION)
+            new PfConceptKey("string",                       PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("integer",                      PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("float",                        PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("boolean",                      PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("timestamp",                    PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("null",                         PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("list",                         PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("map",                          PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("scalar-unit.size",             PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("scalar-unit.time",             PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("scalar-unit.frequency",        PfKey.NULL_KEY_VERSION),
+            new PfConceptKey("tosca.datatypes.TimeInterval", PfKey.NULL_KEY_VERSION)
         );
     // @formatter:on
 
@@ -143,7 +147,7 @@
             final Function<JpaToscaServiceTemplate, String> checkerFunction) {
         String message = checkerFunction.apply(serviceTemplate);
         if (message != null) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message);
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND, message);
         }
     }
 
@@ -242,23 +246,24 @@
      * Get the entity tree from a concept container for a given entity key.
      *
      * @param entityTypes the concept container containing entity types
-     * @param searchKey the key to search for
+     * @param entityName the name of the entity
+     * @param entityVersion the version of the entity
      */
     public static void getEntityTree(
             @NonNull final PfConceptContainer<? extends PfConcept, ? extends PfNameVersion> entityTypes,
-            @NonNull final PfConceptKey searchKey) {
+            final String entityName, final String entityVersion) {
 
         PfValidationResult result = new PfValidationResult();
 
         @SuppressWarnings("unchecked")
         Set<JpaToscaEntityType<?>> filteredEntitySet =
-                (Set<JpaToscaEntityType<?>>) entityTypes.getAll(searchKey.getName(), searchKey.getVersion());
+                (Set<JpaToscaEntityType<?>>) entityTypes.getAll(entityName, entityVersion);
         for (JpaToscaEntityType<?> filteredEntityType : filteredEntitySet) {
             filteredEntitySet.addAll(ToscaUtils.getEntityTypeAncestors(entityTypes, filteredEntityType, result));
         }
 
         if (!result.isValid()) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
         }
 
         entityTypes.getConceptMap().entrySet()
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java
index 0bf3710..858ac09 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java
@@ -87,6 +87,7 @@
         for (String policyResourceName : policyResourceNames) {
             String policyString = ResourceUtils.getResourceAsString(policyResourceName);
             if (policyResourceName.endsWith("yaml")) {
+                LOGGER.info("loading {}", policyResourceName);
                 Object yamlObject = new Yaml().load(policyString);
                 policyString = new GsonBuilder().setPrettyPrinting().create().toJson(yamlObject);
             }
@@ -150,7 +151,7 @@
         assertEquals(VERSION_100, filteredList.get(7).getVersion());
         assertEquals(VERSION_100, filteredList.get(12).getVersion());
 
-        assertEquals(24, policyList.size());
+        assertEquals(23, policyList.size());
         assertEquals(22, filteredList.size());
 
         policyList.get(10).setVersion("2.0.0");
@@ -172,7 +173,7 @@
     public void testFilterNameVersion() {
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(2, filteredList.size());
+        assertEquals(1, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().name("guard.frequency.scaleout").build();
         filteredList = filter.filter(policyList);
@@ -184,7 +185,7 @@
 
         filter = ToscaPolicyFilter.builder().version(VERSION_100).build();
         filteredList = filter.filter(policyList);
-        assertEquals(22, filteredList.size());
+        assertEquals(21, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().name("OSDF_CASABLANCA.SubscriberPolicy_v1").version(VERSION_100).build();
         filteredList = filter.filter(policyList);
@@ -192,7 +193,7 @@
 
         filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").version(VERSION_100).build();
         filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
     }
 
     @Test
@@ -200,11 +201,11 @@
         // null pattern
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().versionPrefix(null).build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(24, filteredList.size());
+        assertEquals(23, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().versionPrefix("1.").build();
         filteredList = filter.filter(policyList);
-        assertEquals(22, filteredList.size());
+        assertEquals(21, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().versionPrefix("100.").build();
         filteredList = filter.filter(policyList);
@@ -215,7 +216,11 @@
     public void testFilterTypeVersion() {
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
+
+        filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Apex").build();
+        filteredList = filter.filter(policyList);
+        assertEquals(0, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Drools").build();
         filteredList = filter.filter(policyList);
@@ -231,7 +236,7 @@
 
         filter = ToscaPolicyFilter.builder().typeVersion(VERSION_000).build();
         filteredList = filter.filter(policyList);
-        assertEquals(4, filteredList.size());
+        assertEquals(3, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().type("onap.policies.optimization.resource.HpaPolicy")
                 .typeVersion(VERSION_100).build();
@@ -241,6 +246,6 @@
         filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").typeVersion(VERSION_000)
                 .build();
         filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
     }
 }
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java
index 30696ce..0f038d3 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java
@@ -163,7 +163,7 @@
     public void testFilterNameVersion() {
         ToscaPolicyTypeFilter filter = ToscaPolicyTypeFilter.builder().name("onap.policies.Monitoring").build();
         List<ToscaPolicyType> filteredList = filter.filter(typeList);
-        assertEquals(2, filteredList.size());
+        assertEquals(1, filteredList.size());
 
         filter = ToscaPolicyTypeFilter.builder().name("onap.policies.monitoring.cdap.tca.hi.lo.app").build();
         filteredList = filter.filter(typeList);
@@ -173,9 +173,9 @@
         filteredList = filter.filter(typeList);
         assertEquals(0, filteredList.size());
 
-        filter = ToscaPolicyTypeFilter.builder().version(VERSION_000).build();
+        filter = ToscaPolicyTypeFilter.builder().version(VERSION_100).build();
         filteredList = filter.filter(typeList);
-        assertEquals(1, filteredList.size());
+        assertEquals(20, filteredList.size());
 
         filter = ToscaPolicyTypeFilter.builder().name("onap.policies.optimization.Vim_fit").version(VERSION_000)
                 .build();
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTest.java
index 36c6654..8b79374 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTest.java
@@ -156,8 +156,7 @@
         assertEquals(1, gotPolicyList.size());
         assertEquals(0, beforePolicy.compareNameVersion(beforePolicy, gotPolicyList.get(0)));
 
-        gotPolicyList = new AuthorativeToscaProvider().getPolicyList(pfDao, "Nonexistant", VERSION_100);
-        assertEquals(0, gotPolicyList.size());
+        assertTrue(new AuthorativeToscaProvider().getPolicyList(pfDao, "Nonexistant", VERSION_100).isEmpty());
     }
 
     @Test
@@ -380,10 +379,11 @@
         assertEquals(0, beforePolicy.compareNameVersion(beforePolicy, createdPolicy));
         assertTrue(beforePolicy.getType().equals(deletedPolicy.getType()));
 
-        ToscaServiceTemplate gotServiceTemplate =
-                new AuthorativeToscaProvider().getPolicies(pfDao, policyKey.getName(), policyKey.getVersion());
-
-        assertTrue(gotServiceTemplate.getToscaTopologyTemplate().getPolicies().isEmpty());
+        // @formatter:off
+        assertThatThrownBy(
+            () -> new AuthorativeToscaProvider().getPolicies(pfDao, policyKey.getName(), policyKey.getVersion()))
+                    .hasMessageMatching("policies for onap.restart.tca:1.0.0 do not exist");
+        // @formatter:on
     }
 
     @Test
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java
index c41f5e1..3f0d9e2 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java
@@ -58,11 +58,11 @@
  */
 public class AuthorativeToscaProviderPolicyTypeTest {
     private static final String VERSION = "version";
-    private static final String POLICY_AFFINITY_VERSION0 = "onap.policies.NoVersion:0.0.0";
-    private static final String POLICY_AFFINITY = "onap.policies.NoVersion";
+    private static final String POLICY_NO_VERSION_VERSION1 = "onap.policies.NoVersion:0.0.1";
+    private static final String POLICY_NO_VERSION = "onap.policies.NoVersion";
     private static final String MISSING_POLICY_TYPES = "no policy types specified on service template";
     private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$";
-    private static final String VERSION_000 = "0.0.0";
+    private static final String VERSION_001 = "0.0.1";
     private static String yamlAsJsonString;
     private PfDao pfDao;
     private StandardCoder standardCoder;
@@ -135,7 +135,7 @@
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_AFFINITY_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -150,21 +150,26 @@
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), createdPolicyType.getDescription()));
 
         List<ToscaPolicyType> gotPolicyTypeList =
-                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_AFFINITY, VERSION_000);
-        assertEquals(1, gotPolicyTypeList.size());
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
-        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_AFFINITY, null);
-        assertEquals(1, gotPolicyTypeList.size());
+        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, null);
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, null);
-        assertEquals(1, gotPolicyTypeList.size());
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
-        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_000);
-        assertEquals(1, gotPolicyTypeList.size());
+        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_001);
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
+
+        assertThatThrownBy(() -> new AuthorativeToscaProvider().getPolicyTypeList(new DefaultPfDao(), POLICY_NO_VERSION,
+                VERSION_001)).hasMessageContaining("Policy Framework DAO has not been initialized");
+
+        assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_001).isEmpty());
     }
 
     @Test
@@ -193,13 +198,21 @@
             new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao, null);
         }).hasMessageMatching("^filter is marked .*on.*ull but is null$");
 
+        assertThatThrownBy(() -> new AuthorativeToscaProvider().getFilteredPolicyTypeList(new DefaultPfDao(),
+                ToscaPolicyTypeFilter.builder().name("i.dont.Exist").build()))
+                        .hasMessageContaining("Policy Framework DAO has not been initialized");
+
+        assertTrue(new AuthorativeToscaProvider()
+                .getFilteredPolicyTypeList(pfDao, ToscaPolicyTypeFilter.builder().name("i.dont.Exist").build())
+                .isEmpty());
+
         ToscaServiceTemplate toscaServiceTemplate = standardCoder.decode(yamlAsJsonString, ToscaServiceTemplate.class);
 
         assertNotNull(toscaServiceTemplate);
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_AFFINITY_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -221,20 +234,20 @@
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription()));
 
         gotServiceTemplate = new AuthorativeToscaProvider().getFilteredPolicyTypes(pfDao,
-                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build());
+                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build());
 
         gotPolicyType = gotServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription()));
 
         List<ToscaPolicyType> gotPolicyTypeList =
-                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_AFFINITY, VERSION_000);
-        assertEquals(1, gotPolicyTypeList.size());
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao,
                 ToscaPolicyTypeFilter.builder().build());
-        assertEquals(1, gotPolicyTypeList.size());
+        assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao,
@@ -243,13 +256,13 @@
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao,
-                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build());
+                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build());
         assertEquals(1, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao,
                 ToscaPolicyTypeFilter.builder().version("1.0.0").build());
-        assertEquals(0, gotPolicyTypeList.size());
+        assertEquals(1, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
     }
 
@@ -283,7 +296,7 @@
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_AFFINITY_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -315,7 +328,7 @@
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_AFFINITY_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -366,7 +379,7 @@
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_AFFINITY_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -380,10 +393,9 @@
         assertEquals(true, beforePolicyType.getName().equals(deletedPolicy.getName()));
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), deletedPolicy.getDescription()));
 
-        ToscaServiceTemplate gotServiceTemplate = new AuthorativeToscaProvider().getPolicyTypes(pfDao,
-                policyTypeKey.getName(), policyTypeKey.getVersion());
-
-        assertTrue(gotServiceTemplate.getPolicyTypes().isEmpty());
+        assertThatThrownBy(() -> {
+            new AuthorativeToscaProvider().getPolicyTypes(pfDao, policyTypeKey.getName(), policyTypeKey.getVersion());
+        }).hasMessage("policy types for onap.policies.NoVersion:0.0.1 do not exist");
     }
 
     @Test
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyGuardPolicyMapperTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyGuardPolicyMapperTest.java
index 6d32c6d..62c088c 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyGuardPolicyMapperTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyGuardPolicyMapperTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 
 import java.util.LinkedHashMap;
 import java.util.Map;
+
 import org.junit.Test;
 import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.tosca.legacy.concepts.LegacyGuardPolicyOutput;
@@ -49,10 +50,6 @@
         JpaToscaPolicy policy = new JpaToscaPolicy(new PfConceptKey("PolicyName", "2.0.0"));
         serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().put(policy.getKey(), policy);
 
-        assertThatThrownBy(() -> {
-            new LegacyGuardPolicyMapper().fromToscaServiceTemplate(serviceTemplate);
-        }).hasMessageContaining("no metadata defined on TOSCA policy");
-
         policy.setMetadata(new LinkedHashMap<>());
         assertThatThrownBy(() -> {
             new LegacyGuardPolicyMapper().fromToscaServiceTemplate(serviceTemplate);
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java
index 0aa1da0..4dcfeaf 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ *  Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@
         JpaToscaServiceTemplate policyTypeServiceTemplate = new JpaToscaServiceTemplate();
         policyTypeServiceTemplate.fromAuthorative(policyTypes);
 
-        String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.input.json");
+        String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.legacy.input.json");
         LegacyOperationalPolicy legacyOperationalPolicy =
                 standardCoder.decode(vcpePolicyJson, LegacyOperationalPolicy.class);
 
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java
index ec03122..4d0fd6f 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ *  Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,8 +51,8 @@
  */
 public class LegacyProvider4LegacyOperationalTest {
     private static final String POLICY_ID_IS_NULL = "^policyId is marked .*on.*ull but is null$";
-    private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.output.json";
-    private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.input.json";
+    private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.legacy.output.json";
+    private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.legacy.input.json";
     private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$";
     private PfDao pfDao;
     private StandardCoder standardCoder;
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataTypeTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataTypeTest.java
index 499cf72..d205794 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataTypeTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaDataTypeTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2019=-2020 Nordix Foundation.
+ *  Copyright (C) 2019-2020 Nordix Foundation.
  *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,13 +34,14 @@
 
 import org.junit.Test;
 import org.onap.policy.models.base.PfConceptKey;
+import org.onap.policy.models.base.PfKey;
 import org.onap.policy.models.base.PfReferenceKey;
 import org.onap.policy.models.base.PfValidationResult;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaConstraint;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
 
 /**
- * DAO test for ToscaDatatype.
+ * DAO test for JpaToscaDatatype.
  *
  * @author Liam Fallon (liam.fallon@est.tech)
  */
@@ -140,4 +141,53 @@
         ToscaDataType datOut = tdta.toAuthorative();
         assertNotNull(datOut);
     }
+
+    @Test
+    public void testGetReferencedDataTypes() {
+        JpaToscaDataType dt0 = new JpaToscaDataType(new PfConceptKey("dt0", "0.0.1"));
+
+        assertTrue(dt0.getReferencedDataTypes().isEmpty());
+
+        dt0.setProperties(new LinkedHashMap<>());
+        assertTrue(dt0.getReferencedDataTypes().isEmpty());
+
+        JpaToscaProperty prop0 = new JpaToscaProperty(new PfReferenceKey(dt0.getKey(), "prop0"));
+        prop0.setType(new PfConceptKey("string", PfKey.NULL_KEY_VERSION));
+        assertTrue(prop0.validate(new PfValidationResult()).isValid());
+
+        dt0.getProperties().put(prop0.getKey().getLocalName(), prop0);
+        assertTrue(dt0.getReferencedDataTypes().isEmpty());
+
+        JpaToscaProperty prop1 = new JpaToscaProperty(new PfReferenceKey(dt0.getKey(), "prop1"));
+        prop1.setType(new PfConceptKey("the.property.Type0", "0.0.1"));
+        assertTrue(prop1.validate(new PfValidationResult()).isValid());
+
+        dt0.getProperties().put(prop1.getKey().getLocalName(), prop1);
+        assertEquals(1, dt0.getReferencedDataTypes().size());
+
+        JpaToscaProperty prop2 = new JpaToscaProperty(new PfReferenceKey(dt0.getKey(), "prop2"));
+        prop2.setType(new PfConceptKey("the.property.Type0", "0.0.1"));
+        assertTrue(prop2.validate(new PfValidationResult()).isValid());
+
+        dt0.getProperties().put(prop2.getKey().getLocalName(), prop2);
+        assertEquals(1, dt0.getReferencedDataTypes().size());
+
+        JpaToscaProperty prop3 = new JpaToscaProperty(new PfReferenceKey(dt0.getKey(), "prop4"));
+        prop3.setType(new PfConceptKey("the.property.Type1", "0.0.1"));
+        prop3.setEntrySchema(new JpaToscaEntrySchema());
+        prop3.getEntrySchema().setType(new PfConceptKey("the.property.Type3", "0.0.1"));
+        assertTrue(prop3.validate(new PfValidationResult()).isValid());
+
+        dt0.getProperties().put(prop3.getKey().getLocalName(), prop3);
+        assertEquals(3, dt0.getReferencedDataTypes().size());
+
+        JpaToscaProperty prop4 = new JpaToscaProperty(new PfReferenceKey(dt0.getKey(), "prop4"));
+        prop4.setType(new PfConceptKey("the.property.Type1", "0.0.1"));
+        prop4.setEntrySchema(new JpaToscaEntrySchema());
+        prop4.getEntrySchema().setType(new PfConceptKey("the.property.Type2", "0.0.1"));
+        assertTrue(prop4.validate(new PfValidationResult()).isValid());
+
+        dt0.getProperties().put(prop4.getKey().getLocalName(), prop4);
+        assertEquals(3, dt0.getReferencedDataTypes().size());
+    }
 }
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplateTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplateTest.java
index 95c51e9..5cbec00 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplateTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaServiceTemplateTest.java
@@ -21,6 +21,7 @@
 
 package org.onap.policy.models.tosca.simple.concepts;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -135,5 +136,101 @@
         assertTrue(tst.validate(new PfValidationResult()).isValid());
 
         assertThatThrownBy(() -> tst.validate(null)).hasMessageMatching("resultIn is marked .*on.*ull but is null");
+
+        tst.setToscaDefinitionsVersion(null);
+        PfValidationResult result = tst.validate(new PfValidationResult());
+        assertThat(result.toString()).contains("service template tosca definitions version may not be null");
+
+        tst.setToscaDefinitionsVersion(JpaToscaServiceTemplate.DEFAULT_TOSCA_DEFINTIONS_VERISON);
+        tst.setDataTypes(null);
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        JpaToscaPolicyType pt0 = new JpaToscaPolicyType(new PfConceptKey("pt0:0.0.1"));
+        tst.getPolicyTypes().getConceptMap().put(pt0.getKey(), pt0);
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        JpaToscaDataType dt0 = new JpaToscaDataType(new PfConceptKey("dt0:0.0.1"));
+        JpaToscaProperty prop0 = new JpaToscaProperty(new PfReferenceKey(pt0.getKey(), "prop0"));
+        prop0.setType(dt0.getKey());
+        pt0.getProperties().put(prop0.getKey().getLocalName(), prop0);
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("referenced data type dt0:0.0.1 not found");
+
+        tst.setDataTypes(null);
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("referenced data type dt0:0.0.1 not found");
+
+        tst.setDataTypes(new JpaToscaDataTypes());
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("referenced data type dt0:0.0.1 not found");
+
+        tst.getDataTypes().getConceptMap().put(dt0.getKey(), dt0);
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.setTopologyTemplate(null);
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.setTopologyTemplate(new JpaToscaTopologyTemplate());
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.getTopologyTemplate().setPolicies(new JpaToscaPolicies());
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.setPolicyTypes(null);
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        JpaToscaPolicy pol0 = new JpaToscaPolicy(new PfConceptKey("pol0:0.0.1"));
+        tst.getTopologyTemplate().getPolicies().getConceptMap().put(pol0.getKey(), pol0);
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("type is null or a null key");
+
+        pol0.setType(new PfConceptKey("i.dont.Exist:0.0.1"));
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains(
+                "no policy types are defined on the service template for the policies in the topology template");
+
+        tst.setPolicyTypes(policyTypes);
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("policy type i.dont.Exist:0.0.1 referenced in policy not found");
+
+        pol0.setType(dt0.getKey());
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains("policy type dt0:0.0.1 referenced in policy not found");
+
+        pol0.setType(pt0.getKey());
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.setPolicyTypes(null);
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains(
+                "no policy types are defined on the service template for the policies in the topology template");
+
+        tst.setPolicyTypes(policyTypes);
+        pol0.setType(pt0.getKey());
+        result = tst.validate(new PfValidationResult());
+        assertTrue(result.isOk());
+
+        tst.setPolicyTypes(new JpaToscaPolicyTypes());
+        result = tst.validate(new PfValidationResult());
+        assertFalse(result.isOk());
+        assertThat(result.toString()).contains(
+                "no policy types are defined on the service template for the policies in the topology template");
+
     }
 }
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProviderTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProviderTest.java
index 03c7f9b..f34c0c8 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProviderTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProviderTest.java
@@ -24,7 +24,6 @@
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import java.util.Properties;
 
@@ -59,6 +58,7 @@
  */
 public class SimpleToscaProviderTest {
     private static final String TEMPLATE_IS_NULL = "^serviceTemplate is marked .*on.*ull but is null$";
+    private static final String INCOMING_TEMPLATE_IS_NULL = "^incomingServiceTemplate is marked .*on.*ull but is null$";
     private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.monitoring.input.tosca.json";
     private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$";
 
@@ -140,10 +140,8 @@
         assertEquals(dataType0, deletedServiceTemplate.getDataTypes().get(dataType0Key));
         assertEquals("Updated Description", deletedServiceTemplate.getDataTypes().get(dataType0Key).getDescription());
 
-        JpaToscaServiceTemplate doesNotExistServiceTemplate =
-                new SimpleToscaProvider().deleteDataType(pfDao, dataType0Key);
-
-        assertEquals(null, doesNotExistServiceTemplate.getDataTypes().get(dataType0Key));
+        assertThatThrownBy(() -> new SimpleToscaProvider().deleteDataType(pfDao, dataType0Key))
+                .hasMessage("data types for DataType0:0.0.1 do not exist");
     }
 
     @Test
@@ -191,10 +189,8 @@
         assertEquals("Updated Description",
                 deletedServiceTemplate.getPolicyTypes().get(policyType0Key).getDescription());
 
-        JpaToscaServiceTemplate doesNotExistServiceTemplate =
-                new SimpleToscaProvider().deletePolicyType(pfDao, policyType0Key);
-
-        assertEquals(null, doesNotExistServiceTemplate.getPolicyTypes().get(policyType0Key));
+        assertThatThrownBy(() -> new SimpleToscaProvider().deletePolicyType(pfDao, policyType0Key))
+                .hasMessage("policy types for PolicyType0:0.0.1 do not exist");
     }
 
     @Test
@@ -235,10 +231,8 @@
         assertEquals("Updated Description",
                 deletedServiceTemplate.getPolicyTypes().get(policyType0Key).getDescription());
 
-        JpaToscaServiceTemplate doesNotExistServiceTemplate =
-                new SimpleToscaProvider().deletePolicyType(pfDao, policyType0Key);
-
-        assertEquals(null, doesNotExistServiceTemplate.getPolicyTypes().get(policyType0Key));
+        assertThatThrownBy(() -> new SimpleToscaProvider().deletePolicyType(pfDao, policyType0Key))
+                .hasMessage("policy types for PolicyType0:0.0.1 do not exist");
     }
 
     @Test
@@ -255,7 +249,8 @@
         JpaToscaServiceTemplate createdServiceTemplate =
                 new SimpleToscaProvider().createPolicies(pfDao, originalServiceTemplate);
 
-        assertEquals(originalServiceTemplate, createdServiceTemplate);
+        assertEquals(originalServiceTemplate.getTopologyTemplate().getPolicies(),
+                createdServiceTemplate.getTopologyTemplate().getPolicies());
 
         PfConceptKey policyKey = new PfConceptKey("onap.restart.tca:1.0.0");
 
@@ -264,7 +259,6 @@
 
         assertEquals(originalServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey),
                 gotServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey));
-
     }
 
     @Test
@@ -281,7 +275,8 @@
         JpaToscaServiceTemplate createdServiceTemplate =
                 new SimpleToscaProvider().createPolicies(pfDao, originalServiceTemplate);
 
-        assertEquals(originalServiceTemplate, createdServiceTemplate);
+        assertEquals(originalServiceTemplate.getTopologyTemplate().getPolicies(),
+                createdServiceTemplate.getTopologyTemplate().getPolicies());
     }
 
     @Test
@@ -315,7 +310,7 @@
         JpaToscaServiceTemplate createdServiceTemplate =
                 new SimpleToscaProvider().createPolicies(pfDao, originalServiceTemplate);
 
-        assertEquals(originalServiceTemplate, createdServiceTemplate);
+        assertEquals(originalServiceTemplate.getTopologyTemplate(), createdServiceTemplate.getTopologyTemplate());
 
         PfConceptKey policyKey = new PfConceptKey("onap.restart.tca:1.0.0");
 
@@ -325,8 +320,11 @@
         assertEquals(originalServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey),
                 deletedServiceTemplate.getTopologyTemplate().getPolicies().get(policyKey));
 
-        assertTrue(new SimpleToscaProvider().getPolicies(pfDao, policyKey.getName(), policyKey.getVersion())
-                .getTopologyTemplate().getPolicies().getConceptMap().isEmpty());
+        // @formatter:off
+        assertThatThrownBy(
+            () -> new SimpleToscaProvider().getPolicies(pfDao, policyKey.getName(), policyKey.getVersion()))
+                    .hasMessage("policies for onap.restart.tca:1.0.0 do not exist");
+        // @formatter:on
     }
 
     @Test
@@ -361,7 +359,7 @@
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().createDataTypes(pfDao, null);
-        }).hasMessageMatching(TEMPLATE_IS_NULL);
+        }).hasMessageMatching(INCOMING_TEMPLATE_IS_NULL);
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().updateDataTypes(null, null);
@@ -401,7 +399,7 @@
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().createPolicyTypes(pfDao, null);
-        }).hasMessageMatching(TEMPLATE_IS_NULL);
+        }).hasMessageMatching(INCOMING_TEMPLATE_IS_NULL);
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().updatePolicyTypes(null, null);
@@ -441,7 +439,7 @@
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().createPolicies(pfDao, null);
-        }).hasMessageMatching(TEMPLATE_IS_NULL);
+        }).hasMessageMatching(INCOMING_TEMPLATE_IS_NULL);
 
         assertThatThrownBy(() -> {
             new SimpleToscaProvider().updatePolicies(null, null);
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java
index 01a52f7..0a8283e 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ *  Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -155,9 +155,9 @@
 
         Entry<PfConceptKey, JpaToscaPolicyType> firstPolicyType = policyTypesIter.next();
         assertEquals(MONITORING, firstPolicyType.getKey().getName());
-        assertEquals(VERSION_000, firstPolicyType.getKey().getVersion());
+        assertEquals(VERSION_100, firstPolicyType.getKey().getVersion());
         assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName());
-        assertEquals("a base policy type for all policies that governs monitoring provisioning",
+        assertEquals("a base policy type for all policies that govern monitoring provisioning",
                 firstPolicyType.getValue().getDescription());
 
         Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next();
@@ -376,13 +376,13 @@
         assertEquals(MONITORING, firstPolicyType.getKey().getName());
         assertEquals(VERSION_100, firstPolicyType.getKey().getVersion());
         assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName());
-        assertEquals("a base policy type for all policies that govern monitoring provision",
+        assertEquals("a base policy type for all policies that govern monitoring provisioning",
                 firstPolicyType.getValue().getDescription());
 
         Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next();
         assertEquals(DCAE, secondPolicyType.getKey().getName());
         assertEquals(VERSION_100, secondPolicyType.getKey().getVersion());
-        assertEquals("policy.nodes.Root", secondPolicyType.getValue().getDerivedFrom().getName());
+        assertEquals("onap.policies.Monitoring", secondPolicyType.getValue().getDerivedFrom().getName());
         assertTrue(secondPolicyType.getValue().getProperties().size() == 2);
 
         Iterator<JpaToscaProperty> propertiesIter = secondPolicyType.getValue().getProperties().values().iterator();
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java
index ca46de1..f9a0143 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java
@@ -201,5 +201,19 @@
         assertEquals(dt1, dtIterator.next());
         assertEquals(pt0, compositeTemplate05.getPolicyTypes().getAll(null).iterator().next());
         assertEquals(p0, compositeTemplate05.getTopologyTemplate().getPolicies().getAll(null).iterator().next());
+
+        JpaToscaServiceTemplate fragmentTemplate09 = new JpaToscaServiceTemplate();
+
+        fragmentTemplate09.setDataTypes(new JpaToscaDataTypes());
+        fragmentTemplate09.getDataTypes().getConceptMap().put(dt1.getKey(), dt1);
+
+        fragmentTemplate09.setPolicyTypes(new JpaToscaPolicyTypes());
+        fragmentTemplate09.getPolicyTypes().getConceptMap().put(pt0.getKey(), pt0);
+
+        fragmentTemplate09.setTopologyTemplate(null);
+
+        JpaToscaServiceTemplate compositeTemplate06 =
+                ToscaServiceTemplateUtils.addFragment(compositeTemplate05, fragmentTemplate09);
+        assertEquals(compositeTemplate05.getTopologyTemplate(), compositeTemplate06.getTopologyTemplate());
     }
 }
diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaUtilsTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaUtilsTest.java
index bad89e9..a5d145e 100644
--- a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaUtilsTest.java
+++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaUtilsTest.java
@@ -253,31 +253,23 @@
     @Test
     public void testgetEntityTree() {
         assertThatThrownBy(() -> {
-            ToscaUtils.getEntityTree(null, null);
+            ToscaUtils.getEntityTree(null, null, null);
         }).hasMessageMatching("entityTypes is marked .*on.*ull but is null");
 
-        assertThatThrownBy(() -> {
-            ToscaUtils.getEntityTree(null, new PfConceptKey());
-        }).hasMessageMatching("entityTypes is marked .*on.*ull but is null");
-
-        assertThatThrownBy(() -> {
-            ToscaUtils.getEntityTree(new JpaToscaDataTypes(), null);
-        }).hasMessageMatching("searchKey is marked .*on.*ull but is null");
-
         JpaToscaDataTypes dataTypes = new JpaToscaDataTypes(new PfConceptKey("datatypes", "0.0.1"));
         JpaToscaDataTypes filteredDataTypes = new JpaToscaDataTypes(new PfConceptKey("datatypes", "0.0.1"));
-        ToscaUtils.getEntityTree(filteredDataTypes, new PfConceptKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, "IDontExist", "0.0.0");
         assertEquals(dataTypes, filteredDataTypes);
 
         JpaToscaDataType dt0 = new JpaToscaDataType(new PfConceptKey("dt0", "0.0.1"));
         dataTypes.getConceptMap().put(dt0.getKey(), dt0);
         filteredDataTypes.getConceptMap().put(dt0.getKey(), dt0);
-        ToscaUtils.getEntityTree(filteredDataTypes, new PfConceptKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, "IDontExist", "0.0.0");
         assertNotEquals(dataTypes, filteredDataTypes);
         assertTrue(filteredDataTypes.getConceptMap().isEmpty());
 
         filteredDataTypes.getConceptMap().put(dt0.getKey(), dt0);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt0.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt0.getKey().getName(), dt0.getKey().getVersion());
         assertEquals(dataTypes, filteredDataTypes);
 
         JpaToscaDataType dt1 = new JpaToscaDataType(new PfConceptKey("dt1", "0.0.1"));
@@ -317,31 +309,31 @@
         dataTypes.getConceptMap().put(dt8.getKey(), dt8);
         dataTypes.getConceptMap().put(dt9.getKey(), dt9);
 
-        ToscaUtils.getEntityTree(filteredDataTypes, dt0.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt0.getKey().getName(), dt0.getKey().getVersion());
         assertEquals(1, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt1.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt1.getKey().getName(), dt1.getKey().getVersion());
         assertEquals(2, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt2.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt2.getKey().getName(), dt2.getKey().getVersion());
         assertEquals(2, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt3.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt3.getKey().getName(), dt3.getKey().getVersion());
         assertEquals(2, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt4.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt4.getKey().getName(), dt4.getKey().getVersion());
         assertEquals(3, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt5.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt5.getKey().getName(), dt5.getKey().getVersion());
         assertEquals(4, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt6.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt6.getKey().getName(), dt6.getKey().getVersion());
         assertEquals(5, filteredDataTypes.getConceptMap().size());
         assertTrue(filteredDataTypes.getConceptMap().containsValue(dt0));
         assertFalse(filteredDataTypes.getConceptMap().containsValue(dt1));
@@ -352,15 +344,15 @@
         assertTrue(filteredDataTypes.getConceptMap().containsValue(dt6));
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt7.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt7.getKey().getName(), dt7.getKey().getVersion());
         assertEquals(1, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt8.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt8.getKey().getName(), dt8.getKey().getVersion());
         assertEquals(2, filteredDataTypes.getConceptMap().size());
 
         filteredDataTypes = new JpaToscaDataTypes(dataTypes);
-        ToscaUtils.getEntityTree(filteredDataTypes, dt9.getKey());
+        ToscaUtils.getEntityTree(filteredDataTypes, dt9.getKey().getName(), dt9.getKey().getVersion());
         assertEquals(3, filteredDataTypes.getConceptMap().size());
 
         dt9.setDerivedFrom(new PfConceptKey("i.dont.Exist", "0.0.0"));
@@ -368,7 +360,7 @@
 
         assertThatThrownBy(() -> {
             final JpaToscaDataTypes badDataTypes = new JpaToscaDataTypes(dataTypes);
-            ToscaUtils.getEntityTree(badDataTypes, dt9.getKey());
+            ToscaUtils.getEntityTree(badDataTypes, dt9.getKey().getName(), dt9.getKey().getVersion());
         }).hasMessageContaining("parent i.dont.Exist:0.0.0 of entity not found");
     }
 }
diff --git a/models-tosca/src/test/resources/META-INF/persistence.xml b/models-tosca/src/test/resources/META-INF/persistence.xml
index 62e0b60..de27dd9 100644
--- a/models-tosca/src/test/resources/META-INF/persistence.xml
+++ b/models-tosca/src/test/resources/META-INF/persistence.xml
@@ -28,10 +28,14 @@
         <class>org.onap.policy.models.base.PfConceptKey</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaDataType</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaDataTypes</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicies</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicy</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyType</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyTypes</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTopologyTemplate</class>
         <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class>
+        <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class>
 
         <properties>
             <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
diff --git a/models-tosca/src/test/resources/onap.policies.NoVersion.yaml b/models-tosca/src/test/resources/onap.policies.NoVersion.yaml
index 7d1262b..2dda556 100644
--- a/models-tosca/src/test/resources/onap.policies.NoVersion.yaml
+++ b/models-tosca/src/test/resources/onap.policies.NoVersion.yaml
@@ -1,7 +1,12 @@
 tosca_definitions_version: tosca_simple_yaml_1_0_0
 policy_types:
+   onap.policies.Optimization:
+      derived_from: tosca.policies.Root
+      version: 1.0.0
+      description: The base policy type for all policies that govern optimization
    onap.policies.NoVersion:
       derived_from: onap.policies.Optimization
+      version: 0.0.1
       properties:
          applicableResources:
             type: list