aboutsummaryrefslogtreecommitdiffstats
path: root/controlloop/common/controller-tdjam
diff options
context:
space:
mode:
Diffstat (limited to 'controlloop/common/controller-tdjam')
-rw-r--r--controlloop/common/controller-tdjam/pom.xml245
-rw-r--r--controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/SerialWorkQueue.java123
-rw-r--r--controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/TdjamController.java833
-rw-r--r--controlloop/common/controller-tdjam/src/main/java/org/onap/policy/extension/system/NonDroolsPolicyController.java668
-rw-r--r--controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi1
-rw-r--r--controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi1
-rw-r--r--controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/TdjamTest.java159
-rw-r--r--controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/tdjam/TdjamControllerTest.java240
-rw-r--r--controlloop/common/controller-tdjam/src/test/java/org/onap/policy/extension/system/NonDroolsPolicyControllerTest.java343
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi3
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi2
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/config/event-manager.properties83
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/config/tdjam-controller.properties64
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/config/tdjam-http-client.properties52
-rw-r--r--controlloop/common/controller-tdjam/src/test/resources/logback-test.xml38
15 files changed, 2855 insertions, 0 deletions
diff --git a/controlloop/common/controller-tdjam/pom.xml b/controlloop/common/controller-tdjam/pom.xml
new file mode 100644
index 000000000..02483d340
--- /dev/null
+++ b/controlloop/common/controller-tdjam/pom.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============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=========================================================
+ -->
+
+<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.drools-applications.controlloop.common</groupId>
+ <artifactId>drools-applications-common</artifactId>
+ <version>1.7.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>controller-tdjam</artifactId>
+ <packaging>kjar</packaging>
+
+ <name>${project.artifactId}</name>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>events</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>aai</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>appc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>appclcm</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>cds</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>guard</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>sdc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>sdnc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>sdnr</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>so</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>vfc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>eventmanager</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actorServiceProvider</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.aai</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.appc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.appclcm</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.cds</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.guard</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.sdnc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.sdnr</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.so</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.vfc</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>model-yaml</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ <!-- <optional>true</optional> -->
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>rules-test</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <!--This profile is used to store Eclipse m2e settings only. It has no
+ influence on the Maven build itself. -->
+ <id>only-eclipse</id>
+ <activation>
+ <property>
+ <name>m2e.version</name>
+ </property>
+ </activation>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-maven-plugin</artifactId>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore />
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/SerialWorkQueue.java b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/SerialWorkQueue.java
new file mode 100644
index 000000000..7d83765a3
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/SerialWorkQueue.java
@@ -0,0 +1,123 @@
+/*-
+ * ============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.tdjam;
+
+import java.util.LinkedList;
+import lombok.Getter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class provides a way to handle synchronization, with minimal blocking. Requests
+ * are queued until {@link #start()} is invoked.
+ */
+public class SerialWorkQueue {
+ private static Logger logger = LoggerFactory.getLogger(SerialWorkQueue.class);
+
+ // current work list
+ private LinkedList<Runnable> workQueue;
+
+ @Getter
+ private boolean running = false;
+
+ /**
+ * Constructor - no initial Runnable.
+ */
+ public SerialWorkQueue() {
+ workQueue = new LinkedList<>();
+ }
+
+ /**
+ * Constructor - initial 'Runnable' is specified.
+ *
+ * @param runnable an initial 'Runnnable' to run
+ */
+ public SerialWorkQueue(Runnable runnable) {
+ workQueue = new LinkedList<>();
+ workQueue.add(runnable);
+ }
+
+ /**
+ * Starts the queue. If the current thread is the first to start it, then the current
+ * thread will process any requests in the queue before returning.
+ */
+ public void start() {
+ Runnable item;
+
+ synchronized (this) {
+ if (running) {
+ // already running
+ return;
+ }
+
+ running = true;
+ item = workQueue.peekFirst();
+ }
+
+ if (item != null) {
+ processQueue(item);
+ }
+ }
+
+ /**
+ * Called to add a 'Runnable' to the work queue. If the queue was empty, the current
+ * thread is used to process the queue.
+ *
+ * @param work the Runnable to be queued, and eventually run
+ */
+ public void queueAndRun(Runnable work) {
+ synchronized (this) {
+ workQueue.add(work);
+ if (!running || workQueue.size() > 1) {
+ // there was already work in the queue, so presumably there is
+ // already an associated thread running
+ return;
+ }
+ // if we reach this point, the queue was empty when this method was
+ // called, so this thread will process the queue
+ }
+
+ processQueue(work);
+ }
+
+ /**
+ * Internal method to process the work queue until it is empty. Note that entries
+ * could be added by this thread or another one while we are working.
+ *
+ * @param firstItem the first item in the queue
+ */
+ private void processQueue(Runnable firstItem) {
+ Runnable next = firstItem;
+ while (next != null) {
+ try {
+ next.run();
+ } catch (Exception e) {
+ logger.error("SerialWorkQueue.processQueue exception", e);
+ }
+
+ synchronized (this) {
+ // remove the job we just ran
+ workQueue.removeFirst();
+ next = workQueue.peekFirst();
+ }
+ }
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/TdjamController.java b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/TdjamController.java
new file mode 100644
index 000000000..0b17f196f
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/controlloop/tdjam/TdjamController.java
@@ -0,0 +1,833 @@
+/*-
+ * ============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.tdjam;
+
+import static org.onap.policy.drools.properties.DroolsPropertyConstants.PROPERTY_CONTROLLER_TYPE;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
+import org.onap.policy.common.endpoints.event.comm.TopicListener;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.onap.policy.controlloop.CanonicalOnset;
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopNotificationType;
+import org.onap.policy.controlloop.ControlLoopResponse;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
+import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2;
+import org.onap.policy.controlloop.utils.ControlLoopUtils;
+import org.onap.policy.drools.controller.DroolsController;
+import org.onap.policy.drools.features.DroolsControllerFeatureApi;
+import org.onap.policy.drools.features.PolicyControllerFeatureApi;
+import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
+import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
+import org.onap.policy.drools.protocol.coders.ProtocolCoderToolset;
+import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.extension.system.NonDroolsPolicyController;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This replaces a Drools session with Java code. Although Drools memory
+ * is simulated when running the Junit tests, there is no actual use of
+ * Drools here.
+ */
+public class TdjamController extends NonDroolsPolicyController {
+ private static Logger logger = LoggerFactory.getLogger(TdjamController.class);
+
+ // the 'controller.type' property is set to this value
+ private static final String TDJAM_CONTROLLER_BUILDER_TAG = "tdjam";
+
+ // additional data associated with session
+ private final String groupId;
+ private final String artifactId;
+
+ // top-level tosca policy table (first key = name, second key = version)
+ private final Map<String, Map<String, ToscaPolicy>> toscaPolicies = new HashMap<>();
+
+ // maps 'controlLoopControlName' to 'ControlLoopParams'
+ private final Map<String, ControlLoopParams> controlLoopParams = new HashMap<>();
+
+ // maps 'requestId' to 'ControlLoopEventManager'
+ private final Map<UUID, ControlLoopEventManager> eventManagers = new ConcurrentHashMap<>();
+
+ // maps onset to 'ControlLoopEventManager'
+ private final Map<VirtualControlLoopEvent, ControlLoopEventManager> onsetToEventManager = new ConcurrentHashMap<>();
+
+ // maps 'topic' to 'TopicData'
+ private final Map<String, TopicData> topicDataTable = new ConcurrentHashMap<>();
+
+ /* ============================================================ */
+
+ /**
+ * Initialize a new 'TdjamController'.
+ *
+ * @param name the controller name
+ * @param properties properties defining the controller
+ */
+ public TdjamController(String name, Properties properties) {
+ super(name, properties);
+
+ this.groupId = getGroupId();
+ this.artifactId = getArtifactId();
+
+ init();
+ }
+
+ private void init() {
+ // go through all of the incoming message decoders associated
+ // with this controller
+ for (ProtocolCoderToolset pct :
+ EventProtocolCoderConstants.getManager()
+ .getDecoders(groupId, artifactId)) {
+ // go through the 'CoderFilters' instances, and see if there are
+ // any that we are interested in
+ for (CoderFilters cf : pct.getCoders()) {
+ try {
+ Class<?> clazz = Class.forName(cf.getCodedClass());
+ if (ControlLoopEvent.class.isAssignableFrom(clazz)) {
+ // this one is of interest
+ logger.debug("TdjamController using CoderFilters: {}", cf);
+ getTopicData(pct.getTopic());
+ }
+ } catch (ClassNotFoundException e) {
+ logger.error("CoderFilter refers to unknown class: {}",
+ cf.getCodedClass(), e);
+ }
+ }
+ }
+
+ // start all 'TopicData' instances
+ for (TopicData topicData : topicDataTable.values()) {
+ topicData.start();
+ }
+ }
+
+ @Override
+ public <T> boolean offer(T object) {
+ if (object instanceof ToscaPolicy) {
+ addToscaPolicy((ToscaPolicy) object);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add or replace a ToscaPolicy instance. The policy is keyed by name and
+ * version.
+ *
+ * @param toscaPolicy the ToscaPolicy being added
+ * @return if a ToscaPolicy with this name/version previously existed within
+ * this TdjamController, it is returned; otherwise, 'null' is returned.
+ */
+ public synchronized ToscaPolicy addToscaPolicy(ToscaPolicy toscaPolicy) {
+ Map<String, ToscaPolicy> level2 =
+ toscaPolicies.computeIfAbsent(toscaPolicy.getName(),
+ key -> new HashMap<String, ToscaPolicy>());
+ ToscaPolicy prev = level2.put(toscaPolicy.getVersion(), toscaPolicy);
+ if (prev != null) {
+ // update 'ControlLoopParams' entries
+ for (ControlLoopParams clp : controlLoopParams.values()) {
+ if (clp.getToscaPolicy() == prev) {
+ clp.setToscaPolicy(toscaPolicy);
+ }
+ }
+ }
+ logger.debug("ToscaPolicy name={}, version={}, count={}, prev={}",
+ toscaPolicy.getName(), toscaPolicy.getVersion(), toscaPolicies.size(), (prev != null));
+ dumpTables();
+
+ // attempt to create a 'ControlLoopParams' instance from this object
+ ControlLoopParams params =
+ ControlLoopUtils.toControlLoopParams(toscaPolicy);
+ if (params != null) {
+ addControlLoopParams(params);
+ }
+ return prev;
+ }
+
+ /**
+ * Remove a ToscaPolicy instance associated with the specified name and
+ * version.
+ *
+ * @param name the name of the ToscaPolicy to remove
+ * @param version the version of the ToscaPolicy to remove
+ * @return the ToscaPolicy that was removed, or 'null' if not found
+ */
+ public synchronized ToscaPolicy removeToscaPolicy(String name, String version) {
+ ToscaPolicy prev = null;
+ Map<String, ToscaPolicy> level2 = toscaPolicies.get(name);
+
+ if (level2 != null && (prev = level2.remove(version)) != null) {
+ // remove all 'ControlLoopParams' entries referencing this policy
+ for (ControlLoopParams clp :
+ new ArrayList<>(controlLoopParams.values())) {
+ if (clp.getToscaPolicy() == prev) {
+ controlLoopParams.remove(clp.getClosedLoopControlName());
+ }
+ }
+ }
+ return prev;
+ }
+
+ /**
+ * Fetch a ToscaPolicy instance associated with the specified name and
+ * version.
+ *
+ * @param name the name of the ToscaPolicy
+ * @param version the version of the ToscaPolicy
+ * @return the ToscaPolicy, or 'null' if not found
+ */
+ public synchronized ToscaPolicy getToscaPolicy(String name, String version) {
+ Map<String, ToscaPolicy> level2 = toscaPolicies.get(name);
+ return (level2 == null ? null : level2.get(version));
+ }
+
+ /**
+ * Return a collection of all ToscaPolicy instances.
+ *
+ * @return all ToscaPolicy instances
+ */
+ public synchronized Collection<ToscaPolicy> getAllToscaPolicies() {
+ HashSet<ToscaPolicy> rval = new HashSet<>();
+ for (Map<String, ToscaPolicy> map : toscaPolicies.values()) {
+ rval.addAll(map.values());
+ }
+ return rval;
+ }
+
+ /**
+ * Add a new 'ControlLoopParams' instance -- they are keyed by
+ * 'closedLoopControlName'.
+ *
+ * @param clp the 'ControlLoopParams' instance to add
+ * @return the 'ControlLoopParams' instance previously associated with the
+ * 'closedLoopControlName' ('null' if it didn't exist)
+ */
+ public synchronized ControlLoopParams addControlLoopParams(ControlLoopParams clp) {
+ ToscaPolicy toscaPolicy =
+ getToscaPolicy(clp.getPolicyName(), clp.getPolicyVersion());
+ if (toscaPolicy == null) {
+ // there needs to be a 'ToscaPolicy' instance with a matching
+ // name/version
+ logger.debug("Missing ToscaPolicy, name={}, version={}",
+ clp.getPolicyName(), clp.getPolicyVersion());
+ return clp;
+ }
+
+ clp.setToscaPolicy(toscaPolicy);
+ ControlLoopParams prev =
+ controlLoopParams.put(clp.getClosedLoopControlName(), clp);
+
+ logger.debug("ControlLoopParams name={}, version={}, closedLoopControlName={}, count={}, prev={}",
+ clp.getPolicyName(), clp.getPolicyVersion(),
+ clp.getClosedLoopControlName(), controlLoopParams.size(), (prev != null));
+ dumpTables();
+ return prev;
+ }
+
+ /**
+ * Return a collection of all ControlLoopParams instances.
+ *
+ * @return all ControlLoopParams instances
+ */
+ public synchronized Collection<ControlLoopParams> getAllControlLoopParams() {
+ return new ArrayList<>(controlLoopParams.values());
+ }
+
+ /**
+ * Return a collection of all EventManager instances.
+ *
+ * @return all EventManager instances
+ *
+ */
+ public synchronized Collection<ControlLoopEventManager> getAllEventManagers() {
+ return new ArrayList<>(eventManagers.values());
+ }
+
+ /**
+ * Return a collection of all onsetToEventManager instances.
+ *
+ * @return all onsetToEventManager instances
+ *
+ */
+ public synchronized Collection<ControlLoopEventManager> getAllOnsetToEventManager() {
+ return new ArrayList<>(onsetToEventManager.values());
+ }
+
+ /**
+ * Reset the controller.
+ *
+ */
+ public synchronized void reset() {
+ toscaPolicies.clear();
+ controlLoopParams.clear();
+ eventManagers.clear();
+ onsetToEventManager.clear();
+ }
+
+ @Override
+ public boolean stop() {
+ super.stop();
+
+ // stop all 'TopicData' instances
+ for (TopicData topicData : topicDataTable.values()) {
+ topicData.stop();
+ }
+ return true;
+ }
+
+ /**
+ * Remove a ControlLoopParams instance associated with the specified
+ * 'closedLoopControlName'.
+ *
+ * @param closedLoopControlName the closedLoopControlName identifying the
+ * ControlLoopParams instance
+ * @return the 'ControlLoopParams' instance, 'null' if not found
+ */
+ public synchronized ControlLoopParams removeControlLoopParams(String closedLoopControlName) {
+ return controlLoopParams.remove(closedLoopControlName);
+ }
+
+ /**
+ * Dump out the ToscaPolicy and ControlLoopParams tables in
+ * human-readable form.
+ */
+ private void dumpTables() {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bos, true);
+
+ // name(25) version(10) closedLoopControlName(...)
+
+ String format = "%-25s %-10s %s\n";
+ out.println("ToscaPolicy Table");
+ out.format(format, "Name", "Version", "");
+ out.format(format, "----", "-------", "");
+
+ for (Map<String, ToscaPolicy> level2 : toscaPolicies.values()) {
+ for (ToscaPolicy tp : level2.values()) {
+ out.format(format, tp.getName(), tp.getVersion(), "");
+ }
+ }
+
+ out.println("\nControlLoopParams Table");
+ out.format(format, "Name", "Version", "ClosedLoopControlName");
+ out.format(format, "----", "-------", "---------------------");
+ for (ControlLoopParams cp : controlLoopParams.values()) {
+ out.format(format, cp.getPolicyName(), cp.getPolicyVersion(),
+ cp.getClosedLoopControlName());
+ }
+
+ logger.debug(new String(bos.toByteArray()));
+ }
+
+ /**
+ * Find or create a 'TopicData' instance associated with the specified
+ * topic name.
+ *
+ * @param name the topic name
+ * @return the new or existing 'TopicData' instance associated with 'name'
+ */
+ private TopicData getTopicData(String name) {
+ return topicDataTable.computeIfAbsent(name, key -> new TopicData(name));
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Process an incoming 'ControlLoopEvent'.
+ *
+ * @param event the incoming 'ControlLoopEvent'
+ */
+ private void processEvent(ControlLoopEvent event) {
+ String clName = event.getClosedLoopControlName();
+ ControlLoopParams params = controlLoopParams.get(clName);
+ if (params == null) {
+ logger.debug("No ControlLoopParams for event: {}", event);
+ return;
+ }
+
+ UUID requestId = event.getRequestId();
+ if (event instanceof CanonicalOnset) {
+ CanonicalOnset coEvent = (CanonicalOnset) event;
+
+ if (requestId == null) {
+ // the requestId should not be 'null'
+ handleNullRequestId(coEvent, params);
+ return;
+ }
+
+ ControlLoopEventManager manager = onsetToEventManager.computeIfAbsent(coEvent, key -> {
+ // a ControlLoopEventManager does not yet exist for this
+ // 'event' -- create one, with the initial event
+ try {
+ ControlLoopEventManager mgr = new ControlLoopEventManager(params, coEvent);
+ eventManagers.put(requestId, mgr);
+ return mgr;
+ } catch (ControlLoopException e) {
+ logger.error("Exception creating ControlLoopEventManager", e);
+ return null;
+ }
+ });
+
+ if (manager != null && !manager.getSerialWorkQueue().isRunning()) {
+ // new manager - start it by processing the initial event
+ manager.getSerialWorkQueue().start();
+ return;
+ }
+ }
+
+ if (event instanceof VirtualControlLoopEvent) {
+ ControlLoopEventManager manager = eventManagers.get(requestId);
+ if (manager != null) {
+ manager.getSerialWorkQueue()
+ .queueAndRun(() -> manager.subsequentEvent((VirtualControlLoopEvent) event));
+ return;
+ }
+ }
+
+ // this block of code originally appeared in the 'EVENT.CLEANUP'
+ // Drools rule
+ String ruleName = "EVENT.CLEANUP";
+
+ logger.info("{}: {}", clName, ruleName);
+ logger.debug("{}: {}: orphan event={}", clName, ruleName, event);
+ }
+
+ /**
+ * Generate and send a notification message in response to a 'CanonicalOnset'
+ * with a null 'requestId'.
+ *
+ * @param event the CanonicalOnset event
+ * @param params the associated ControlLoopParams
+ */
+ private void handleNullRequestId(CanonicalOnset event,
+ ControlLoopParams params) {
+ // this block of code originally appeared in the 'EVENT' Drools rule
+ String ruleName = "EVENT";
+ String clName = event.getClosedLoopControlName();
+
+ VirtualControlLoopNotification notification =
+ new VirtualControlLoopNotification(event);
+ notification.setNotification(ControlLoopNotificationType.REJECTED);
+ notification.setFrom("policy");
+ notification.setMessage("Missing requestId");
+ notification.setPolicyName(params.getPolicyName() + "." + ruleName);
+ notification.setPolicyScope(params.getPolicyScope());
+ notification.setPolicyVersion(params.getPolicyVersion());
+
+ //
+ // Generate notification
+ //
+ try {
+ PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", notification);
+
+ } catch (RuntimeException e) {
+ logger.warn("{}: {}.{}: event={} exception generating notification",
+ clName, params.getPolicyName(), ruleName,
+ event, e);
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * This nested class corresponds to a single topic name. At present, the
+ * only topics that are directly handled by this class are
+ * 'ControlLoopEvent', and subclasses (hence, the call to 'processEvent').
+ * If other event types later need to be directly handled, this may need to
+ * become an abstract class, with subclasses for the various event types.
+ */
+ private class TopicData implements TopicListener {
+ // topic name
+ private String name;
+
+ // set of 'TopicSource' instances associated with this topic
+ // (probably only one, but the underlying APIs support a list)
+ private List<TopicSource> topicSources = null;
+
+ /**
+ * Constructor -- initialize the 'TopicData' instance.
+ *
+ * @param name the topic name
+ */
+ private TopicData(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Register all of the 'TopicSource' instances associated with this
+ * topic, and start the listeners.
+ */
+ private void start() {
+ if (topicSources == null) {
+ // locate topic sources
+ ArrayList<String> topics = new ArrayList<>();
+ topics.add(name);
+ topicSources = TopicEndpointManager.getManager().getTopicSources(topics);
+ }
+
+ for (TopicSource consumer : topicSources) {
+ consumer.register(this);
+ consumer.start();
+ }
+ }
+
+ /**
+ * Unregister all of the 'TopicSource' instances associated with this
+ * topic, and stop the listeners.
+ */
+ private void stop() {
+ if (topicSources != null) {
+ for (TopicSource consumer : topicSources) {
+ consumer.unregister(this);
+ consumer.stop();
+ }
+ }
+ }
+
+ /*===========================*/
+ /* 'TopicListener' interface */
+ /*===========================*/
+
+ @Override
+ public void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event) {
+ logger.debug("TopicData.onTopicEvent: {}", event);
+ Object decodedObject =
+ EventProtocolCoderConstants.getManager().decode(groupId, artifactId, topic, event);
+ if (decodedObject != null) {
+ logger.debug("Decoded to object of {}", decodedObject.getClass());
+ if (decodedObject instanceof ControlLoopEvent) {
+ PolicyEngineConstants.getManager().getExecutorService().execute(() ->
+ processEvent((ControlLoopEvent) decodedObject));
+ }
+ }
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * This is a 'ControlLoopEventManager2' variant designed to run under
+ * 'TdjamController'.
+ */
+ private class ControlLoopEventManager extends ControlLoopEventManager2 {
+ private static final long serialVersionUID = 1L;
+
+ // used to serialize method calls from multiple threads, which avoids the
+ // need for additional synchronization
+ private final SerialWorkQueue serialWorkQueue;
+
+ private final ControlLoopParams params;
+
+ // onset event
+ private final CanonicalOnset event;
+
+ /**
+ * Constructor - initialize a ControlLoopEventManager.
+ *
+ * @param params the 'ControlLoopParam's instance associated with the
+ * 'closedLoopControlName'
+ * @param event the initial ControlLoopEvent
+ */
+ private ControlLoopEventManager(ControlLoopParams params, CanonicalOnset event)
+ throws ControlLoopException {
+
+ super(params, event);
+ this.params = params;
+ this.event = event;
+ this.serialWorkQueue = new SerialWorkQueue(this::initialEvent);
+ }
+
+ /**
+ * Return the SerialWorkQueue.
+ *
+ * @return the SerialWorkQueue
+ */
+ private SerialWorkQueue getSerialWorkQueue() {
+ return serialWorkQueue;
+ }
+
+ /**
+ * This is a notification from the base class that a state transition
+ * has occurred.
+ */
+ @Override
+ protected void notifyUpdate() {
+ update();
+ }
+
+ /**
+ * Process the initial event from DCAE that caused the
+ * 'ControlLoopEventManager' to be created.
+ */
+ private void initialEvent() {
+ // this block of code originally appeared in the 'EVENT' Drools rule
+ String ruleName = "EVENT";
+ UUID requestId = event.getRequestId();
+ String clName = event.getClosedLoopControlName();
+
+ VirtualControlLoopNotification notification;
+
+ try {
+ //
+ // Check the event, because we need it to not be null when
+ // we create the ControlLoopEventManager. The ControlLoopEventManager
+ // will do extra syntax checking as well as check if the closed loop is disabled.
+ //
+ try {
+ start();
+ } catch (Exception e) {
+ eventManagers.remove(requestId, this);
+ onsetToEventManager.remove(event, this);
+ throw e;
+ }
+ notification = makeNotification();
+ notification.setNotification(ControlLoopNotificationType.ACTIVE);
+ notification.setPolicyName(params.getPolicyName() + "." + ruleName);
+ } catch (Exception e) {
+ logger.warn("{}: {}.{}", clName, params.getPolicyName(), ruleName, e);
+ notification = new VirtualControlLoopNotification(event);
+ notification.setNotification(ControlLoopNotificationType.REJECTED);
+ notification.setMessage("Exception occurred: " + e.getMessage());
+ notification.setPolicyName(params.getPolicyName() + "." + ruleName);
+ notification.setPolicyScope(params.getPolicyScope());
+ notification.setPolicyVersion(params.getPolicyVersion());
+ }
+ //
+ // Generate notification
+ //
+ try {
+ PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", notification);
+
+ } catch (RuntimeException e) {
+ logger.warn("{}: {}.{}: event={} exception generating notification",
+ clName, params.getPolicyName(), ruleName,
+ event, e);
+ }
+ }
+
+ /**
+ * Process a subsequent event from DCAE.
+ *
+ * @param event the VirtualControlLoopEvent event
+ */
+ private void subsequentEvent(VirtualControlLoopEvent event) {
+ // this block of code originally appeared in the
+ // 'EVENT.MANAGER>NEW.EVENT' Drools rule
+ String ruleName = "EVENT.MANAGER.NEW.EVENT";
+
+ //
+ // Check what kind of event this is
+ //
+ switch (onNewEvent(event)) {
+ case SYNTAX_ERROR:
+ //
+ // Ignore any bad syntax events
+ //
+ logger.warn("{}: {}.{}: syntax error",
+ getClosedLoopControlName(), getPolicyName(), ruleName);
+ break;
+
+ case FIRST_ABATEMENT:
+ case SUBSEQUENT_ABATEMENT:
+ //
+ // TODO: handle the abatement. Currently, it's just discarded.
+ //
+ break;
+
+ case FIRST_ONSET:
+ case SUBSEQUENT_ONSET:
+ default:
+ //
+ // We don't care about subsequent onsets
+ //
+ logger.warn("{}: {}.{}: subsequent onset",
+ getClosedLoopControlName(), getPolicyName(), ruleName);
+ break;
+ }
+ }
+
+ /**
+ * Called when a state transition occurs.
+ */
+ private void update() {
+ // handle synchronization by running it under the SerialWorkQueue
+ getSerialWorkQueue().queueAndRun(() -> {
+ if (isActive()) {
+ updateActive();
+ } else {
+ updateInactive();
+ }
+ });
+ }
+
+ /**
+ * Called when a state transition occurs, and we are in the active state.
+ */
+ private void updateActive() {
+ if (!isUpdated()) {
+ // no notification needed
+ return;
+ }
+
+ // this block of code originally appeared in the
+ // 'EVENT.MANAGER.PROCESSING' Drools rule
+ String ruleName = "EVENT.MANAGER.PROCESSING";
+ VirtualControlLoopNotification notification =
+ getNotification();
+
+ logger.info("{}: {}.{}: manager={}",
+ getClosedLoopControlName(), getPolicyName(), ruleName,
+ this);
+ //
+ // Generate notification
+ //
+ try {
+ notification.setPolicyName(getPolicyName() + "." + ruleName);
+ PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", notification);
+
+ } catch (RuntimeException e) {
+ logger.warn("{}: {}.{}: manager={} exception generating notification",
+ getClosedLoopControlName(), getPolicyName(), ruleName,
+ this, e);
+ }
+ //
+ // Generate Response notification
+ //
+ try {
+ ControlLoopResponse clResponse = getControlLoopResponse();
+ if (clResponse != null) {
+ PolicyEngineConstants.getManager().deliver("DCAE_CL_RSP", clResponse);
+ }
+
+ } catch (RuntimeException e) {
+ logger.warn("{}: {}.{}: manager={} exception generating Response notification",
+ getClosedLoopControlName(), getPolicyName(), ruleName,
+ this, e);
+ }
+ //
+ // Discard this message and wait for the next response.
+ //
+ nextStep();
+ update();
+ }
+
+ /**
+ * Called when a state transition has occurred, and we are not in the
+ * active state.
+ */
+ private void updateInactive() {
+ // this block of code originally appeared in the 'EVENT.MANAGER.FINAL'
+ // Drools rule
+ String ruleName = "EVENT.MANAGER.FINAL";
+ VirtualControlLoopNotification notification =
+ getNotification();
+
+ logger.info("{}: {}.{}: manager={}",
+ getClosedLoopControlName(), getPolicyName(), ruleName,
+ this);
+ //
+ // Generate notification
+ //
+ try {
+ notification.setPolicyName(getPolicyName() + "." + ruleName);
+ PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", notification);
+ } catch (RuntimeException e) {
+ logger.warn("{}: {}.{}: manager={} exception generating notification",
+ getClosedLoopControlName(), getPolicyName(), ruleName,
+ this, e);
+ }
+ //
+ // Destroy the manager
+ //
+ destroy();
+
+ // Remove the entry from the table
+ eventManagers.remove(getRequestId(), this);
+ onsetToEventManager.remove(event, this);
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * An instance of this class is called by 'IndexedPolicyControllerFactory'.
+ * It does the build operation when the value of the 'controller.type'
+ * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
+ */
+ public static class PolicyBuilder implements PolicyControllerFeatureApi {
+ @Override
+ public int getSequenceNumber() {
+ return 1;
+ }
+
+ @Override
+ public PolicyController beforeInstance(String name, Properties properties) {
+ if (TDJAM_CONTROLLER_BUILDER_TAG.equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
+ return new TdjamController(name, properties);
+ }
+ return null;
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * An instance of this class is called by 'IndexedDroolsControllerFactory'.
+ * It does the build operation when the value of the 'controller.type'
+ * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
+ */
+ public static class DroolsBuilder implements DroolsControllerFeatureApi {
+ @Override
+ public int getSequenceNumber() {
+ return 1;
+ }
+
+ @Override
+ public DroolsController beforeInstance(Properties properties,
+ String groupId, String artifactId, String version,
+ List<TopicCoderFilterConfiguration> decoderConfigurations,
+ List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
+
+ if (TDJAM_CONTROLLER_BUILDER_TAG.equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
+ return TdjamController.getBuildInProgress();
+ }
+ return null;
+ }
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/extension/system/NonDroolsPolicyController.java b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/extension/system/NonDroolsPolicyController.java
new file mode 100644
index 000000000..d876bee96
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/main/java/org/onap/policy/extension/system/NonDroolsPolicyController.java
@@ -0,0 +1,668 @@
+/*
+ * ============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.extension.system;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.commons.collections4.queue.CircularFifoQueue;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.onap.policy.common.endpoints.event.comm.Topic;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
+import org.onap.policy.common.utils.services.OrderedServiceImpl;
+import org.onap.policy.drools.controller.DroolsController;
+import org.onap.policy.drools.core.PolicyContainer;
+import org.onap.policy.drools.features.DroolsControllerFeatureApi;
+import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
+import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
+import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
+import org.onap.policy.drools.protocol.coders.EventProtocolParams;
+import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
+import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
+import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
+import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
+import org.onap.policy.drools.system.internal.AggregatedPolicyController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class combines the 'PolicyController' and 'DroolsController'
+ * interfaces, and provides a controller that does not have Drools running
+ * underneath. It also contains some code copied from 'MavenDroolsController'
+ * and 'NullDroolsController'. The goal is to have it look like other
+ * controllers, use the same style property file, and provide access to
+ * UEB/DMAAP message streams associated with the controller.
+ */
+public class NonDroolsPolicyController extends AggregatedPolicyController implements DroolsController {
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(NonDroolsPolicyController.class);
+
+ /**
+ * The PolicyController and DroolsController factories assume that the
+ * controllers are separate objects, but in this case, the same object
+ * is used for both. We want the DroolsController 'build' method to
+ * return the same object; however, at the point the DroolsController
+ * build is taking place, the PolicyController hasn't yet been placed
+ * in any tables. The following variable is used to pass this information
+ * from one stack frame to another within the same thread.
+ */
+ private static ThreadLocal<NonDroolsPolicyController> buildInProgress = new ThreadLocal<>();
+
+ /**
+ * alive status of this drools controller,
+ * reflects invocation of start()/stop() only.
+ */
+ protected volatile boolean alive = false;
+
+ /**
+ * locked status of this drools controller,
+ * reflects if i/o drools related operations are permitted,
+ * more specifically: offer() and deliver().
+ * It does not affect the ability to start and stop
+ * underlying drools infrastructure
+ */
+ protected volatile boolean locked = false;
+
+ /**
+ * list of topics, each with associated decoder classes, each
+ * with a list of associated filters.
+ */
+ protected List<TopicCoderFilterConfiguration> decoderConfigurations;
+
+ /**
+ * list of topics, each with associated encoder classes, each
+ * with a list of associated filters.
+ */
+ protected List<TopicCoderFilterConfiguration> encoderConfigurations;
+
+ /**
+ * recent sink events processed.
+ */
+ protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
+
+ // this is used to avoid infinite recursion in a shutdown or halt operation
+ private boolean shutdownInProgress = false;
+
+ private static Properties convert(String name, Properties properties) {
+
+ Properties newProperties = new Properties();
+ for (String pname : properties.stringPropertyNames()) {
+ newProperties.setProperty(pname, properties.getProperty(pname));
+ }
+
+ newProperties.setProperty("rules.groupId", "NonDroolsPolicyController");
+ newProperties.setProperty("rules.artifactId", name);
+ newProperties.setProperty("rules.version", "1.0");
+ return newProperties;
+ }
+
+ /**
+ * constructor -- pass parameters to superclass.
+ * @param name controller name
+ * @param properties contents of controller properties file
+ */
+ public NonDroolsPolicyController(String name, Properties properties) {
+ super(name, convert(name, properties));
+ }
+
+ /**
+ * This is used to pass the 'NonDroolsPolicyController' object to the
+ * 'DroolsPolicyBuilder' object, as the same object is used for both
+ * 'PolicyController' and 'DroolsController'.
+ *
+ * @return the NonDroolsPolicyController object ('null' if not available)
+ */
+ public static NonDroolsPolicyController getBuildInProgress() {
+ return buildInProgress.get();
+ }
+
+ protected void initDrools(Properties properties) {
+ try {
+ // Register with drools factory
+ buildInProgress.set(this);
+ this.droolsController.set(getDroolsFactory().build(properties, sources, sinks));
+ buildInProgress.set(null);
+ } catch (Exception | LinkageError e) {
+ logger.error("{}: cannot init-drools", this);
+ throw new IllegalArgumentException(e);
+ }
+
+ decoderConfigurations = codersAndFilters(properties, sources);
+ encoderConfigurations = codersAndFilters(properties, sinks);
+
+ // add to 'EventProtocolCoderConstants.getManager()' table
+ for (TopicCoderFilterConfiguration tcfc : decoderConfigurations) {
+ for (PotentialCoderFilter pcf : tcfc.getCoderFilters()) {
+ getCoderManager().addDecoder(
+ EventProtocolParams.builder()
+ .groupId(getGroupId())
+ .artifactId(getArtifactId())
+ .topic(tcfc.getTopic())
+ .eventClass(pcf.getCodedClass())
+ .protocolFilter(pcf.getFilter())
+ .customGsonCoder(tcfc.getCustomGsonCoder())
+ .modelClassLoaderHash(NonDroolsPolicyController.class.getClassLoader().hashCode()));
+ }
+ }
+ for (TopicCoderFilterConfiguration tcfc : encoderConfigurations) {
+ for (PotentialCoderFilter pcf : tcfc.getCoderFilters()) {
+ getCoderManager().addEncoder(
+ EventProtocolParams.builder()
+ .groupId(getGroupId())
+ .artifactId(getArtifactId())
+ .topic(tcfc.getTopic())
+ .eventClass(pcf.getCodedClass())
+ .protocolFilter(pcf.getFilter())
+ .customGsonCoder(tcfc.getCustomGsonCoder())
+ .modelClassLoaderHash(NonDroolsPolicyController.class.getClassLoader().hashCode()));
+ }
+ }
+ }
+
+ /*==============================*/
+ /* 'DroolsController' interface */
+ /*==============================*/
+
+ // methods copied from 'MavenDroolsController' and 'NullDroolsController'
+
+ @Override
+ public boolean start() {
+
+ logger.info("START: {}", this);
+
+ synchronized (this) {
+ if (this.alive) {
+ return true;
+ }
+ this.alive = true;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+
+ logger.info("STOP: {}", this);
+
+ synchronized (this) {
+ if (!this.alive) {
+ return true;
+ }
+ this.alive = false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ if (shutdownInProgress) {
+ // avoid infinite recursion
+ return;
+ }
+ logger.info("{}: SHUTDOWN", this);
+
+ try {
+ this.stop();
+ this.removeCoders();
+ shutdownInProgress = true;
+
+ // the following method calls 'this.shutdown' recursively
+ getDroolsFactory().shutdown(this);
+ } catch (Exception e) {
+ logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
+ } finally {
+ shutdownInProgress = false;
+ }
+ }
+
+ @Override
+ public void halt() {
+ if (shutdownInProgress) {
+ // avoid infinite recursion
+ return;
+ }
+ logger.info("{}: HALT", this);
+
+ try {
+ this.stop();
+ this.removeCoders();
+ shutdownInProgress = true;
+
+ // the following method calls 'this.halt' recursively
+ getDroolsFactory().destroy(this);
+ } catch (Exception e) {
+ logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
+ } finally {
+ shutdownInProgress = false;
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return this.alive;
+ }
+
+ @Override
+ public boolean lock() {
+ logger.info("LOCK: {}", this);
+
+ this.locked = true;
+ return true;
+ }
+
+ @Override
+ public boolean unlock() {
+ logger.info("UNLOCK: {}", this);
+
+ this.locked = false;
+ return true;
+ }
+
+ @Override
+ public boolean isLocked() {
+ return this.locked;
+ }
+
+ @Override
+ public String getGroupId() {
+ return "NonDroolsPolicyController";
+ }
+
+ @Override
+ public String getArtifactId() {
+ return getName();
+ }
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public List<String> getSessionNames() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<String> getCanonicalSessionNames() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<String> getBaseDomainNames() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean offer(String topic, String event) {
+ return false;
+ }
+
+ @Override
+ public <T> boolean offer(T event) {
+ return false;
+ }
+
+ @Override
+ public boolean deliver(TopicSink sink, Object event) {
+
+ // this one is from 'MavenDroolsController'
+
+ logger.info("{} DELIVER: {} FROM {} TO {}", this, event, this, sink);
+
+ for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
+ try {
+ if (feature.beforeDeliver(this, sink, event)) {
+ return true;
+ }
+ } catch (Exception e) {
+ logger.error("{}: feature {} before-deliver failure because of {}", this, feature.getClass().getName(),
+ e.getMessage(), e);
+ }
+ }
+
+ if (sink == null) {
+ throw new IllegalArgumentException(this + " invalid sink");
+ }
+
+ if (event == null) {
+ throw new IllegalArgumentException(this + " invalid event");
+ }
+
+ if (this.locked) {
+ throw new IllegalStateException(this + " is locked");
+ }
+
+ if (!this.alive) {
+ throw new IllegalStateException(this + " is stopped");
+ }
+
+ String json =
+ getCoderManager().encode(sink.getTopic(), event, this);
+
+ synchronized (this.recentSinkEvents) {
+ this.recentSinkEvents.add(json);
+ }
+
+ boolean success = sink.send(json);
+
+ for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
+ try {
+ if (feature.afterDeliver(this, sink, event, json, success)) {
+ return true;
+ }
+ } catch (Exception e) {
+ logger.error("{}: feature {} after-deliver failure because of {}", this, feature.getClass().getName(),
+ e.getMessage(), e);
+ }
+ }
+
+ return success;
+
+ }
+
+ @Override
+ public Object[] getRecentSourceEvents() {
+ return new String[0];
+ }
+
+ @Override
+ public PolicyContainer getContainer() {
+ return null;
+ }
+
+ @Override
+ public String[] getRecentSinkEvents() {
+ synchronized (this.recentSinkEvents) {
+ String[] events = new String[recentSinkEvents.size()];
+ return recentSinkEvents.toArray(events);
+ }
+ }
+
+ @Override
+ public boolean ownsCoder(Class<?> coderClass, int modelHash) {
+ //throw new IllegalStateException(makeInvokeMsg());
+ return true;
+ }
+
+ @Override
+ public Class<?> fetchModelClass(String className) {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(makeInvokeMsg());
+ }
+ }
+
+ @Override
+ public boolean isBrained() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("NonDroolsPolicyController []");
+ return builder.toString();
+ }
+
+ @Override
+ public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
+ List<TopicCoderFilterConfiguration> decoderConfigurations,
+ List<TopicCoderFilterConfiguration> encoderConfigurations)
+ throws LinkageError {
+ throw new IllegalStateException(makeInvokeMsg());
+ }
+
+ @Override
+ public Map<String, Integer> factClassNames(String sessionName) {
+ return new HashMap<>();
+ }
+
+ @Override
+ public long factCount(String sessionName) {
+ return 0;
+ }
+
+ @Override
+ public List<Object> facts(String sessionName, String className, boolean delete) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public <T> List<T> facts(@NonNull String sessionName, @NonNull Class<T> clazz) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<Object> factQuery(String sessionName, String queryName,
+ String queriedEntity,
+ boolean delete, Object... queryParams) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public <T> boolean delete(@NonNull String sessionName, @NonNull T fact) {
+ return false;
+ }
+
+ @Override
+ public <T> boolean delete(@NonNull T fact) {
+ return false;
+ }
+
+ @Override
+ public <T> boolean delete(@NonNull String sessionName, @NonNull Class<T> fact) {
+ return false;
+ }
+
+ @Override
+ public <T> boolean delete(@NonNull Class<T> fact) {
+ return false;
+ }
+
+ private String makeInvokeMsg() {
+ return this.getClass().getName() + " invoked";
+ }
+
+ /**
+ * remove decoders.
+ */
+ protected void removeDecoders() {
+ logger.info("REMOVE-DECODERS: {}", this);
+
+ if (this.decoderConfigurations == null) {
+ return;
+ }
+
+
+ for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
+ String topic = coderConfig.getTopic();
+ getCoderManager().removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
+ }
+ }
+
+ /**
+ * remove decoders.
+ */
+ protected void removeEncoders() {
+
+ logger.info("REMOVE-ENCODERS: {}", this);
+
+ if (this.encoderConfigurations == null) {
+ return;
+ }
+
+ for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
+ String topic = coderConfig.getTopic();
+ getCoderManager().removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
+ }
+ }
+
+ /**
+ * removes this drools controllers and encoders and decoders from operation.
+ */
+ protected void removeCoders() {
+ logger.info("{}: REMOVE-CODERS", this);
+
+ try {
+ this.removeDecoders();
+ } catch (IllegalArgumentException e) {
+ logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
+ }
+
+ try {
+ this.removeEncoders();
+ } catch (IllegalArgumentException e) {
+ logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
+ }
+ }
+
+ protected List<TopicCoderFilterConfiguration> codersAndFilters(Properties properties,
+ List<? extends Topic> topicEntities) {
+
+ List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = new ArrayList<>();
+
+ if (topicEntities == null || topicEntities.isEmpty()) {
+ return topics2DecodedClasses2Filters;
+ }
+
+ for (Topic topic : topicEntities) {
+
+ // 1. first the topic
+
+ String firstTopic = topic.getTopic();
+
+ String propertyTopicEntityPrefix = getPropertyTopicPrefix(topic) + firstTopic;
+
+ // 2. check if there is a custom decoder for this topic that the user prefers to use
+ // instead of the ones provided in the platform
+
+ CustomGsonCoder customGsonCoder = getCustomCoder(properties, propertyTopicEntityPrefix);
+
+ // 3. second the list of classes associated with each topic
+
+ String eventClasses = properties
+ .getProperty(propertyTopicEntityPrefix + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX);
+
+ if (eventClasses == null || eventClasses.isEmpty()) {
+ logger.warn("There are no event classes for topic {}", firstTopic);
+ continue;
+ }
+
+ List<PotentialCoderFilter> classes2Filters =
+ getFilterExpressions(properties, propertyTopicEntityPrefix, eventClasses);
+
+ TopicCoderFilterConfiguration topic2Classes2Filters =
+ new TopicCoderFilterConfiguration(firstTopic, classes2Filters, customGsonCoder);
+ topics2DecodedClasses2Filters.add(topic2Classes2Filters);
+ }
+
+ return topics2DecodedClasses2Filters;
+ }
+
+ private String getPropertyTopicPrefix(Topic topic) {
+ boolean isSource = topic instanceof TopicSource;
+ CommInfrastructure commInfra = topic.getTopicCommInfrastructure();
+ if (commInfra == CommInfrastructure.UEB) {
+ if (isSource) {
+ return PolicyEndPointProperties.PROPERTY_UEB_SOURCE_TOPICS + ".";
+ } else {
+ return PolicyEndPointProperties.PROPERTY_UEB_SINK_TOPICS + ".";
+ }
+ } else if (commInfra == CommInfrastructure.DMAAP) {
+ if (isSource) {
+ return PolicyEndPointProperties.PROPERTY_DMAAP_SOURCE_TOPICS + ".";
+ } else {
+ return PolicyEndPointProperties.PROPERTY_DMAAP_SINK_TOPICS + ".";
+ }
+ } else if (commInfra == CommInfrastructure.NOOP) {
+ if (isSource) {
+ return PolicyEndPointProperties.PROPERTY_NOOP_SOURCE_TOPICS + ".";
+ } else {
+ return PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS + ".";
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra);
+ }
+ }
+
+ private CustomGsonCoder getCustomCoder(Properties properties, String propertyPrefix) {
+ String customGson = properties.getProperty(propertyPrefix
+ + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX);
+
+ CustomGsonCoder customGsonCoder = null;
+ if (customGson != null && !customGson.isEmpty()) {
+ try {
+ customGsonCoder = new CustomGsonCoder(customGson);
+ } catch (IllegalArgumentException e) {
+ logger.warn("{}: cannot create custom-gson-coder {} because of {}", this, customGson,
+ e.getMessage(), e);
+ }
+ }
+ return customGsonCoder;
+ }
+
+ private List<PotentialCoderFilter> getFilterExpressions(Properties properties, String propertyPrefix,
+ String eventClasses) {
+
+ List<PotentialCoderFilter> classes2Filters = new ArrayList<>();
+
+ List<String> topicClasses = new ArrayList<>(Arrays.asList(eventClasses.split("\\s*,\\s*")));
+
+ for (String theClass : topicClasses) {
+
+ // 4. for each coder class, get the filter expression
+
+ String filter = properties
+ .getProperty(propertyPrefix
+ + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX
+ + "." + theClass + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX);
+
+ JsonProtocolFilter protocolFilter = new JsonProtocolFilter(filter);
+ PotentialCoderFilter class2Filters = new PotentialCoderFilter(theClass, protocolFilter);
+ classes2Filters.add(class2Filters);
+ }
+
+ return classes2Filters;
+ }
+
+ // these may be overridden by junit tests
+
+ protected EventProtocolCoder getCoderManager() {
+ return EventProtocolCoderConstants.getManager();
+ }
+
+ protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
+ return DroolsControllerFeatureApiConstants.getProviders();
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi b/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi
new file mode 100644
index 000000000..09a087ee6
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi
@@ -0,0 +1 @@
+org.onap.policy.controlloop.tdjam.TdjamController$DroolsBuilder
diff --git a/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi b/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi
new file mode 100644
index 000000000..dad467869
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi
@@ -0,0 +1 @@
+org.onap.policy.controlloop.tdjam.TdjamController$PolicyBuilder
diff --git a/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/TdjamTest.java b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/TdjamTest.java
new file mode 100644
index 000000000..cf40ba6d4
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/TdjamTest.java
@@ -0,0 +1,159 @@
+/*-
+ * ============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;
+
+import java.util.Properties;
+import lombok.Getter;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.common.rules.test.BaseTest;
+import org.onap.policy.controlloop.common.rules.test.Listener;
+import org.onap.policy.controlloop.common.rules.test.NamedRunner;
+import org.onap.policy.controlloop.common.rules.test.Rules;
+import org.onap.policy.controlloop.common.rules.test.TestNames;
+import org.onap.policy.drools.persistence.SystemPersistence;
+import org.onap.policy.drools.persistence.SystemPersistenceConstants;
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyControllerConstants;
+import org.onap.policy.drools.system.PolicyEngine;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.onap.policy.simulators.Util;
+
+
+
+/**
+ * Tests use cases using BaseTest Set.
+ *
+ * <p/>
+ * Note: this runs ALL tests (i.e., any whose names start with "test").
+ */
+@RunWith(NamedRunner.class)
+@TestNames(prefixes = {"test"})
+
+public class TdjamTest extends BaseTest {
+ protected static final String CONTROLLER_NAME = "tdjam";
+ protected static PolicyController controller;
+
+ @Getter
+ private static final PolicyEngine pdpd = makeEngine();
+
+ @Getter
+ private static final SystemPersistence pdpdRepo = makePdpdRepo();
+
+ /**
+ * Sets up statics.
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() {
+ initStatics();
+ pdpdRepo.setConfigurationDir("src/test/resources/config");
+ pdpd.configure(new Properties());
+ controller = pdpd.createPolicyController(CONTROLLER_NAME, pdpdRepo.getControllerProperties(CONTROLLER_NAME));
+ pdpd.start();
+ httpClients.addClients("tdjam");
+ simulators.start(Util::buildAaiSim, Util::buildSoSim, Util::buildVfcSim, Util::buildGuardSim,
+ Util::buildSdncSim);
+ }
+
+ /**
+ * Cleans up statics.
+ */
+ @AfterClass
+ public static void tearDownAfterClass() {
+ finishStatics();
+ PolicyControllerConstants.getFactory().shutdown(controller);
+ pdpd.stop();
+ }
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ topics = topicMaker.get();
+ }
+
+ /**
+ * Tears down.
+ */
+ @After
+ public void tearDown() {
+ topics.destroy();
+ }
+
+ protected static PolicyEngine makeEngine() {
+ return PolicyEngineConstants.getManager();
+ }
+
+ protected static SystemPersistence makePdpdRepo() {
+ return SystemPersistenceConstants.getManager();
+ }
+
+ @Override
+ protected void waitForLockAndPermit(ToscaPolicy policy, Listener<VirtualControlLoopNotification> policyClMgt) {
+ String policyName = policy.getIdentifier().getName();
+
+ policyClMgt.await(notif -> notif.getNotification() == ControlLoopNotificationType.ACTIVE
+ && (policyName + ".EVENT").equals(notif.getPolicyName()));
+
+ policyClMgt.await(notif -> notif.getNotification() == ControlLoopNotificationType.OPERATION
+ && (policyName + ".EVENT.MANAGER.PROCESSING").equals(notif.getPolicyName())
+ && notif.getMessage().startsWith("Sending guard query"));
+
+ policyClMgt.await(notif -> notif.getNotification() == ControlLoopNotificationType.OPERATION
+ && (policyName + ".EVENT.MANAGER.PROCESSING").equals(notif.getPolicyName())
+ && notif.getMessage().startsWith("Guard result") && notif.getMessage().endsWith("Permit"));
+
+ policyClMgt.await(notif -> notif.getNotification() == ControlLoopNotificationType.OPERATION
+ && (policyName + ".EVENT.MANAGER.PROCESSING").equals(notif.getPolicyName())
+ && notif.getMessage().startsWith("actor="));
+ }
+
+ @Override
+ protected VirtualControlLoopNotification waitForFinal(ToscaPolicy policy,
+ Listener<VirtualControlLoopNotification> policyClMgt, ControlLoopNotificationType finalType) {
+
+ return policyClMgt.await(notif -> notif.getNotification() == finalType
+ && (policy.getIdentifier().getName() + ".EVENT.MANAGER.FINAL").equals(notif.getPolicyName()));
+ }
+
+ @Override
+ protected ToscaPolicy checkPolicy(String fileName) {
+ try {
+ policy = Rules.getPolicyFromFile(fileName);
+ } catch (CoderException e) {
+ throw new IllegalArgumentException(fileName, e);
+ }
+ controller.getDrools().offer(policy);
+ return policy;
+ }
+
+ @Override
+ protected Listener<VirtualControlLoopNotification> createNoficationTopicListener() {
+ return topics.createListener(POLICY_CL_MGT_TOPIC,
+ VirtualControlLoopNotification.class, controller);
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/tdjam/TdjamControllerTest.java b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/tdjam/TdjamControllerTest.java
new file mode 100644
index 000000000..5edba8701
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/controlloop/tdjam/TdjamControllerTest.java
@@ -0,0 +1,240 @@
+/*-
+ * ============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.tdjam;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.onap.policy.drools.properties.DroolsPropertyConstants.PROPERTY_CONTROLLER_TYPE;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.UUID;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.controlloop.CanonicalOnset;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+import org.onap.policy.controlloop.common.rules.test.Listener;
+import org.onap.policy.controlloop.common.rules.test.Topics;
+import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
+import org.onap.policy.drools.controller.DroolsControllerConstants;
+import org.onap.policy.drools.system.PolicyControllerConstants;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.drools.utils.PropertyUtil;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
+import org.powermock.reflect.Whitebox;
+import org.slf4j.LoggerFactory;
+
+public class TdjamControllerTest {
+ private static Properties prop;
+ private static Logger logger = (Logger) LoggerFactory.getLogger(TdjamController.class);
+ private static ListAppender<ILoggingEvent> appender = new ListAppender<ILoggingEvent>();
+
+ /**
+ * Setup appender, and initialize properties.
+ */
+ @BeforeClass
+ public static void setupClass() throws Exception {
+ logger.setLevel(Level.DEBUG);
+ logger.addAppender(appender);
+
+ prop = PropertyUtil.getProperties("src/test/resources/config/tdjam-controller.properties");
+ prop.setProperty(PROPERTY_CONTROLLER_TYPE, "tdjam");
+
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+
+ }
+
+ /**
+ * Remove appender.
+ */
+ @AfterClass
+ public static void cleanupClass() {
+
+ PolicyEngineConstants.getManager().stop();
+ PolicyEngineConstants.getManager().getExecutorService().shutdown();
+
+ appender.stop();
+ System.out.println("APPENDER:");
+ for (ILoggingEvent event : appender.list) {
+ System.out.println(" " + event);
+ }
+ logger.detachAppender(appender);
+ }
+
+ @Test
+ public void toscaPolicyTests() {
+ TdjamController tc = (TdjamController) PolicyControllerConstants.getFactory().build("tc", prop);
+ assertTrue(PolicyControllerConstants.getFactory().inventory().contains(tc));
+ assertTrue(DroolsControllerConstants.getFactory().inventory().contains(tc));
+
+ final HashSet<ToscaPolicy> toscaPolicies = new HashSet<>();
+ final HashSet<ControlLoopParams> controlLoopParams = new HashSet<>();
+
+ ToscaPolicy a1 = buildToscaPolicy("a", "1", tc);
+ ToscaPolicy a2 = buildToscaPolicy("a", "2", tc);
+ ToscaPolicy b1 = buildToscaPolicy("b", "1", tc);
+
+ toscaPolicies.add(a1);
+ toscaPolicies.add(a2);
+ toscaPolicies.add(b1);
+
+ assertSame(a1, tc.getToscaPolicy("a", "1"));
+ assertSame(a2, tc.getToscaPolicy("a", "2"));
+ assertSame(b1, tc.getToscaPolicy("b", "1"));
+ assertEquals(toscaPolicies, tc.getAllToscaPolicies());
+
+ // create associated ControlLoopParams
+ final ControlLoopParams clpa1 = buildControlLoopParams("a", "1", "clpa1", tc);
+ final ControlLoopParams clpa2 = buildControlLoopParams("a", "2", "clpa2", tc);
+ final ControlLoopParams clpb1 = buildControlLoopParams("b", "1", "clpb1", tc);
+ final ControlLoopParams clpb3 = buildControlLoopParams("b", "3", "clpb3", null);
+
+ // the add for 'clpb3' should fail, because there is no ToscaPolicy
+ startLog();
+ assertSame(clpb3, tc.addControlLoopParams(clpb3));
+ stopLog();
+ assertLog(".*Missing ToscaPolicy, name=b, version=3.*");
+ assertNull(tc.removeControlLoopParams("clpb3"));
+
+ controlLoopParams.add(clpa1);
+ controlLoopParams.add(clpa2);
+ controlLoopParams.add(clpb1);
+ assertEquals(controlLoopParams, new HashSet<>(tc.getAllControlLoopParams()));
+
+ // manually remove a ControlLoopParams
+ assertSame(clpa1, tc.removeControlLoopParams("clpa1"));
+ assertTrue(controlLoopParams.remove(clpa1));
+ assertEquals(controlLoopParams, new HashSet<>(tc.getAllControlLoopParams()));
+
+ // tests of nonexistent policies
+ assertNull(tc.getToscaPolicy("c", "1")); // non-existent name
+ assertNull(tc.removeToscaPolicy("c", "1"));
+ assertNull(tc.getToscaPolicy("b", "3")); // non-existent version
+ assertNull(tc.removeToscaPolicy("b", "3"));
+
+ assertSame(a1, tc.removeToscaPolicy("a", "1"));
+ assertTrue(toscaPolicies.remove(a1));
+ assertEquals(toscaPolicies, tc.getAllToscaPolicies());
+ assertSame(a2, tc.removeToscaPolicy("a", "2"));
+ assertTrue(toscaPolicies.remove(a2));
+ assertEquals(toscaPolicies, tc.getAllToscaPolicies());
+
+ // ControlLoopParams removal should be automatic
+ assertTrue(controlLoopParams.remove(clpa2));
+ assertEquals(controlLoopParams, new HashSet<>(tc.getAllControlLoopParams()));
+
+ // test reset method
+ tc.reset();
+ assertTrue(tc.getAllToscaPolicies().isEmpty());
+ assertTrue(tc.getAllControlLoopParams().isEmpty());
+ assertTrue(tc.getAllEventManagers().isEmpty());
+ assertTrue(tc.getAllOnsetToEventManager().isEmpty());
+
+ PolicyControllerConstants.getFactory().shutdown(tc);
+ assertFalse(PolicyControllerConstants.getFactory().inventory().contains(tc));
+ assertFalse(DroolsControllerConstants.getFactory().inventory().contains(tc));
+ }
+
+ @Test
+ public void onsetErrors() throws Exception {
+ TdjamController tc = (TdjamController) PolicyControllerConstants.getFactory().build("tc", prop);
+ assertTrue(PolicyControllerConstants.getFactory().inventory().contains(tc));
+ assertTrue(DroolsControllerConstants.getFactory().inventory().contains(tc));
+ tc.start();
+
+ buildToscaPolicy("a", "1", tc);
+ final ControlLoopParams clpa1 = buildControlLoopParams("a", "1", "clpa1", tc);
+ assertTrue(tc.getAllControlLoopParams().contains(clpa1));
+
+ CanonicalOnset canonicalOnset = new CanonicalOnset();
+ startLog();
+ Whitebox.invokeMethod(tc, "processEvent", canonicalOnset);
+ stopLog();
+ assertLog(".*No ControlLoopParams for event: CanonicalOnset.*");
+
+ // set Name with new canonicalOnset
+ CanonicalOnset canonicalOnset2 = new CanonicalOnset();
+ canonicalOnset2.setClosedLoopControlName("clpa1");
+ // try with a non-null requestID, but missing target
+ canonicalOnset2.setRequestId(UUID.randomUUID());
+ startLog();
+ Whitebox.invokeMethod(tc, "processEvent", canonicalOnset2);
+ stopLog();
+ assertLog(".*Exception creating ControlLoopEventManager.*");
+
+ PolicyControllerConstants.getFactory().shutdown(tc);
+ assertFalse(PolicyControllerConstants.getFactory().inventory().contains(tc));
+ }
+
+ private ToscaPolicy buildToscaPolicy(String name, String version, TdjamController tc) {
+ ToscaPolicy tp = new ToscaPolicy();
+ tp.setName(name);
+ tp.setVersion(version);
+
+ if (tc != null) {
+ tc.addToscaPolicy(tp);
+ }
+ return tp;
+ }
+
+ private ControlLoopParams buildControlLoopParams(String name, String version,
+ String closedLoopControlName, TdjamController tc) {
+
+ ControlLoopParams clp = new ControlLoopParams();
+ clp.setPolicyName(name);
+ clp.setPolicyVersion(version);
+ clp.setClosedLoopControlName(closedLoopControlName);
+
+ if (tc != null) {
+ assertTrue(tc.addControlLoopParams(clp) != clp);
+ }
+
+ return clp;
+ }
+
+ private void startLog() {
+ appender.list.clear();
+ appender.start();
+ }
+
+ private void stopLog() {
+ appender.stop();
+ }
+
+ private void assertLog(String regexp) {
+ for (ILoggingEvent event : appender.list) {
+ if (event.toString().matches(regexp)) {
+ return;
+ }
+ }
+ fail("Missing log entry: " + regexp);
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/extension/system/NonDroolsPolicyControllerTest.java b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/extension/system/NonDroolsPolicyControllerTest.java
new file mode 100644
index 000000000..ee96cb893
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/java/org/onap/policy/extension/system/NonDroolsPolicyControllerTest.java
@@ -0,0 +1,343 @@
+/*
+ * ============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.extension.system;
+
+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.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.onap.policy.drools.properties.DroolsPropertyConstants.PROPERTY_CONTROLLER_TYPE;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Function;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+import org.onap.policy.drools.controller.DroolsController;
+import org.onap.policy.drools.features.DroolsControllerFeatureApi;
+import org.onap.policy.drools.features.PolicyControllerFeatureApi;
+import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyControllerConstants;
+import org.onap.policy.drools.utils.PropertyUtil;
+
+public class NonDroolsPolicyControllerTest {
+ //public static boolean loop = true;
+ private static Properties prop;
+
+ @BeforeClass
+ public static void setupClass() throws Exception {
+ prop = PropertyUtil.getProperties("src/test/resources/config/tdjam-controller.properties");
+ }
+
+ @Test
+ public void testState() {
+ NonDroolsPolicyController controller = buildController("tdjam");
+
+ assertEquals("nondrools", controller.getName());
+ assertEquals("NonDroolsPolicyController", controller.getGroupId());
+ assertEquals("nondrools", controller.getArtifactId());
+ assertEquals("1.0", controller.getVersion());
+ assertTrue(controller.isBrained());
+
+ assertFalse(controller.isAlive());
+ assertFalse(controller.isLocked());
+
+ // first 'start()'
+ controller.start();
+ assertTrue(controller.isAlive());
+ assertFalse(controller.isLocked());
+
+ // second 'start()'
+ controller.start();
+ assertTrue(controller.isAlive());
+ assertFalse(controller.isLocked());
+
+ // test a few stubbed-off methods
+ assertTrue(controller.getSessionNames().isEmpty());
+ assertTrue(controller.getCanonicalSessionNames().isEmpty());
+ assertTrue(controller.getBaseDomainNames().isEmpty());
+ assertFalse(controller.offer("topic", "event"));
+ assertFalse(controller.offer("event"));
+ assertEquals(0, controller.getRecentSourceEvents().length);
+ assertEquals(0, controller.getRecentSinkEvents().length);
+ assertNull(controller.getContainer());
+ assertThatIllegalArgumentException().isThrownBy(
+ () -> controller.fetchModelClass("NoSuchClass"));
+ assertThatIllegalStateException().isThrownBy(
+ () -> controller.updateToVersion(null, null, null, null, null));
+ assertTrue(controller.factClassNames(null).isEmpty());
+ assertEquals(0, controller.factCount(null));
+ assertTrue(controller.facts(null, null, false).isEmpty());
+ assertTrue(controller.facts("sessionName", String.class).isEmpty());
+ assertTrue(controller.factQuery(null, null, null, false).isEmpty());
+ assertFalse(controller.delete("sessionName", "fact"));
+ assertFalse(controller.delete("fact"));
+ assertFalse(controller.delete("sessionName", String.class));
+ assertFalse(controller.delete(String.class));
+
+ controller.lock();
+ assertTrue(controller.isAlive());
+ assertTrue(controller.isLocked());
+
+ controller.stop();
+ assertFalse(controller.isAlive());
+ assertTrue(controller.isLocked());
+
+ controller.unlock();
+ assertFalse(controller.isAlive());
+ assertFalse(controller.isLocked());
+
+ destroy(controller);
+ }
+
+ @Test
+ public void deliverTest() {
+ DroolsControllerFeatureHandler.resetStats();
+ final NonDroolsPolicyController controller = buildController("tdjam");
+
+ final TopicSink topicSink = mock(TopicSink.class);
+ when(topicSink.getTopic()).thenReturn("POLICY-CL-MGT");
+ when(topicSink.send(any())).thenReturn(false);
+
+ final VirtualControlLoopNotification msg = new VirtualControlLoopNotification(null);
+
+ controller.lock();
+
+ // invalid sink
+ try {
+ controller.deliver(null, null);
+ fail("Expected IllegalArgumentException did not occur");
+ } catch (IllegalArgumentException ex) {
+ assertTrue(ex.getMessage(),
+ ex.getMessage().endsWith(" invalid sink"));
+ }
+
+ // invalid event
+ try {
+ controller.deliver(topicSink, null);
+ fail("Expected IllegalArgumentException did not occur");
+ } catch (IllegalArgumentException ex) {
+ assertTrue(ex.getMessage(),
+ ex.getMessage().endsWith(" invalid event"));
+ }
+
+ // is locked
+ try {
+ controller.deliver(topicSink, "event");
+ fail("Expected IllegalStateException did not occur");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage(),
+ ex.getMessage().endsWith(" is locked"));
+ }
+ controller.unlock();
+
+ // is stopped
+ try {
+ controller.deliver(topicSink, "event");
+ fail("Expected IllegalStateException did not occur");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage(),
+ ex.getMessage().endsWith(" is stopped"));
+ }
+
+ // there should have been 4 'beforeDeliver' calls up to this point
+ assertEquals(4, DroolsControllerFeatureHandler.beforeDeliverFalse);
+
+ Function<String, Boolean> signal = (sig) -> {
+ msg.getAai().put("signal", sig);
+ return controller.deliver(topicSink, msg);
+ };
+
+ controller.start();
+
+ // 'beforeDeliver' intercepts
+ DroolsControllerFeatureHandler.resetStats();
+
+ assertTrue(signal.apply("beforeDeliverTrue"));
+ assertEquals(1, DroolsControllerFeatureHandler.beforeDeliverTrue);
+ assertEquals(0, DroolsControllerFeatureHandler.afterDeliverFalse);
+
+ assertFalse(signal.apply("beforeDeliverException"));
+ assertEquals(1, DroolsControllerFeatureHandler.beforeDeliverException);
+ assertEquals(1, DroolsControllerFeatureHandler.afterDeliverFalse);
+ // it would be nice to check the log message at this point
+
+ // 'afterDeliver' intercepts
+ DroolsControllerFeatureHandler.resetStats();
+
+ assertTrue(signal.apply("afterDeliverTrue"));
+ assertEquals(1, DroolsControllerFeatureHandler.afterDeliverTrue);
+
+ assertFalse(signal.apply("afterDeliverException"));
+ assertEquals(1, DroolsControllerFeatureHandler.afterDeliverException);
+
+ assertFalse(signal.apply("nothing in particular"));
+ assertEquals(1, DroolsControllerFeatureHandler.afterDeliverFalse);
+
+ destroy(controller);
+ }
+
+ private NonDroolsPolicyController buildController(String type) {
+ prop.setProperty(PROPERTY_CONTROLLER_TYPE, type);
+ PolicyController controller =
+ PolicyControllerConstants.getFactory().build("nondrools", prop);
+ assertTrue(controller instanceof NonDroolsPolicyController);
+ return (NonDroolsPolicyController) controller;
+ }
+
+ private void destroy(PolicyController controller) {
+ String name = controller.getName();
+ assertSame(controller, PolicyControllerConstants.getFactory().get(name));
+ PolicyControllerConstants.getFactory().destroy(controller);
+ assertThatIllegalArgumentException().isThrownBy(
+ () -> PolicyControllerConstants.getFactory().get(name));
+ }
+
+ /* ============================================================ */
+
+ /**
+ * An instance of this class is called by 'IndexedPolicyControllerFactory'.
+ * It does the build operation when the value of the 'controller.type'
+ * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
+ */
+ public static class PolicyBuilder implements PolicyControllerFeatureApi {
+ @Override
+ public int getSequenceNumber() {
+ return 1;
+ }
+
+ @Override
+ public PolicyController beforeInstance(String name, Properties properties) {
+ if ("nondrools".equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
+ return new NonDroolsPolicyController(name, properties);
+ }
+ return null;
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * An instance of this class is called by 'IndexedDroolsControllerFactory'.
+ * It does the build operation when the value of the 'controller.type'
+ * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
+ */
+ public static class DroolsBuilder implements DroolsControllerFeatureApi {
+ @Override
+ public int getSequenceNumber() {
+ return 1;
+ }
+
+ @Override
+ public DroolsController beforeInstance(Properties properties,
+ String groupId, String artifactId, String version,
+ List<TopicCoderFilterConfiguration> decoderConfigurations,
+ List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
+
+ if ("nondrools".equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
+ return NonDroolsPolicyController.getBuildInProgress();
+ }
+ return null;
+ }
+ }
+
+ /* ============================================================ */
+
+ public static class DroolsControllerFeatureHandler implements DroolsControllerFeatureApi {
+ static int beforeDeliverFalse = 0;
+ static int beforeDeliverTrue = 0;
+ static int beforeDeliverException = 0;
+ static int afterDeliverFalse = 0;
+ static int afterDeliverTrue = 0;
+ static int afterDeliverException = 0;
+
+ private static void resetStats() {
+ beforeDeliverFalse = 0;
+ beforeDeliverTrue = 0;
+ beforeDeliverException = 0;
+ afterDeliverFalse = 0;
+ afterDeliverTrue = 0;
+ afterDeliverException = 0;
+ }
+
+ @Override
+ public int getSequenceNumber() {
+ return 1;
+ }
+
+ @Override
+ public boolean beforeDeliver(DroolsController controller, TopicSink sink, Object fact) {
+ if (fact instanceof VirtualControlLoopNotification) {
+ String factString = ((VirtualControlLoopNotification) fact).getAai().get("signal");
+ if (factString == null) {
+ // this hook is run during 'FrankfurtTest' as well
+ return false;
+ }
+ if (factString.contains("beforeDeliverTrue")) {
+ beforeDeliverTrue += 1;
+ return true;
+ }
+ if (factString.contains("beforeDeliverException")) {
+ beforeDeliverException += 1;
+ RuntimeException ex = new RuntimeException("beforeDeliver");
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+ beforeDeliverFalse += 1;
+ return false;
+ }
+
+
+ @Override
+ public boolean afterDeliver(DroolsController controller, TopicSink sink, Object fact,
+ String json, boolean success) {
+
+ if (fact instanceof VirtualControlLoopNotification) {
+ String factString = ((VirtualControlLoopNotification) fact).getAai().get("signal");
+ if (factString == null) {
+ // this hook is run during 'FrankfurtTest' as well
+ return false;
+ }
+ if (factString.contains("afterDeliverTrue")) {
+ afterDeliverTrue += 1;
+ return true;
+ }
+ if (factString.contains("afterDeliverException")) {
+ afterDeliverException += 1;
+ throw new RuntimeException("afterDeliver");
+ }
+ }
+ afterDeliverFalse += 1;
+ return false;
+ }
+ }
+}
diff --git a/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi b/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi
new file mode 100644
index 000000000..bb7cf8e3f
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.DroolsControllerFeatureApi
@@ -0,0 +1,3 @@
+org.onap.policy.extension.system.NonDroolsPolicyControllerTest$DroolsControllerFeatureHandler
+org.onap.policy.extension.system.NonDroolsPolicyControllerTest$DroolsBuilder
+org.onap.policy.controlloop.tdjam.TdjamController$DroolsBuilder
diff --git a/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi b/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi
new file mode 100644
index 000000000..4f2764376
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/META-INF/services/org.onap.policy.drools.features.PolicyControllerFeatureApi
@@ -0,0 +1,2 @@
+org.onap.policy.controlloop.tdjam.TdjamController$PolicyBuilder
+org.onap.policy.extension.system.NonDroolsPolicyControllerTest$PolicyBuilder
diff --git a/controlloop/common/controller-tdjam/src/test/resources/config/event-manager.properties b/controlloop/common/controller-tdjam/src/test/resources/config/event-manager.properties
new file mode 100644
index 000000000..f5be41c35
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/config/event-manager.properties
@@ -0,0 +1,83 @@
+#
+# ============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========================================================
+#
+
+# DB parameters
+operation.history.url=jdbc:h2:mem:Tdjam
+operation.history.userName=sa
+operation.history.password=
+
+# Actor parameters
+#
+# Note: every operation must have at least one entry, otherwise it will not be
+# configured and started. Thus some of them have a "placeholder" property.
+#
+
+#actor.service.GUARD.disabled=true
+actor.service.GUARD.clientName=GUARD
+actor.service.GUARD.onapName=my-onap-name
+actor.service.GUARD.onapComponent=my-onap-component
+actor.service.GUARD.onapInstance=my-onap-instance
+actor.service.GUARD.operations.Decision.path=decision
+
+actor.service.AAI.clientName=AAI
+actor.service.AAI.operations.CustomQuery.path=aai/v16/query
+actor.service.AAI.operations.Pnf.path=aai/v16/network/pnfs/pnf
+actor.service.AAI.operations.Tenant.path=aai/v16/search/nodes-query
+
+actor.service.APPC.sinkTopic=APPC-LCM-READ
+actor.service.APPC.sourceTopic=APPC-LCM-WRITE
+actor.service.APPC.operations.ConfigModify.placeholder=
+actor.service.APPC.operations.Migrate.placeholder=
+actor.service.APPC.operations.Restart.placeholder=
+actor.service.APPC.operations.Rebuild.placeholder=
+
+# legacy APPC - must specify sink and source for each operation
+actor.service.APPC.operations.ModifyConfig.sinkTopic=APPC-CL
+actor.service.APPC.operations.ModifyConfig.sourceTopic=APPC-CL
+
+actor.service.CDS.operations.any.host=localhost
+actor.service.CDS.operations.any.port=7878
+actor.service.CDS.operations.any.username=grpc-username
+actor.service.CDS.operations.any.password=grpc-password
+actor.service.CDS.operations.any.timeout=10
+
+actor.service.SDNC.clientName=SDNC
+actor.service.SDNC.operations.BandwidthOnDemand.path=\
+ GENERIC-RESOURCE-API:vf-module-topology-operation
+actor.service.SDNC.operations.Reroute.path=\
+ GENERIC-RESOURCE-API:network-topology-operation
+
+actor.service.SDNR.sinkTopic=SDNR-CL
+actor.service.SDNR.sourceTopic=SDNR-CL-RSP
+actor.service.SDNR.operations.any.placeholder=
+
+actor.service.SO.clientName=SO
+actor.service.SO.pollPath=orchestrationRequests/v5/
+actor.service.SO.maxPolls=20
+actor.service.SO.pollWaitSec=20
+actor.service.SO.operations.VF\ Module\ Create.path=serviceInstantiation/v7/serviceInstances
+actor.service.SO.operations.VF\ Module\ Delete.path=serviceInstances/v7
+
+actor.service.VFC.clientName=VFC
+actor.service.VFC.pollPath=jobs
+actor.service.VFC.maxPolls=20
+actor.service.VFC.pollWaitSec=20
+actor.service.VFC.operations.Restart.path=ns
+actor.service.VFC.operations.Restart.timeoutSec=60
diff --git a/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-controller.properties b/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-controller.properties
new file mode 100644
index 000000000..41db06c51
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-controller.properties
@@ -0,0 +1,64 @@
+#
+# ============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=========================================================
+#
+
+controller.name=tdjam
+controller.type=tdjam
+
+rules.groupId=NonDroolsPolicyController
+rules.artifactId=tdjam
+rules.version=1.0.0
+
+noop.source.topics=DCAE_TOPIC,APPC-CL,APPC-LCM-WRITE,SDNR-CL-RSP,POLICY-CL-MGT,APPC-LCM-READ
+
+noop.source.topics.DCAE_TOPIC.events=\
+ org.onap.policy.controlloop.CanonicalOnset,org.onap.policy.controlloop.CanonicalAbated
+noop.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalOnset.\
+ filter=[?($.closedLoopEventStatus == 'ONSET')]
+noop.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalAbated.\
+ filter=[?($.closedLoopEventStatus == 'ABATED')]
+noop.source.topics.DCAE_TOPIC.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gson
+
+noop.source.topics.APPC-CL.events=org.onap.policy.appc.Response,org.onap.policy.appc.Request
+noop.source.topics.APPC-CL.events.org.onap.policy.appc.Response.filter=[?($.CommonHeader && $.Status)]
+noop.source.topics.APPC-CL.events.org.onap.policy.appc.Request.filter=[?($.CommonHeader && $.Action)]
+noop.source.topics.APPC-CL.events.custom.gson=org.onap.policy.appc.util.Serialization,gsonPretty
+
+noop.source.topics.APPC-LCM-WRITE.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper
+noop.source.topics.APPC-LCM-WRITE.events.org.onap.policy.appclcm.AppcLcmDmaapWrapper.filter=[?($.type == 'response')]
+noop.source.topics.APPC-LCM-WRITE.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson
+
+noop.source.topics.SDNR-CL-RSP.events=org.onap.policy.sdnr.PciResponseWrapper
+noop.source.topics.SDNR-CL-RSP.events.org.onap.policy.sdnr.PciResponseWrapper.filter=[?($.type == 'response')]
+noop.source.topics.SDNR-CL-RSP.events.custom.gson=org.onap.policy.sdnr.util.Serialization,gson
+
+noop.source.topics.POLICY-CL-MGT.events=org.onap.policy.controlloop.VirtualControlLoopNotification
+noop.source.topics.POLICY-CL-MGT.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty
+
+noop.source.topics.APPC-LCM-READ.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper
+noop.source.topics.APPC-LCM-READ.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson
+
+noop.sink.topics=APPC-CL,APPC-LCM-READ,POLICY-CL-MGT,SDNR-CL,DCAE_CL_RSP
+
+noop.sink.topics.POLICY-CL-MGT.events=org.onap.policy.controlloop.VirtualControlLoopNotification
+noop.sink.topics.POLICY-CL-MGT.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty
+
+noop.sink.topics.DCAE_CL_RSP.events=org.onap.policy.controlloop.ControlLoopResponse
+noop.sink.topics.DCAE_CL_RSP.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty
+
diff --git a/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-http-client.properties b/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-http-client.properties
new file mode 100644
index 000000000..1e3e88cec
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/config/tdjam-http-client.properties
@@ -0,0 +1,52 @@
+#
+# ============LICENSE_START=======================================================
+# ONAP
+# ================================================================================
+# Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+#
+
+http.client.services=GUARD,AAI,SDNC,SO,VFC
+
+http.client.services.GUARD.managed=true
+http.client.services.GUARD.host=localhost
+http.client.services.GUARD.port=6669
+http.client.services.GUARD.userName=pdpx
+http.client.services.GUARD.password=pdpx
+http.client.services.GUARD.contextUriPath=policy/pdpx/v1/
+
+http.client.services.AAI.managed=true
+http.client.services.AAI.host=localhost
+http.client.services.AAI.port=6666
+http.client.services.AAI.contextUriPath=
+
+http.client.services.SDNC.managed=true
+http.client.services.SDNC.host=localhost
+http.client.services.SDNC.port=6665
+http.client.services.SDNC.userName=sdnc
+http.client.services.SDNC.password=sdnc
+http.client.services.SDNC.contextUriPath=
+
+http.client.services.SO.managed=true
+http.client.services.SO.host=localhost
+http.client.services.SO.port=6667
+http.client.services.SO.contextUriPath=
+
+http.client.services.VFC.managed=true
+http.client.services.VFC.host=localhost
+http.client.services.VFC.port=6668
+http.client.services.VFC.userName=VFC
+http.client.services.VFC.password=VFC
+http.client.services.VFC.contextUriPath=api/nslcm/v1
diff --git a/controlloop/common/controller-tdjam/src/test/resources/logback-test.xml b/controlloop/common/controller-tdjam/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..656cb8136
--- /dev/null
+++ b/controlloop/common/controller-tdjam/src/test/resources/logback-test.xml
@@ -0,0 +1,38 @@
+<!--
+ ============LICENSE_START=======================================================
+ controller-tdjam
+ ================================================================================
+ 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=========================================================
+ -->
+
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{dd-MMM-yyyy HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <!-- the following line doesn't seem necessary, but it is needed for some reason -->
+ <logger name="org.onap.policy.controlloop.tdjam" level="debug" additivity="false">
+ <appender-ref ref="STDOUT" />
+ </logger>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT" />
+ </root>
+</configuration>