3b3655122995a0a24ed8dcb568496663218a5433
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.controlloop.tdjam;
22
23 import static org.onap.policy.drools.properties.DroolsPropertyConstants.PROPERTY_CONTROLLER_TYPE;
24
25 import java.io.ByteArrayOutputStream;
26 import java.io.PrintStream;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Properties;
35 import java.util.UUID;
36 import java.util.concurrent.ConcurrentHashMap;
37 import org.onap.policy.common.endpoints.event.comm.Topic;
38 import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
39 import org.onap.policy.common.endpoints.event.comm.TopicListener;
40 import org.onap.policy.common.endpoints.event.comm.TopicSource;
41 import org.onap.policy.controlloop.CanonicalOnset;
42 import org.onap.policy.controlloop.ControlLoopEvent;
43 import org.onap.policy.controlloop.ControlLoopException;
44 import org.onap.policy.controlloop.ControlLoopNotificationType;
45 import org.onap.policy.controlloop.ControlLoopResponse;
46 import org.onap.policy.controlloop.VirtualControlLoopEvent;
47 import org.onap.policy.controlloop.VirtualControlLoopNotification;
48 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
49 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2;
50 import org.onap.policy.controlloop.utils.ControlLoopUtils;
51 import org.onap.policy.drools.controller.DroolsController;
52 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
53 import org.onap.policy.drools.features.PolicyControllerFeatureApi;
54 import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
55 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
56 import org.onap.policy.drools.protocol.coders.ProtocolCoderToolset;
57 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
58 import org.onap.policy.drools.system.PolicyController;
59 import org.onap.policy.drools.system.PolicyEngineConstants;
60 import org.onap.policy.extension.system.NonDroolsPolicyController;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 /**
66  * This replaces a Drools session with Java code. Although Drools memory
67  * is simulated when running the Junit tests, there is no actual use of
68  * Drools here.
69  */
70 public class TdjamController extends NonDroolsPolicyController {
71     private static Logger logger = LoggerFactory.getLogger(TdjamController.class);
72
73     // the 'controller.type' property is set to this value
74     private static final String TDJAM_CONTROLLER_BUILDER_TAG = "tdjam";
75
76     // topic on which to publish event notifications
77     private static final String POLICY_CL_MGT = "POLICY-CL-MGT";
78
79     // additional data associated with session
80     private final String groupId;
81     private final String artifactId;
82
83     // top-level tosca policy table (first key = name, second key = version)
84     private final Map<String, Map<String, ToscaPolicy>> toscaPolicies = new HashMap<>();
85
86     // maps 'controlLoopControlName' to 'ControlLoopParams'
87     private final Map<String, ControlLoopParams> controlLoopParams = new HashMap<>();
88
89     // maps 'requestId' to 'ControlLoopEventManager'
90     private final Map<UUID, ControlLoopEventManager> eventManagers = new ConcurrentHashMap<>();
91
92     // maps onset to 'ControlLoopEventManager'
93     private final Map<VirtualControlLoopEvent, ControlLoopEventManager> onsetToEventManager = new ConcurrentHashMap<>();
94
95     // maps 'topic' to 'TopicData'
96     private final Map<String, TopicData> topicDataTable = new ConcurrentHashMap<>();
97
98     /* ============================================================ */
99
100     /**
101      * Initialize a new 'TdjamController'.
102      *
103      * @param name the controller name
104      * @param properties properties defining the controller
105      */
106     public TdjamController(String name, Properties properties) {
107         super(name, properties);
108
109         this.groupId = getGroupId();
110         this.artifactId = getArtifactId();
111
112         init();
113     }
114
115     private void init() {
116         // go through all of the incoming message decoders associated
117         // with this controller
118         for (ProtocolCoderToolset pct :
119                 EventProtocolCoderConstants.getManager()
120                 .getDecoders(groupId, artifactId)) {
121             // go through the 'CoderFilters' instances, and see if there are
122             // any that we are interested in
123             for (CoderFilters cf : pct.getCoders()) {
124                 try {
125                     Class<?> clazz = Class.forName(cf.getCodedClass());
126                     if (ControlLoopEvent.class.isAssignableFrom(clazz)) {
127                         // this one is of interest
128                         logger.debug("TdjamController using CoderFilters: {}", cf);
129                         getTopicData(pct.getTopic());
130                     }
131                 } catch (ClassNotFoundException e) {
132                     logger.error("CoderFilter refers to unknown class: {}",
133                                  cf.getCodedClass(), e);
134                 }
135             }
136         }
137
138         // start all 'TopicData' instances
139         for (TopicData topicData : topicDataTable.values()) {
140             topicData.start();
141         }
142     }
143
144     @Override
145     public <T> boolean offer(T object) {
146         if (object instanceof ToscaPolicy) {
147             addToscaPolicy((ToscaPolicy) object);
148             return true;
149         }
150         return false;
151     }
152
153     /**
154      * Add or replace a ToscaPolicy instance. The policy is keyed by name and
155      * version.
156      *
157      * @param toscaPolicy the ToscaPolicy being added
158      * @return if a ToscaPolicy with this name/version previously existed within
159      *     this TdjamController, it is returned; otherwise, 'null' is returned.
160      */
161     public synchronized ToscaPolicy addToscaPolicy(ToscaPolicy toscaPolicy) {
162         Map<String, ToscaPolicy> level2 =
163             toscaPolicies.computeIfAbsent(toscaPolicy.getName(),
164                 key -> new HashMap<String, ToscaPolicy>());
165         ToscaPolicy prev = level2.put(toscaPolicy.getVersion(), toscaPolicy);
166         if (prev != null) {
167             // update 'ControlLoopParams' entries
168             for (ControlLoopParams clp : controlLoopParams.values()) {
169                 if (clp.getToscaPolicy() == prev) {
170                     clp.setToscaPolicy(toscaPolicy);
171                 }
172             }
173         }
174         logger.debug("ToscaPolicy name={}, version={}, count={}, prev={}",
175             toscaPolicy.getName(), toscaPolicy.getVersion(), toscaPolicies.size(), (prev != null));
176         dumpTables();
177
178         // attempt to create a 'ControlLoopParams' instance from this object
179         ControlLoopParams params =
180             ControlLoopUtils.toControlLoopParams(toscaPolicy);
181         if (params != null) {
182             addControlLoopParams(params);
183         }
184         return prev;
185     }
186
187     /**
188      * Remove a ToscaPolicy instance associated with the specified name and
189      * version.
190      *
191      * @param name the name of the ToscaPolicy to remove
192      * @param version the version of the ToscaPolicy to remove
193      * @return the ToscaPolicy that was removed, or 'null' if not found
194      */
195     public synchronized ToscaPolicy removeToscaPolicy(String name, String version) {
196         ToscaPolicy prev = null;
197         Map<String, ToscaPolicy> level2 = toscaPolicies.get(name);
198
199         if (level2 != null && (prev = level2.remove(version)) != null) {
200             // remove all 'ControlLoopParams' entries referencing this policy
201             for (ControlLoopParams clp :
202                     new ArrayList<>(controlLoopParams.values())) {
203                 if (clp.getToscaPolicy() == prev) {
204                     controlLoopParams.remove(clp.getClosedLoopControlName());
205                 }
206             }
207         }
208         return prev;
209     }
210
211     /**
212      * Fetch a ToscaPolicy instance associated with the specified name and
213      * version.
214      *
215      * @param name the name of the ToscaPolicy
216      * @param version the version of the ToscaPolicy
217      * @return the ToscaPolicy, or 'null' if not found
218      */
219     public synchronized ToscaPolicy getToscaPolicy(String name, String version) {
220         Map<String, ToscaPolicy> level2 = toscaPolicies.get(name);
221         return (level2 == null ? null : level2.get(version));
222     }
223
224     /**
225      * Return a collection of all ToscaPolicy instances.
226      *
227      * @return all ToscaPolicy instances
228      */
229     public synchronized Collection<ToscaPolicy> getAllToscaPolicies() {
230         HashSet<ToscaPolicy> rval = new HashSet<>();
231         for (Map<String, ToscaPolicy> map : toscaPolicies.values()) {
232             rval.addAll(map.values());
233         }
234         return rval;
235     }
236
237     /**
238      * Add a new 'ControlLoopParams' instance -- they are keyed by
239      * 'closedLoopControlName'.
240      *
241      * @param clp the 'ControlLoopParams' instance to add
242      * @return the 'ControlLoopParams' instance previously associated with the
243      *     'closedLoopControlName' ('null' if it didn't exist)
244      */
245     public synchronized ControlLoopParams addControlLoopParams(ControlLoopParams clp) {
246         ToscaPolicy toscaPolicy =
247             getToscaPolicy(clp.getPolicyName(), clp.getPolicyVersion());
248         if (toscaPolicy == null) {
249             // there needs to be a 'ToscaPolicy' instance with a matching
250             // name/version
251             logger.debug("Missing ToscaPolicy, name={}, version={}",
252                          clp.getPolicyName(), clp.getPolicyVersion());
253             return clp;
254         }
255
256         clp.setToscaPolicy(toscaPolicy);
257         ControlLoopParams prev =
258             controlLoopParams.put(clp.getClosedLoopControlName(), clp);
259
260         logger.debug("ControlLoopParams name={}, version={}, closedLoopControlName={}, count={}, prev={}",
261                      clp.getPolicyName(), clp.getPolicyVersion(),
262                      clp.getClosedLoopControlName(), controlLoopParams.size(), (prev != null));
263         dumpTables();
264         return prev;
265     }
266
267     /**
268      * Return a collection of all ControlLoopParams instances.
269      *
270      * @return all ControlLoopParams instances
271      */
272     public synchronized Collection<ControlLoopParams> getAllControlLoopParams() {
273         return new ArrayList<>(controlLoopParams.values());
274     }
275
276     /**
277      * Return a collection of all EventManager  instances.
278      *
279      * @return all EventManager instances
280      *
281      */
282     public synchronized Collection<ControlLoopEventManager> getAllEventManagers() {
283         return new ArrayList<>(eventManagers.values());
284     }
285
286     /**
287      * Return a collection of all onsetToEventManager  instances.
288      *
289      * @return all onsetToEventManager instances
290      *
291      */
292     public synchronized Collection<ControlLoopEventManager> getAllOnsetToEventManager() {
293         return new ArrayList<>(onsetToEventManager.values());
294     }
295
296     /**
297      *  Reset the controller.
298      *
299      */
300     public synchronized void reset() {
301         toscaPolicies.clear();
302         controlLoopParams.clear();
303         eventManagers.clear();
304         onsetToEventManager.clear();
305     }
306
307     @Override
308     public boolean stop() {
309         super.stop();
310
311         // stop all 'TopicData' instances
312         for (TopicData topicData : topicDataTable.values()) {
313             topicData.stop();
314         }
315         return true;
316     }
317
318     /**
319      * Remove a ControlLoopParams instance associated with the specified
320      * 'closedLoopControlName'.
321      *
322      * @param closedLoopControlName the closedLoopControlName identifying the
323      *     ControlLoopParams instance
324      * @return the 'ControlLoopParams' instance, 'null' if not found
325      */
326     public synchronized ControlLoopParams removeControlLoopParams(String closedLoopControlName) {
327         return controlLoopParams.remove(closedLoopControlName);
328     }
329
330     /**
331      * Dump out the ToscaPolicy and ControlLoopParams tables in
332      * human-readable form.
333      */
334     private void dumpTables() {
335         ByteArrayOutputStream bos = new ByteArrayOutputStream();
336         PrintStream out = new PrintStream(bos, true);
337
338         // name(25) version(10) closedLoopControlName(...)
339
340         String format = "%-25s %-10s %s\n";
341         out.println("ToscaPolicy Table");
342         out.format(format, "Name", "Version", "");
343         out.format(format, "----", "-------", "");
344
345         for (Map<String, ToscaPolicy> level2 : toscaPolicies.values()) {
346             for (ToscaPolicy tp : level2.values()) {
347                 out.format(format, tp.getName(), tp.getVersion(), "");
348             }
349         }
350
351         out.println("\nControlLoopParams Table");
352         out.format(format, "Name", "Version", "ClosedLoopControlName");
353         out.format(format, "----", "-------", "---------------------");
354         for (ControlLoopParams cp : controlLoopParams.values()) {
355             out.format(format, cp.getPolicyName(), cp.getPolicyVersion(),
356                        cp.getClosedLoopControlName());
357         }
358
359         if (logger.isDebugEnabled()) {
360             logger.debug(new String(bos.toByteArray(), StandardCharsets.UTF_8));
361         }
362     }
363
364     /**
365      * Find or create a 'TopicData' instance associated with the specified
366      * topic name.
367      *
368      * @param name the topic name
369      * @return the new or existing 'TopicData' instance associated with 'name'
370      */
371     private TopicData getTopicData(String name) {
372         return topicDataTable.computeIfAbsent(name, key -> new TopicData(name));
373     }
374
375     /* ============================================================ */
376
377     /**
378      * Process an incoming 'ControlLoopEvent'.
379      *
380      * @param event the incoming 'ControlLoopEvent'
381      */
382     private void processEvent(ControlLoopEvent event) {
383         String clName = event.getClosedLoopControlName();
384         ControlLoopParams params = controlLoopParams.get(clName);
385         if (params == null) {
386             logger.debug("No ControlLoopParams for event: {}", event);
387             return;
388         }
389
390         UUID requestId = event.getRequestId();
391         if (event instanceof CanonicalOnset) {
392             CanonicalOnset coEvent = (CanonicalOnset) event;
393
394             if (requestId == null) {
395                 // the requestId should not be 'null'
396                 handleNullRequestId(coEvent, params);
397                 return;
398             }
399
400             ControlLoopEventManager manager = onsetToEventManager.computeIfAbsent(coEvent, key -> {
401                 // a ControlLoopEventManager does not yet exist for this
402                 // 'event' -- create one, with the initial event
403                 try {
404                     ControlLoopEventManager mgr = new ControlLoopEventManager(params, coEvent);
405                     eventManagers.put(requestId, mgr);
406                     return mgr;
407                 } catch (ControlLoopException e) {
408                     logger.error("Exception creating ControlLoopEventManager", e);
409                     return null;
410                 }
411             });
412
413             if (manager != null && !manager.getSerialWorkQueue().isRunning()) {
414                 // new manager - start it by processing the initial event
415                 manager.getSerialWorkQueue().start();
416                 return;
417             }
418         }
419
420         if (event instanceof VirtualControlLoopEvent) {
421             ControlLoopEventManager manager = eventManagers.get(requestId);
422             if (manager != null) {
423                 manager.getSerialWorkQueue()
424                                 .queueAndRun(() -> manager.subsequentEvent((VirtualControlLoopEvent) event));
425                 return;
426             }
427         }
428
429         // this block of code originally appeared in the 'EVENT.CLEANUP'
430         // Drools rule
431         String ruleName = "EVENT.CLEANUP";
432
433         logger.info("{}: {}", clName, ruleName);
434         logger.debug("{}: {}: orphan event={}", clName, ruleName, event);
435     }
436
437     /**
438      * Generate and send a notification message in response to a 'CanonicalOnset'
439      * with a null 'requestId'.
440      *
441      * @param event the CanonicalOnset event
442      * @param params the associated ControlLoopParams
443      */
444     private void handleNullRequestId(CanonicalOnset event,
445                                      ControlLoopParams params) {
446         // this block of code originally appeared in the 'EVENT' Drools rule
447         String ruleName = "EVENT";
448         String clName = event.getClosedLoopControlName();
449
450         VirtualControlLoopNotification notification =
451             new VirtualControlLoopNotification(event);
452         notification.setNotification(ControlLoopNotificationType.REJECTED);
453         notification.setFrom("policy");
454         notification.setMessage("Missing requestId");
455         notification.setPolicyName(params.getPolicyName() + "." + ruleName);
456         notification.setPolicyScope(params.getPolicyScope());
457         notification.setPolicyVersion(params.getPolicyVersion());
458
459         //
460         // Generate notification
461         //
462         try {
463             PolicyEngineConstants.getManager().deliver(POLICY_CL_MGT, notification);
464
465         } catch (RuntimeException e) {
466             logger.warn("{}: {}.{}: event={} exception generating notification",
467                         clName, params.getPolicyName(), ruleName,
468                         event, e);
469         }
470     }
471
472     /* ============================================================ */
473
474     /**
475      * This nested class corresponds to a single topic name. At present, the
476      * only topics that are directly handled by this class are
477      * 'ControlLoopEvent', and subclasses (hence, the call to 'processEvent').
478      * If other event types later need to be directly handled, this may need to
479      * become an abstract class, with subclasses for the various event types.
480      */
481     private class TopicData implements TopicListener {
482         // topic name
483         private String name;
484
485         // set of 'TopicSource' instances associated with this topic
486         // (probably only one, but the underlying APIs support a list)
487         private List<TopicSource> topicSources = null;
488
489         /**
490          * Constructor -- initialize the 'TopicData' instance.
491          *
492          * @param name the topic name
493          */
494         private TopicData(String name) {
495             this.name = name;
496         }
497
498         /**
499          * Register all of the 'TopicSource' instances associated with this
500          * topic, and start the listeners.
501          */
502         private void start() {
503             if (topicSources == null) {
504                 // locate topic sources
505                 ArrayList<String> topics = new ArrayList<>();
506                 topics.add(name);
507                 topicSources = TopicEndpointManager.getManager().getTopicSources(topics);
508             }
509
510             for (TopicSource consumer : topicSources) {
511                 consumer.register(this);
512                 consumer.start();
513             }
514         }
515
516         /**
517          * Unregister all of the 'TopicSource' instances associated with this
518          * topic, and stop the listeners.
519          */
520         private void stop() {
521             if (topicSources != null) {
522                 for (TopicSource consumer : topicSources) {
523                     consumer.unregister(this);
524                     consumer.stop();
525                 }
526             }
527         }
528
529         /*===========================*/
530         /* 'TopicListener' interface */
531         /*===========================*/
532
533         @Override
534         public void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event) {
535             logger.debug("TopicData.onTopicEvent: {}", event);
536             Object decodedObject =
537                 EventProtocolCoderConstants.getManager().decode(groupId, artifactId, topic, event);
538             if (decodedObject != null) {
539                 logger.debug("Decoded to object of {}", decodedObject.getClass());
540                 if (decodedObject instanceof ControlLoopEvent) {
541                     PolicyEngineConstants.getManager().getExecutorService().execute(() ->
542                         processEvent((ControlLoopEvent) decodedObject));
543                 }
544             }
545         }
546     }
547
548     /* ============================================================ */
549
550     /**
551      * This is a 'ControlLoopEventManager2' variant designed to run under
552      * 'TdjamController'.
553      */
554     private class ControlLoopEventManager extends ControlLoopEventManager2 {
555         private static final long serialVersionUID = 1L;
556
557         // used to serialize method calls from multiple threads, which avoids the
558         // need for additional synchronization
559         private final SerialWorkQueue serialWorkQueue;
560
561         private final ControlLoopParams params;
562
563         // onset event
564         private final CanonicalOnset event;
565
566         /**
567          * Constructor - initialize a ControlLoopEventManager.
568          *
569          * @param params the 'ControlLoopParam's instance associated with the
570          *     'closedLoopControlName'
571          * @param event the initial ControlLoopEvent
572          */
573         private ControlLoopEventManager(ControlLoopParams params, CanonicalOnset event)
574             throws ControlLoopException {
575
576             super(params, event);
577             this.params = params;
578             this.event = event;
579             this.serialWorkQueue = new SerialWorkQueue(this::initialEvent);
580         }
581
582         /**
583          * Return the SerialWorkQueue.
584          *
585          * @return the SerialWorkQueue
586          */
587         private SerialWorkQueue getSerialWorkQueue() {
588             return serialWorkQueue;
589         }
590
591         /**
592          * This is a notification from the base class that a state transition
593          * has occurred.
594          */
595         @Override
596         protected void notifyUpdate() {
597             update();
598         }
599
600         /**
601          * Process the initial event from DCAE that caused the
602          * 'ControlLoopEventManager' to be created.
603          */
604         private void initialEvent() {
605             // this block of code originally appeared in the 'EVENT' Drools rule
606             String ruleName = "EVENT";
607             UUID requestId = event.getRequestId();
608             String clName = event.getClosedLoopControlName();
609
610             VirtualControlLoopNotification notification;
611
612             try {
613                 //
614                 // Check the event, because we need it to not be null when
615                 // we create the ControlLoopEventManager. The ControlLoopEventManager
616                 // will do extra syntax checking as well as check if the closed loop is disabled.
617                 //
618                 start();
619                 notification = makeNotification();
620                 notification.setNotification(ControlLoopNotificationType.ACTIVE);
621                 notification.setPolicyName(params.getPolicyName() + "." + ruleName);
622             } catch (Exception e) {
623                 logger.warn("{}: {}.{}", clName, params.getPolicyName(), ruleName, e);
624                 eventManagers.remove(requestId, this);
625                 onsetToEventManager.remove(event, this);
626                 notification = new VirtualControlLoopNotification(event);
627                 notification.setNotification(ControlLoopNotificationType.REJECTED);
628                 notification.setMessage("Exception occurred: " + e.getMessage());
629                 notification.setPolicyName(params.getPolicyName() + "." + ruleName);
630                 notification.setPolicyScope(params.getPolicyScope());
631                 notification.setPolicyVersion(params.getPolicyVersion());
632             }
633             //
634             // Generate notification
635             //
636             try {
637                 PolicyEngineConstants.getManager().deliver(POLICY_CL_MGT, notification);
638
639             } catch (RuntimeException e) {
640                 logger.warn("{}: {}.{}: event={} exception generating notification",
641                             clName, params.getPolicyName(), ruleName,
642                             event, e);
643             }
644         }
645
646         /**
647          * Process a subsequent event from DCAE.
648          *
649          * @param event the VirtualControlLoopEvent event
650          */
651         private void subsequentEvent(VirtualControlLoopEvent event) {
652             // this block of code originally appeared in the
653             // 'EVENT.MANAGER>NEW.EVENT' Drools rule
654             String ruleName = "EVENT.MANAGER.NEW.EVENT";
655
656             //
657             // Check what kind of event this is
658             //
659             switch (onNewEvent(event)) {
660                 case SYNTAX_ERROR:
661                     //
662                     // Ignore any bad syntax events
663                     //
664                     logger.warn("{}: {}.{}: syntax error",
665                                 getClosedLoopControlName(), getPolicyName(), ruleName);
666                     break;
667
668                 case FIRST_ABATEMENT:
669                 case SUBSEQUENT_ABATEMENT:
670                     //
671                     // TODO: handle the abatement.  Currently, it's just discarded.
672                     //
673                     break;
674
675                 case FIRST_ONSET:
676                 case SUBSEQUENT_ONSET:
677                 default:
678                     //
679                     // We don't care about subsequent onsets
680                     //
681                     logger.warn("{}: {}.{}: subsequent onset",
682                                 getClosedLoopControlName(), getPolicyName(), ruleName);
683                     break;
684             }
685         }
686
687         /**
688          * Called when a state transition occurs.
689          */
690         private void update() {
691             // handle synchronization by running it under the SerialWorkQueue
692             getSerialWorkQueue().queueAndRun(() -> {
693                 if (isActive()) {
694                     updateActive();
695                 } else {
696                     updateInactive();
697                 }
698             });
699         }
700
701         /**
702          * Called when a state transition occurs, and we are in the active state.
703          */
704         private void updateActive() {
705             if (!isUpdated()) {
706                 // no notification needed
707                 return;
708             }
709
710             // this block of code originally appeared in the
711             // 'EVENT.MANAGER.PROCESSING' Drools rule
712             String ruleName = "EVENT.MANAGER.PROCESSING";
713             VirtualControlLoopNotification notification =
714                 getNotification();
715
716             logger.info("{}: {}.{}: manager={}",
717                         getClosedLoopControlName(), getPolicyName(), ruleName,
718                         this);
719             //
720             // Generate notification
721             //
722             try {
723                 notification.setPolicyName(getPolicyName() + "." + ruleName);
724                 PolicyEngineConstants.getManager().deliver(POLICY_CL_MGT, notification);
725
726             } catch (RuntimeException e) {
727                 logger.warn("{}: {}.{}: manager={} exception generating notification",
728                             getClosedLoopControlName(), getPolicyName(), ruleName,
729                             this, e);
730             }
731             //
732             // Generate Response notification
733             //
734             try {
735                 ControlLoopResponse clResponse = getControlLoopResponse();
736                 if (clResponse != null) {
737                     PolicyEngineConstants.getManager().deliver("DCAE_CL_RSP", clResponse);
738                 }
739
740             } catch (RuntimeException e) {
741                 logger.warn("{}: {}.{}: manager={} exception generating Response notification",
742                             getClosedLoopControlName(), getPolicyName(), ruleName,
743                             this, e);
744             }
745             //
746             // Discard this message and wait for the next response.
747             //
748             nextStep();
749             update();
750         }
751
752         /**
753          * Called when a state transition has occurred, and we are not in the
754          * active state.
755          */
756         private void updateInactive() {
757             // this block of code originally appeared in the 'EVENT.MANAGER.FINAL'
758             // Drools rule
759             String ruleName = "EVENT.MANAGER.FINAL";
760             VirtualControlLoopNotification notification =
761                 getNotification();
762
763             logger.info("{}: {}.{}: manager={}",
764                         getClosedLoopControlName(), getPolicyName(), ruleName,
765                         this);
766             //
767             // Generate notification
768             //
769             try {
770                 notification.setPolicyName(getPolicyName() + "." + ruleName);
771                 PolicyEngineConstants.getManager().deliver(POLICY_CL_MGT, notification);
772             } catch (RuntimeException e) {
773                 logger.warn("{}: {}.{}: manager={} exception generating notification",
774                             getClosedLoopControlName(), getPolicyName(), ruleName,
775                             this, e);
776             }
777             //
778             // Destroy the manager
779             //
780             destroy();
781
782             // Remove the entry from the table
783             eventManagers.remove(getRequestId(), this);
784             onsetToEventManager.remove(event, this);
785         }
786     }
787
788     /* ============================================================ */
789
790     /**
791      * An instance of this class is called by 'IndexedPolicyControllerFactory'.
792      * It does the build operation when the value of the 'controller.type'
793      * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
794      */
795     public static class PolicyBuilder implements PolicyControllerFeatureApi {
796         @Override
797         public int getSequenceNumber() {
798             return 1;
799         }
800
801         @Override
802         public PolicyController beforeInstance(String name, Properties properties) {
803             if (TDJAM_CONTROLLER_BUILDER_TAG.equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
804                 return new TdjamController(name, properties);
805             }
806             return null;
807         }
808     }
809
810     /* ============================================================ */
811
812     /**
813      * An instance of this class is called by 'IndexedDroolsControllerFactory'.
814      * It does the build operation when the value of the 'controller.type'
815      * property matches the value of TDJAM_CONTROLLER_BUILDER_TAG.
816      */
817     public static class DroolsBuilder implements DroolsControllerFeatureApi {
818         @Override
819         public int getSequenceNumber() {
820             return 1;
821         }
822
823         @Override
824         public DroolsController beforeInstance(Properties properties,
825                                       String groupId, String artifactId, String version,
826                                       List<TopicCoderFilterConfiguration> decoderConfigurations,
827                                       List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
828
829             if (TDJAM_CONTROLLER_BUILDER_TAG.equals(properties.getProperty(PROPERTY_CONTROLLER_TYPE))) {
830                 return NonDroolsPolicyController.getBuildInProgress();
831             }
832             return null;
833         }
834     }
835 }