Merge "add unit test for pdpstatistics provider"
authorLiam Fallon <liam.fallon@est.tech>
Tue, 18 Feb 2020 16:13:01 +0000 (16:13 +0000)
committerGerrit Code Review <gerrit@onap.org>
Tue, 18 Feb 2020 16:13:01 +0000 (16:13 +0000)
91 files changed:
models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vCPE.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vCPE.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml
models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vDNS.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vDNS.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json
models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml [new file with mode: 0644]
models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml
models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml
models-interactions/model-actors/actor.aai/pom.xml [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java [new file with mode: 0644]
models-interactions/model-actors/actor.appc/pom.xml
models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java
models-interactions/model-actors/actor.test/pom.xml
models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java [moved from models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java with 75% similarity]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java [moved from models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java with 74% similarity]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
models-interactions/model-actors/pom.xml
models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java
models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java
models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java [new file with mode: 0644]
models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java
models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java
models-pdp/src/test/resources/META-INF/persistence.xml
models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java
models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java
models-provider/src/test/resources/META-INF/persistence.xml
models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java
models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java
models-tosca/src/test/resources/META-INF/persistence.xml
models-tosca/src/test/resources/onap.policies.NoVersion.yaml

index e4a0694..2c7981a 100644 (file)
@@ -23,7 +23,7 @@ topology_template:
                actor: APPC
                recipe: Restart
                target:
-                 type: VM
+                 targetType: VM
                retry: 3
                timeout: 1200
                success: final_success
index 63c0d8b..f6f15fe 100644 (file)
@@ -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"
                     }
                 }
             }
index f21fd5a..773e0c9 100644 (file)
@@ -3,4 +3,4 @@ policy_types:
    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
index 0dbe7e4..4a918be 100644 (file)
@@ -3,7 +3,9 @@ policy_types:
     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
@@ -31,36 +33,7 @@ policy_types:
                 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
@@ -77,6 +50,8 @@ data_types:
                     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
@@ -91,7 +66,7 @@ data_types:
                 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:
index e1555e8..9c6c612 100644 (file)
@@ -2,6 +2,7 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 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:
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 (file)
index 0000000..2d793cc
--- /dev/null
@@ -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
+
index 2f5abdd..5fa4308 100644 (file)
@@ -2,7 +2,8 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 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
index cf70e9b..8419b09 100644 (file)
@@ -2,10 +2,10 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 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-interactions/model-actors/actor.aai/pom.xml b/models-interactions/model-actors/actor.aai/pom.xml
new file mode 100644 (file)
index 0000000..4e932a1
--- /dev/null
@@ -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 (file)
index 0000000..df427c3
--- /dev/null
@@ -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 (file)
index 0000000..bc2dde9
--- /dev/null
@@ -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.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 void 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));
+    }
+}
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 (file)
index 0000000..60a2820
--- /dev/null
@@ -0,0 +1,135 @@
+/*-
+ * ============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 void 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);
+    }
+
+    /**
+     * 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 (file)
index 0000000..14edc3a
--- /dev/null
@@ -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 (file)
index 0000000..6a52e3f
--- /dev/null
@@ -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 (file)
index 0000000..513f339
--- /dev/null
@@ -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 (file)
index 0000000..c95425e
--- /dev/null
@@ -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 BasicAaiOperator<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/AaiGetOperatorTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java
new file mode 100644 (file)
index 0000000..ebe9535
--- /dev/null
@@ -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 AaiGetOperatorTest extends BasicAaiOperator<Void> {
+
+    private static final String INPUT_FIELD = "input";
+    private static final String TEXT = "my-text";
+
+    private AaiGetOperation oper;
+
+    public AaiGetOperatorTest() {
+        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 (file)
index 0000000..39ed6fe
--- /dev/null
@@ -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 BasicAaiOperator<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/BasicAaiOperator.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java
new file mode 100644 (file)
index 0000000..50b562a
--- /dev/null
@@ -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 BasicAaiOperator<Q> extends BasicHttpOperation<Q> {
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicAaiOperator() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicAaiOperator(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"));
+    }
+}
index 74bff9a..26eb7c1 100644 (file)
   ============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>
+    </dependencies>
 </project>
index 0da1e2a..2491c33 100644 (file)
@@ -33,17 +33,19 @@ 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.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 @@ public class AppcActorServiceProvider extends ActorImpl {
     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 @@ public class AppcActorServiceProvider extends ActorImpl {
     }
 
     /**
-     * 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 @@ public class AppcActorServiceProvider extends ActorImpl {
         }
     }
 
+    // **HERE**
 }
index 9d42c49..406722e 100644 (file)
@@ -25,6 +25,8 @@ 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;
@@ -47,6 +49,14 @@ public abstract class SdncOperation extends HttpOperation<SdncResponse> {
         super(params, operator, SdncResponse.class);
     }
 
+    /**
+     * Starts the GUARD.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        return startGuardAsync();
+    }
+
     @Override
     protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
 
@@ -59,7 +69,7 @@ public abstract class SdncOperation extends HttpOperation<SdncResponse> {
         headers.put("Accept", MediaType.APPLICATION_JSON);
         String url = makeUrl();
 
-        logRestRequest(url, request);
+        logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
 
         // @formatter:off
         return handleResponse(outcome, url,
index d8c707c..deafc4e 100644 (file)
@@ -23,6 +23,7 @@ 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;
@@ -30,7 +31,6 @@ import static org.mockito.Mockito.when;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.BiFunction;
 import org.onap.policy.common.utils.coder.CoderException;
@@ -100,7 +100,10 @@ public abstract class BasicSdncOperator extends BasicHttpOperation<SdncRequest>
         verify(client).post(callbackCaptor.capture(), any(), requestCaptor.capture(), any());
         callbackCaptor.getValue().completed(rawResponse);
 
-        assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+        executor.runAll(100);
+        assertTrue(future2.isDone());
+
+        assertEquals(PolicyResult.SUCCESS, future2.get().getResult());
 
         return requestCaptor.getValue().getEntity();
     }
index 6b05807..3a10fa3 100644 (file)
             <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>
index 15e4848..e160479 100644 (file)
@@ -33,8 +33,10 @@ 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;
@@ -89,6 +91,7 @@ public class BasicHttpOperation<Q> {
     protected VirtualControlLoopEvent event;
     protected ControlLoopEventContext context;
     protected OperationOutcome outcome;
+    protected PseudoExecutor executor;
 
     /**
      * Constructs the object using a default actor and operation name.
@@ -122,6 +125,8 @@ public class BasicHttpOperation<Q> {
         future = new CompletableFuture<>();
         when(client.getBaseUrl()).thenReturn(BASE_URI);
 
+        executor = new PseudoExecutor();
+
         makeContext();
 
         outcome = params.makeOutcome();
@@ -132,6 +137,8 @@ public class BasicHttpOperation<Q> {
     /**
      * 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());
@@ -142,8 +149,8 @@ public class BasicHttpOperation<Q> {
 
         context = new ControlLoopEventContext(event);
 
-        params = ControlLoopOperationParams.builder().context(context).actorService(service).actor(actorName)
-                        .operation(operationName).targetEntity(TARGET_ENTITY).build();
+        params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service)
+                        .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).build();
     }
 
     /**
@@ -166,4 +173,18 @@ public class BasicHttpOperation<Q> {
     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);
+        };
+    }
 }
index c33483d..096b8b8 100644 (file)
@@ -24,7 +24,11 @@ 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;
 
@@ -55,7 +59,7 @@ public class BasicHttpOperationTest {
     }
 
     @Test
-    public void testSetUp() {
+    public void testSetUp() throws Exception {
         assertNotNull(oper.client);
         assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT));
         assertEquals(200, oper.rawResponse.getStatus());
@@ -63,6 +67,7 @@ public class BasicHttpOperationTest {
         assertEquals(BasicHttpOperation.BASE_URI, oper.client.getBaseUrl());
         assertNotNull(oper.context);
         assertNotNull(oper.outcome);
+        assertNotNull(oper.executor);
         assertTrue(oper.operator.isAlive());
     }
 
@@ -79,6 +84,7 @@ public class BasicHttpOperationTest {
 
         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());
@@ -101,4 +107,23 @@ public class BasicHttpOperationTest {
         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());
+    }
 }
index 2886b1f..24c2cfc 100644 (file)
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
  * {@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 @@ public class ActorService extends StartConfigPartial<Map<String, Object>> {
     }
 
     @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/impl/BidirectionalTopicActor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java
new file mode 100644 (file)
index 0000000..1e44a17
--- /dev/null
@@ -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 (file)
index 0000000..f82015d
--- /dev/null
@@ -0,0 +1,253 @@
+/*-
+ * ============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);
+                postProcessResponse(outcome, rawResponse, response);
+                return outcome;
+
+            case FAILURE:
+                logger.info("{}.{} request failed for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                return setOutcome(outcome, PolicyResult.FAILURE);
+
+            case STILL_WAITING:
+            default:
+                logger.info("{}.{} request incomplete for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                return null;
+        }
+    }
+
+    /**
+     * 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 (file)
index 0000000..51689e4
--- /dev/null
@@ -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);
+            }
+        };
+    }
+}
index c4bf5f4..f1829d7 100644 (file)
@@ -33,9 +33,7 @@ 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.Coder;
 import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
@@ -52,7 +50,6 @@ import org.slf4j.LoggerFactory;
 @Getter
 public abstract class HttpOperation<T> extends OperationPartial {
     private static final Logger logger = LoggerFactory.getLogger(HttpOperation.class);
-    private static final Coder coder = new StandardCoder();
 
     /**
      * Operator that created this operation.
@@ -171,7 +168,7 @@ public abstract class HttpOperation<T> extends OperationPartial {
 
         String strResponse = HttpClient.getBody(rawResponse, String.class);
 
-        logRestResponse(url, strResponse);
+        logMessage(EventType.IN, CommInfrastructure.REST, url, strResponse);
 
         T response;
         if (responseClass == String.class) {
@@ -181,9 +178,9 @@ public abstract class HttpOperation<T> extends OperationPartial {
             try {
                 response = makeCoder().decode(strResponse, responseClass);
             } catch (CoderException e) {
-                logger.warn("{}.{} cannot decode response with http error code {} for {}", params.getActor(),
-                                params.getOperation(), rawResponse.getStatus(), params.getRequestId(), e);
-                return setOutcome(outcome, PolicyResult.FAILURE_EXCEPTION);
+                logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId(), e);
+                throw new IllegalArgumentException("cannot decode response");
             }
         }
 
@@ -224,63 +221,10 @@ public abstract class HttpOperation<T> extends OperationPartial {
         return (rawResponse.getStatus() == 200);
     }
 
-    /**
-     * 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 <Q> void logRestRequest(String url, Q request) {
-        String json;
-        try {
-            if (request == null) {
-                json = null;
-            } else if (request instanceof String) {
-                json = request.toString();
-            } else {
-                json = makeCoder().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 <S> void logRestResponse(String url, S 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) {
-            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);
-    }
-
-    // these may be overridden by junit tests
-
-    protected Coder makeCoder() {
-        return coder;
+    @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;
     }
 }
index d00b88b..0b34971 100644 (file)
 
 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;
@@ -28,6 +33,14 @@ 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;
@@ -53,8 +66,8 @@ import org.slf4j.LoggerFactory;
  * 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
@@ -470,103 +483,110 @@ public abstract class OperationPartial implements Operation {
      * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
      * any outstanding futures when one completes.
      *
-     * @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
+     * @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(List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
+    protected CompletableFuture<OperationOutcome> anyOf(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
 
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = anyOf(arrFutures);
-        return result;
+        return anyOf(Arrays.asList(futureMakers));
     }
 
     /**
-     * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any
-     * outstanding futures when one completes.
+     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+     * any outstanding futures when one completes.
      *
-     * @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
+     * @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(
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
+                    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];
         }
 
-        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
+        CompletableFuture.anyOf(futures).thenApply(outcome -> (OperationOutcome) outcome)
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
 
         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)}).
+     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
      *
-     * @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
+     * @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(List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
+    protected CompletableFuture<OperationOutcome> allOf(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
 
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = allOf(arrFutures);
-        return result;
+        return allOf(Arrays.asList(futureMakers));
     }
 
     /**
-     * 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)}).
+     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
      *
-     * @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
+     * @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(
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        if (futures.length == 1) {
-            return futures[0];
-        }
+                    List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
 
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        attachFutures(controller, futures);
+        Queue<OperationOutcome> outcomes = new LinkedList<>();
 
-        OperationOutcome[] outcomes = new OperationOutcome[futures.length];
+        CompletableFuture<OperationOutcome>[] futures =
+                        attachFutures(controller, futureMakers, future -> future.thenApply(outcome -> {
+                            synchronized (outcomes) {
+                                outcomes.add(outcome);
+                            }
+                            return outcome;
+                        }));
 
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] futures2 = new CompletableFuture[futures.length];
+        if (futures.length == 0) {
+            // no futures were started
+            return null;
+        }
 
-        // 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);
+        if (futures.length == 1) {
+            return futures[0];
         }
 
         // @formatter:off
-        CompletableFuture.allOf(futures2)
+        CompletableFuture.allOf(futures)
                         .thenApply(unused -> combineOutcomes(outcomes))
                         .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
         // @formatter:on
@@ -575,22 +595,62 @@ public abstract class OperationPartial implements Operation {
     }
 
     /**
-     * Attaches the given futures to the controller.
+     * Invokes the functions to create the futures and attaches them 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) {
+     * @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;
+        }
 
-        if (futures.length == 0) {
-            throw new IllegalArgumentException("empty list of futures");
+        // 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;
+            }
         }
 
-        // attach each task
-        for (CompletableFuture<OperationOutcome> future : futures) {
-            controller.add(future);
+        @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);
     }
 
     /**
@@ -599,15 +659,13 @@ public abstract class OperationPartial implements Operation {
      * @param outcomes outcomes to be examined
      * @return the combined outcome
      */
-    private OperationOutcome combineOutcomes(OperationOutcome[] outcomes) {
+    private OperationOutcome combineOutcomes(Queue<OperationOutcome> outcomes) {
 
         // identify the outcome with the highest priority
-        OperationOutcome outcome = outcomes[0];
+        OperationOutcome outcome = outcomes.remove();
         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];
+        for (OperationOutcome outcome2 : outcomes) {
             int priority2 = detmPriority(outcome2);
 
             if (priority2 > priority) {
@@ -656,71 +714,113 @@ public abstract class OperationPartial implements Operation {
     }
 
     /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
+     * 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 controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param outcome outcome of the previous task
-     * @param task task to be performed
-     * @return the task, if everything checks out. Otherwise, it returns an incomplete
-     *         future and completes the controller instead
+     * @param futureMakers functions to make the futures
+     * @return a future to cancel the sequence or await the outcome
      */
-    // @formatter:off
-    protected CompletableFuture<OperationOutcome> doTask(
-                    PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess, OperationOutcome outcome,
-                    CompletableFuture<OperationOutcome> task) {
-        // @formatter:on
+    protected CompletableFuture<OperationOutcome> sequence(
+                    @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
 
-        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 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;
         }
 
-        return controller.wrap(task);
+        /*
+         * 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;
     }
 
     /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
+     * Executes the next task in the queue, if the previous outcome was successful.
      *
-     * @param controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param task function to start the task to be performed
-     * @return a function to perform the task. If everything checks out, then it returns
-     *         the task. Otherwise, it returns an incomplete future and completes the
-     *         controller instead
+     * @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
      */
-    // @formatter:off
-    protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> nextTaskOnSuccess(
                     PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess,
-                    Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) {
-        // @formatter:on
+                    Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) {
 
         return outcome -> {
-
-            if (!controller.isRunning()) {
-                return new CompletableFuture<>();
+            if (!isSuccess(outcome)) {
+                // return the failure
+                return CompletableFuture.completedFuture(outcome);
             }
 
-            if (checkSuccess && !isSuccess(outcome)) {
-                controller.complete(outcome);
-                return new CompletableFuture<>();
+            CompletableFuture<OperationOutcome> nextTask = getNextTask(taskQueue);
+            if (nextTask == null) {
+                // no tasks - just return the success
+                return CompletableFuture.completedFuture(outcome);
             }
 
-            return controller.wrap(task.apply(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.
@@ -809,6 +909,38 @@ public abstract class OperationPartial implements Operation {
         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
 
     /**
@@ -841,4 +973,10 @@ public abstract class OperationPartial implements Operation {
     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/parameters/BidirectionalTopicActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java
new file mode 100644 (file)
index 0000000..291aeeb
--- /dev/null
@@ -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;
+}
@@ -29,34 +29,36 @@ import org.onap.policy.common.parameters.annotations.NotBlank;
 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 (file)
index 0000000..dc6f2b6
--- /dev/null
@@ -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);
+    }
+}
index 275c8bc..d589e1d 100644 (file)
 
 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 @@ public class HttpActorParams {
     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.
-     */
-    @Min(0)
-    private int 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
+     * Amount of time, in seconds, to wait for the HTTP request to complete. The default
+     * is 90 seconds.
      */
-    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;
 }
index 93711c0..2d3ab8b 100644 (file)
@@ -48,12 +48,13 @@ public class HttpParams {
     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 int timeoutSec = 0;
+    @Min(1)
+    private int timeoutSec;
 
 
     /**
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 (file)
index 0000000..30ee1e2
--- /dev/null
@@ -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 (file)
index 0000000..1041187
--- /dev/null
@@ -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 (file)
index 0000000..2d98b66
--- /dev/null
@@ -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 (file)
index 0000000..fc57273
--- /dev/null
@@ -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 (file)
index 0000000..fcb4635
--- /dev/null
@@ -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 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);
+        }
+    }
+}
index 851a791..efc7bb8 100644 (file)
@@ -65,7 +65,7 @@ public class ActorServiceTest {
     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/impl/BidirectionalTopicActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java
new file mode 100644 (file)
index 0000000..e1606ae
--- /dev/null
@@ -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 (file)
index 0000000..ceb63fe
--- /dev/null
@@ -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 (file)
index 0000000..4fae782
--- /dev/null
@@ -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;
+        }
+    }
+}
index 8ce3b32..80b1d42 100644 (file)
@@ -52,7 +52,12 @@ public class HttpActorTest {
         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 @@ public class HttpActorTest {
                         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);
     }
index 19f781d..50cb8fa 100644 (file)
@@ -22,6 +22,7 @@ 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;
@@ -31,9 +32,7 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import ch.qos.logback.classic.Logger;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.UUID;
@@ -64,6 +63,7 @@ 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;
@@ -71,12 +71,10 @@ 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.Coder;
 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.common.utils.test.log.logback.ExtractAppender;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
 import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
@@ -85,7 +83,6 @@ import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopE
 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.slf4j.LoggerFactory;
 
 public class HttpOperationTest {
 
@@ -95,18 +92,11 @@ public class HttpOperationTest {
     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 MY_REQUEST = "my-request";
     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();
 
-    /**
-     * Used to attach an appender to the class' logger.
-     */
-    private static final Logger logger = (Logger) LoggerFactory.getLogger(HttpOperation.class);
-    private static final ExtractAppender appender = new ExtractAppender();
-
     /**
      * {@code True} if the server should reject the request, {@code false} otherwise.
      */
@@ -163,14 +153,6 @@ public class HttpOperationTest {
 
         HttpClientFactoryInstance.getClientFactory()
                         .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
-
-        /**
-         * Attach appender to the logger.
-         */
-        appender.setContext(logger.getLoggerContext());
-        appender.start();
-
-        logger.addAppender(appender);
     }
 
     /**
@@ -178,8 +160,6 @@ public class HttpOperationTest {
      */
     @AfterClass
     public static void tearDownAfterClass() {
-        appender.stop();
-
         HttpClientFactoryInstance.getClientFactory().destroy();
         HttpServletServerFactoryInstance.getServerFactory().destroy();
     }
@@ -192,8 +172,6 @@ public class HttpOperationTest {
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        appender.clearExtractions();
-
         rejectRequest = false;
         nget = 0;
         npost = 0;
@@ -259,9 +237,9 @@ public class HttpOperationTest {
     @Test
     public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
 
-        // no default yet
-        assertEquals(0L, oper.getTimeoutMs(null));
-        assertEquals(0L, oper.getTimeoutMs(0));
+        // use value from operator
+        assertEquals(1000L, oper.getTimeoutMs(null));
+        assertEquals(1000L, oper.getTimeoutMs(0));
 
         // should use given value
         assertEquals(20 * 1000L, oper.getTimeoutMs(20));
@@ -359,8 +337,7 @@ public class HttpOperationTest {
     public void testProcessResponseDecodeExcept() throws CoderException {
         MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
 
-        assertSame(outcome, oper2.processResponse(outcome, PATH, response));
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+        assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
     }
 
     @Test
@@ -441,96 +418,6 @@ public class HttpOperationTest {
         assertEquals(PolicyResult.SUCCESS, outcome.getResult());
     }
 
-    @Test
-    public void testLogRestRequest() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        oper.logRestRequest(PATH, new MyRequest());
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(PATH).contains("{\n  \"input\": \"some input\"\n}");
-
-        // log a plain string
-        appender.clearExtractions();
-        oper.logRestRequest(PATH, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(PATH).contains(MY_REQUEST);
-
-        // log a null request
-        appender.clearExtractions();
-        oper.logRestRequest(PATH, null);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        // exception from coder
-        oper = new MyGetOperation<>(String.class) {
-            @Override
-            protected Coder makeCoder() {
-                return new StandardCoder() {
-                    @Override
-                    public String encode(Object object, boolean pretty) throws CoderException {
-                        throw new CoderException(EXPECTED_EXCEPTION);
-                    }
-                };
-            }
-        };
-
-        appender.clearExtractions();
-        oper.logRestRequest(PATH, new MyRequest());
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print request");
-        assertThat(output.get(1)).contains(PATH);
-    }
-
-    @Test
-    public void testLogRestResponse() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        oper.logRestResponse(PATH, new MyResponse());
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(PATH).contains("{\n  \"output\": \"some output\"\n}");
-
-        // log a plain string
-        appender.clearExtractions();
-        oper.logRestResponse(PATH, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        // log a null response
-        appender.clearExtractions();
-        oper.logRestResponse(PATH, null);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(PATH).contains("null");
-
-        // exception from coder
-        oper = new MyGetOperation<>(String.class) {
-            @Override
-            protected Coder makeCoder() {
-                return new StandardCoder() {
-                    @Override
-                    public String encode(Object object, boolean pretty) throws CoderException {
-                        throw new CoderException(EXPECTED_EXCEPTION);
-                    }
-                };
-            }
-        };
-
-        appender.clearExtractions();
-        oper.logRestResponse(PATH, new MyResponse());
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print response");
-        assertThat(output.get(1)).contains(PATH);
-    }
-
     @Test
     public void testMakeDecoder() {
         assertNotNull(oper.makeCoder());
@@ -569,7 +456,7 @@ public class HttpOperationTest {
     private void initOper(HttpOperator operator, String clientName) {
         operator.stop();
 
-        HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).build();
+        HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build();
         Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
         operator.configure(mapParams);
         operator.start();
@@ -614,7 +501,7 @@ public class HttpOperationTest {
             headers.put("Accept", MediaType.APPLICATION_JSON);
             String url = makeUrl();
 
-            logRestRequest(url, null);
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
 
             // @formatter:off
             return handleResponse(outcome, url,
@@ -640,7 +527,7 @@ public class HttpOperationTest {
             headers.put("Accept", MediaType.APPLICATION_JSON);
             String url = makeUrl();
 
-            logRestRequest(url, request);
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
 
             // @formatter:off
             return handleResponse(outcome, url,
@@ -666,7 +553,7 @@ public class HttpOperationTest {
             headers.put("Accept", MediaType.APPLICATION_JSON);
             String url = makeUrl();
 
-            logRestRequest(url, request);
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
 
             // @formatter:off
             return handleResponse(outcome, url,
@@ -687,7 +574,7 @@ public class HttpOperationTest {
             headers.put("Accept", MediaType.APPLICATION_JSON);
             String url = makeUrl();
 
-            logRestRequest(url, null);
+            logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
 
             // @formatter:off
             return handleResponse(outcome, url,
index 0d5cb24..67ac27c 100644 (file)
@@ -20,8 +20,8 @@
 
 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.assertThatIllegalStateException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,13 +30,13 @@ 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.Queue;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -46,42 +46,59 @@ 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.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.actorserviceprovider.pipeline.PipelineControllerFuture;
 import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
 
 public class OperationPartialTest {
-    private static final int MAX_PARALLEL_REQUESTS = 10;
+    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 TARGET = "my-target";
+    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 MyExec executor;
+    private PseudoExecutor executor;
     private ControlLoopOperationParams params;
 
     private MyOper oper;
@@ -96,6 +113,28 @@ public class OperationPartialTest {
 
     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}.
      */
@@ -105,11 +144,11 @@ public class OperationPartialTest {
         event.setRequestId(REQ_ID);
 
         context = new ControlLoopEventContext(event);
-        executor = new MyExec();
+        executor = new PseudoExecutor();
 
         params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
                         .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
-                        .startCallback(this::starter).targetEntity(TARGET).build();
+                        .startCallback(this::starter).targetEntity(MY_SINK).build();
 
         operator = new OperatorPartial(ACTOR, OPERATION) {
             @Override
@@ -210,19 +249,19 @@ public class OperationPartialTest {
      */
     @Test
     public void testStartMultiple() {
-        for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
+        for (int count = 0; count < MAX_PARALLEL; ++count) {
             oper.start();
         }
 
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS * MAX_PARALLEL));
 
         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);
+        assertEquals(MAX_PARALLEL, numStart);
+        assertEquals(MAX_PARALLEL, oper.getCount());
+        assertEquals(MAX_PARALLEL, numEnd);
     }
 
     /**
@@ -255,7 +294,7 @@ public class OperationPartialTest {
         oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
 
         oper.start().cancel(false);
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
 
         assertNull(opstart);
         assertNull(opend);
@@ -296,7 +335,7 @@ public class OperationPartialTest {
     @Test
     public void testStartOperationAsync() {
         oper.start();
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
 
         assertEquals(1, oper.getCount());
     }
@@ -331,14 +370,14 @@ public class OperationPartialTest {
         outcome.setResult(PolicyResult.FAILURE);
 
         // incorrect actor
-        outcome.setActor(TARGET);
+        outcome.setActor(MY_SINK);
         assertFalse(oper.isActorFailed(outcome));
         outcome.setActor(null);
         assertFalse(oper.isActorFailed(outcome));
         outcome.setActor(ACTOR);
 
         // incorrect operation
-        outcome.setOperation(TARGET);
+        outcome.setOperation(MY_SINK);
         assertFalse(oper.isActorFailed(outcome));
         outcome.setOperation(null);
         assertFalse(oper.isActorFailed(outcome));
@@ -356,7 +395,7 @@ public class OperationPartialTest {
         OperationPartial oper2 = new OperationPartial(params, operator) {};
 
         oper2.start();
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
 
         assertNotNull(opend);
         assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
@@ -520,14 +559,14 @@ public class OperationPartialTest {
         // wrong actor - should be false
         outcome.setActor(null);
         assertFalse(oper.isSameOperation(outcome));
-        outcome.setActor(TARGET);
+        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(TARGET);
+        outcome.setOperation(MY_SINK);
         assertFalse(oper.isSameOperation(outcome));
         outcome.setOperation(OPERATION);
 
@@ -585,43 +624,47 @@ public class OperationPartialTest {
     @Test
     public void testAnyOf() throws Exception {
         // first task completes, others do not
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
 
         final OperationOutcome outcome = params.makeOutcome();
 
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
-        tasks.add(new CompletableFuture<>());
+        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());
+        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 = new LinkedList<>();
-
-        tasks.add(new CompletableFuture<>());
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
+        tasks.clear();
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
+        tasks.add(() -> new CompletableFuture<>());
 
         result = oper.anyOf(tasks);
-        assertTrue(executor.runAll());
-
+        assertTrue(executor.runAll(MAX_REQUESTS));
         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));
+        tasks.clear();
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> new CompletableFuture<>());
+        tasks.add(() -> CompletableFuture.completedFuture(outcome));
 
         result = oper.anyOf(tasks);
-        assertTrue(executor.runAll());
-
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertTrue(result.isDone());
         assertSame(outcome, result.get());
     }
@@ -632,54 +675,82 @@ public class OperationPartialTest {
     @Test
     @SuppressWarnings("unchecked")
     public void testAnyOfEdge() throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
 
         // zero items: check both using a list and using an array
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf(tasks));
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf());
+        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);
+        tasks.add(() -> future1);
 
         assertSame(future1, oper.anyOf(tasks));
-        assertSame(future1, oper.anyOf(future1));
+        assertSame(future1, oper.anyOf(() -> future1));
     }
 
-    /**
-     * Tests both flavors of allOf(), because one invokes the other.
-     */
     @Test
-    public void testAllOf() throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+    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<>();
 
-        tasks.add(future1);
-        tasks.add(future2);
-        tasks.add(future3);
+        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());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertFalse(result.isDone());
         future1.complete(outcome);
 
         // complete 3 before 2
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertFalse(result.isDone());
         future3.complete(outcome);
 
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertFalse(result.isDone());
         future2.complete(outcome);
 
         // all of them are now done
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertTrue(result.isDone());
         assertSame(outcome, result.get());
     }
@@ -690,18 +761,41 @@ public class OperationPartialTest {
     @Test
     @SuppressWarnings("unchecked")
     public void testAllOfEdge() throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+        List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
 
         // zero items: check both using a list and using an array
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf(tasks));
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf());
+        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);
+        tasks.add(() -> future1);
 
         assertSame(future1, oper.allOf(tasks));
-        assertSame(future1, oper.allOf(future1));
+        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
@@ -715,12 +809,14 @@ public class OperationPartialTest {
         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));
+        // 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());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         assertTrue(result.isDone());
         assertNull(result.get());
 
@@ -728,26 +824,85 @@ public class OperationPartialTest {
         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()));
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+        tasks.add(() -> CompletableFuture.failedFuture(except));
+        tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
         result = oper.allOf(tasks);
 
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         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<>();
+    /**
+     * 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));
+            tasks.add(() -> CompletableFuture.completedFuture(outcome));
 
             if (count == expected) {
                 expectedOutcome = outcome;
@@ -756,17 +911,11 @@ public class OperationPartialTest {
 
         CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
 
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
         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() throws CoderException {
         assertEquals(1, oper.detmPriority(null));
@@ -790,210 +939,6 @@ public class OperationPartialTest {
         assertEquals(1, 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(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(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(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(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(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(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(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(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.
      */
@@ -1014,7 +959,7 @@ public class OperationPartialTest {
         oper = new MyOper();
 
         future.set(oper.start());
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
 
         // should have only run once
         assertEquals(1, numStart);
@@ -1036,7 +981,7 @@ public class OperationPartialTest {
         oper = new MyOper();
 
         future.set(oper.start());
-        assertTrue(executor.runAll());
+        assertTrue(executor.runAll(MAX_REQUESTS));
 
         // should not have been set
         assertNull(opend);
@@ -1091,6 +1036,62 @@ public class OperationPartialTest {
         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));
@@ -1188,7 +1189,7 @@ public class OperationPartialTest {
 
         manipulator.accept(future);
 
-        assertTrue(testName, executor.runAll());
+        assertTrue(testName, executor.runAll(MAX_REQUESTS));
 
         assertEquals(testName, expectedCallbacks, numStart);
         assertEquals(testName, expectedCallbacks, numEnd);
@@ -1217,6 +1218,30 @@ public class OperationPartialTest {
         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;
@@ -1267,36 +1292,4 @@ public class OperationPartialTest {
             return 0L;
         }
     }
-
-    /**
-     * Executor that will run tasks until the queue is empty or a maximum number of tasks
-     * have been executed. Doesn't actually run anything until {@link #runAll()} is
-     * invoked.
-     */
-    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();
-        }
-    }
 }
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 (file)
index 0000000..1f38ad3
--- /dev/null
@@ -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;
+    }
+}
@@ -29,44 +29,46 @@ 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.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 (file)
index 0000000..9014203
--- /dev/null
@@ -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;
+    }
+}
index daa0aff..9e70853 100644 (file)
 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 {
 
@@ -48,63 +43,40 @@ public class HttpActorParamsTest {
     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 @@ public class HttpActorParamsTest {
         HttpActorParams params2 = new HttpActorParams();
         params2.setClientName(CLIENT);
         params2.setTimeoutSec(TIMEOUT);
-        params2.setPath(paths);
+        params2.setOperation(operations);
 
         return params2;
     }
index ae4a79f..fdfb4b4 100644 (file)
@@ -54,7 +54,7 @@ public class HttpParamsTest {
         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/topic/BidirectionalTopicHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java
new file mode 100644 (file)
index 0000000..54d56de
--- /dev/null
@@ -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 (file)
index 0000000..a01159b
--- /dev/null
@@ -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 (file)
index 0000000..19df9c2
--- /dev/null
@@ -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 (file)
index 0000000..3012ff6
--- /dev/null
@@ -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);
+        }
+    }
+}
index 8604688..7b5b9fc 100644 (file)
         <appender-ref ref="STDOUT" />
     </logger>
 
-    <!-- this is required for HttpOperationTest -->
-    <logger name="org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation" level="info" additivity="false">
+    <!-- this is required for OperationPartialTest -->
+    <logger
+            name="org.onap.policy.controlloop.actorserviceprovider.impl.OperationPartial"
+            level="info" additivity="false">
         <appender-ref ref="STDOUT" />
     </logger>
 </configuration>
index 029ac7f..3089114 100644 (file)
@@ -37,6 +37,7 @@
   <modules>
     <module>actorServiceProvider</module>
     <module>actor.test</module>
+    <module>actor.aai</module>
     <module>actor.appc</module>
     <module>actor.vfc</module>
     <module>actor.sdnc</module>
index 43fa6c0..06f6030 100644 (file)
@@ -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 @@ package org.onap.policy.models.pdp.concepts;
 
 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 @@ public class PdpEngineWorkerStatistics implements Serializable {
      *
      * @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;
index ad5547e..1ba983b 100644 (file)
@@ -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 @@ package org.onap.policy.models.pdp.concepts;
 
 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 @@ import org.onap.policy.models.base.PfUtils;
  *
  * @author Ram Krishna Verma (ram.krishna.verma@est.tech)
  */
-@Getter
-@Setter
-@ToString
+@Data
 @NoArgsConstructor
 public class PdpStatistics {
 
@@ -57,7 +55,7 @@ public class PdpStatistics {
      *
      * @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 (file)
index 0000000..c0d2ba6
--- /dev/null
@@ -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
index 08098cc..adf9b9f 100644 (file)
@@ -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 @@ package org.onap.policy.models.pdp.concepts;
 
 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();
-
-        // 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++);
+        PdpStatistics orig = createPdpStatistics();
+        PdpStatistics copied = new PdpStatistics(orig);
+        assertEquals(orig, copied);
+    }
 
-        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;
     }
 }
index 62f0c5b..0b22e1b 100644 (file)
@@ -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.
 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;
     }
 }
index b1a1795..5c7caae 100644 (file)
@@ -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" />
index 0eee4b2..30428b0 100644 (file)
@@ -1,6 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  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 @@ public class PolicyLegacyOperationalPersistenceTest {
 
     // @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
 
@@ -148,8 +149,8 @@ public class PolicyLegacyOperationalPersistenceTest {
     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 =
index 0cdc2ad..3116868 100644 (file)
@@ -36,6 +36,7 @@ import org.junit.Before;
 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.provider.PolicyModelsProviderParameters;
 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 @@ import org.yaml.snakeyaml.Yaml;
  * @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,14 +83,6 @@ public class PolicyToscaPersistenceTest {
         createPolicyTypes();
     }
 
-    /**
-     * Set up standard coder.
-     */
-    @Before
-    public void setupStandardCoder() {
-        standardCoder = new StandardCoder();
-    }
-
     @After
     public void teardown() throws Exception {
         databaseProvider.close();
@@ -103,29 +100,44 @@ public class PolicyToscaPersistenceTest {
             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 @@ public class PolicyToscaPersistenceTest {
         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);
index 77062ce..d63c415 100644 (file)
@@ -34,6 +34,8 @@
         <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>
index 6e60303..a65cdbe 100644 (file)
@@ -423,6 +423,7 @@ public class AuthorativeToscaProvider {
      */
     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;
index f5335fe..09cc6c0 100644 (file)
@@ -93,13 +93,13 @@ public class LegacyProvider {
 
         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 @@ public class LegacyProvider {
         return updatedLegacyGuardPolicyMap;
     }
 
-
     /**
      * Delete legacy guard policy.
      *
index 2816df0..b500a8b 100644 (file)
@@ -261,6 +261,11 @@ public class JpaToscaPolicy extends JpaToscaEntityType<ToscaPolicy> implements P
     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"));
index b068bea..423620d 100644 (file)
@@ -203,6 +203,11 @@ public class JpaToscaPolicyType extends JpaToscaEntityType<ToscaPolicyType> impl
     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);
         }
index 9c7d6d3..c537bbc 100644 (file)
@@ -106,7 +106,7 @@ public class SimpleToscaProvider {
 
         PfValidationResult result = serviceTemplateToWrite.validate(new PfValidationResult());
         if (!result.isValid()) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
         }
 
         new SimpleToscaServiceTemplateProvider().write(dao, serviceTemplateToWrite);
@@ -264,6 +264,9 @@ public class SimpleToscaProvider {
                     "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();
 
@@ -271,11 +274,13 @@ public class SimpleToscaProvider {
                 JpaToscaServiceTemplate dataTypeEntityTreeServiceTemplate =
                         getDataTypes(dao, referencedDataTypeKey.getName(), referencedDataTypeKey.getVersion());
 
-                serviceTemplate =
-                        ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeEntityTreeServiceTemplate);
+                dataTypeServiceTemplate = ToscaServiceTemplateUtils.addFragment(dataTypeServiceTemplate,
+                        dataTypeEntityTreeServiceTemplate);
             }
         }
 
+        serviceTemplate = ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeServiceTemplate);
+
         LOGGER.debug("<-getPolicyTypes: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
         return serviceTemplate;
     }
@@ -372,7 +377,11 @@ public class SimpleToscaProvider {
             throws PfModelException {
         LOGGER.debug("->getPolicies: name={}, version={}", name, version);
 
-        JpaToscaServiceTemplate serviceTemplate = getServiceTemplate(dao);
+        JpaToscaServiceTemplate dbServiceTemplate = getServiceTemplate(dao);
+
+        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate(dbServiceTemplate);
+        serviceTemplate.setDataTypes(new JpaToscaDataTypes());
+        serviceTemplate.setPolicyTypes(new JpaToscaPolicyTypes());
 
         if (!ToscaUtils.doPoliciesExist(serviceTemplate)) {
             throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
@@ -386,18 +395,20 @@ public class SimpleToscaProvider {
                     "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()) {
-            if (policy.getDerivedFrom() != null) {
-                JpaToscaServiceTemplate referencedEntitiesServiceTemplate =
-                        getPolicyTypes(dao, policy.getDerivedFrom().getName(), policy.getDerivedFrom().getVersion());
+            JpaToscaServiceTemplate referencedEntitiesServiceTemplate =
+                    getPolicyTypes(dao, policy.getType().getName(), policy.getType().getVersion());
 
-                serviceTemplate =
-                        ToscaServiceTemplateUtils.addFragment(serviceTemplate, referencedEntitiesServiceTemplate);
-            }
+            returnServiceTemplate.getTopologyTemplate().getPolicies().getConceptMap().put(policy.getKey(), policy);
+            returnServiceTemplate =
+                    ToscaServiceTemplateUtils.addFragment(returnServiceTemplate, referencedEntitiesServiceTemplate);
         }
 
-        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
-        return serviceTemplate;
+        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, returnServiceTemplate);
+        return returnServiceTemplate;
     }
 
     /**
@@ -497,7 +508,7 @@ public class SimpleToscaProvider {
         if (policyType == null) {
             String errorMessage =
                     "policy type " + policyTypeKey.getId() + " for policy " + policy.getId() + " does not exist";
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, errorMessage);
         }
     }
 
@@ -526,7 +537,7 @@ public class SimpleToscaProvider {
         // We should have one and only one returned entry
         if (filterdPolicyTypeList.size() != 1) {
             String errorMessage = "search for latest policy type " + policyTypeName + " returned more than one entry";
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            throw new PfModelRuntimeException(Response.Status.CONFLICT, errorMessage);
         }
 
         return (JpaToscaPolicyType) filterdPolicyTypeList.get(0);
index 87b499b..6f21c19 100644 (file)
@@ -74,7 +74,7 @@ public class ToscaServiceTemplateUtils {
         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 @@ public class ToscaServiceTemplateUtils {
 
         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;
index cc04319..b75273e 100644 (file)
@@ -147,7 +147,7 @@ public final class ToscaUtils {
             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);
         }
     }
 
@@ -263,7 +263,7 @@ public final class ToscaUtils {
         }
 
         if (!result.isValid()) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
         }
 
         entityTypes.getConceptMap().entrySet()
index 0bf3710..858ac09 100644 (file)
@@ -87,6 +87,7 @@ public class ToscaPolicyFilterTest {
         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 @@ public class ToscaPolicyFilterTest {
         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 class ToscaPolicyFilterTest {
     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 @@ public class ToscaPolicyFilterTest {
 
         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 @@ public class ToscaPolicyFilterTest {
 
         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 @@ public class ToscaPolicyFilterTest {
         // 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 class ToscaPolicyFilterTest {
     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 @@ public class ToscaPolicyFilterTest {
 
         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 @@ public class ToscaPolicyFilterTest {
         filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").typeVersion(VERSION_000)
                 .build();
         filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
     }
 }
index 30696ce..0f038d3 100644 (file)
@@ -163,7 +163,7 @@ public class ToscaPolicyTypeFilterTest {
     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 @@ public class ToscaPolicyTypeFilterTest {
         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();
index ae350bd..3f0d9e2 100644 (file)
@@ -58,11 +58,11 @@ import org.yaml.snakeyaml.Yaml;
  */
 public class AuthorativeToscaProviderPolicyTypeTest {
     private static final String VERSION = "version";
-    private static final String POLICY_NO_VERSION_VERSION0 = "onap.policies.NoVersion:0.0.0";
+    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 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -150,7 +150,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), createdPolicyType.getDescription()));
 
         List<ToscaPolicyType> gotPolicyTypeList =
-                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_000);
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
@@ -162,14 +162,14 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
-        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_000);
+        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_000)).hasMessageContaining("Policy Framework DAO has not been initialized");
+                VERSION_001)).hasMessageContaining("Policy Framework DAO has not been initialized");
 
-        assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_000).isEmpty());
+        assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_001).isEmpty());
     }
 
     @Test
@@ -212,7 +212,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -234,14 +234,14 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         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_NO_VERSION, VERSION_000);
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
@@ -256,7 +256,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         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()));
 
@@ -296,7 +296,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -328,7 +328,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -379,7 +379,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -395,7 +395,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
 
         assertThatThrownBy(() -> {
             new AuthorativeToscaProvider().getPolicyTypes(pfDao, policyTypeKey.getName(), policyTypeKey.getVersion());
-        }).hasMessage("policy types for onap.policies.NoVersion:0.0.0 do not exist");
+        }).hasMessage("policy types for onap.policies.NoVersion:0.0.1 do not exist");
     }
 
     @Test
index 0aa1da0..4dcfeaf 100644 (file)
@@ -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 @@ public class LegacyOperationalPolicyMapperTest {
         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);
 
index ec03122..4d0fd6f 100644 (file)
@@ -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 @@ import org.yaml.snakeyaml.Yaml;
  */
 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;
index 01a52f7..0a8283e 100644 (file)
@@ -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 @@ public class MonitoringPolicyTypeSerializationTest {
 
         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 @@ public class MonitoringPolicyTypeSerializationTest {
         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();
index ca46de1..f9a0143 100644 (file)
@@ -201,5 +201,19 @@ public class ToscaServiceTemplateUtilsTest {
         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());
     }
 }
index d6fba8f..de27dd9 100644 (file)
@@ -34,6 +34,8 @@
         <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" />
index 5923eb2..2dda556 100644 (file)
@@ -6,6 +6,7 @@ policy_types:
       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