97eb6a04d39dba4d4f2b8c7133d7608a03d1d7c7
[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.extension.system;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Properties;
30 import org.apache.commons.collections4.queue.CircularFifoQueue;
31 import org.checkerframework.checker.nullness.qual.NonNull;
32 import org.onap.policy.common.endpoints.event.comm.Topic;
33 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
34 import org.onap.policy.common.endpoints.event.comm.TopicSink;
35 import org.onap.policy.common.endpoints.event.comm.TopicSource;
36 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
37 import org.onap.policy.common.utils.services.OrderedServiceImpl;
38 import org.onap.policy.drools.controller.DroolsController;
39 import org.onap.policy.drools.core.PolicyContainer;
40 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
41 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
42 import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
43 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
44 import org.onap.policy.drools.protocol.coders.EventProtocolParams;
45 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
46 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
47 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
48 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
49 import org.onap.policy.drools.system.internal.AggregatedPolicyController;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * This class combines the 'PolicyController' and 'DroolsController'
55  * interfaces, and provides a controller that does not have Drools running
56  * underneath. It also contains some code copied from 'MavenDroolsController'
57  * and 'NullDroolsController'. The goal is to have it look like other
58  * controllers, use the same style property file, and provide access to
59  * UEB/DMAAP message streams associated with the controller.
60  */
61 public class NonDroolsPolicyController extends AggregatedPolicyController implements DroolsController {
62     /**
63      * Logger.
64      */
65     private static final Logger logger = LoggerFactory.getLogger(NonDroolsPolicyController.class);
66
67     /**
68      * The PolicyController and DroolsController factories assume that the
69      * controllers are separate objects, but in this case, the same object
70      * is used for both. We want the DroolsController 'build' method to
71      * return the same object; however, at the point the DroolsController
72      * build is taking place, the PolicyController hasn't yet been placed
73      * in any tables. The following variable is used to pass this information
74      * from one stack frame to another within the same thread.
75      */
76     private static ThreadLocal<NonDroolsPolicyController> buildInProgress = new ThreadLocal<>();
77
78     /**
79      * alive status of this drools controller,
80      * reflects invocation of start()/stop() only.
81      */
82     protected volatile boolean alive = false;
83
84     /**
85      * locked status of this drools controller,
86      * reflects if i/o drools related operations are permitted,
87      * more specifically: offer() and deliver().
88      * It does not affect the ability to start and stop
89      * underlying drools infrastructure
90      */
91     protected volatile boolean locked = false;
92
93     /**
94      * list of topics, each with associated decoder classes, each
95      * with a list of associated filters.
96      */
97     protected List<TopicCoderFilterConfiguration> decoderConfigurations;
98
99     /**
100      * list of topics, each with associated encoder classes, each
101      * with a list of associated filters.
102      */
103     protected List<TopicCoderFilterConfiguration> encoderConfigurations;
104
105     /**
106      * recent sink events processed.
107      */
108     protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
109
110     // this is used to avoid infinite recursion in a shutdown or halt operation
111     private boolean shutdownInProgress = false;
112
113     private static Properties convert(String name, Properties properties) {
114
115         Properties newProperties = new Properties();
116         for (String pname : properties.stringPropertyNames()) {
117             newProperties.setProperty(pname, properties.getProperty(pname));
118         }
119
120         newProperties.setProperty("rules.groupId", "NonDroolsPolicyController");
121         newProperties.setProperty("rules.artifactId", name);
122         newProperties.setProperty("rules.version", "1.0");
123         return newProperties;
124     }
125
126     /**
127      * constructor -- pass parameters to superclass.
128      * @param name controller name
129      * @param properties contents of controller properties file
130      */
131     public NonDroolsPolicyController(String name, Properties properties) {
132         super(name, convert(name, properties));
133     }
134
135     /**
136      * This is used to pass the 'NonDroolsPolicyController' object to the
137      * 'DroolsPolicyBuilder' object, as the same object is used for both
138      * 'PolicyController' and 'DroolsController'.
139      *
140      * @return the NonDroolsPolicyController object ('null' if not available)
141      */
142     public static NonDroolsPolicyController getBuildInProgress() {
143         return buildInProgress.get();
144     }
145
146     @Override
147     protected void initDrools(Properties properties) {
148         try {
149             // Register with drools factory
150             buildInProgress.set(this);
151             this.droolsController.set(getDroolsFactory().build(properties, sources, sinks));
152             buildInProgress.remove();
153         } catch (Exception | LinkageError e) {
154             logger.error("{}: cannot init-drools", this);
155             throw new IllegalArgumentException(e);
156         }
157
158         decoderConfigurations = codersAndFilters(properties, sources);
159         encoderConfigurations = codersAndFilters(properties, sinks);
160
161         // add to 'EventProtocolCoderConstants.getManager()' table
162         for (TopicCoderFilterConfiguration tcfc : decoderConfigurations) {
163             for (PotentialCoderFilter pcf : tcfc.getCoderFilters()) {
164                 getCoderManager().addDecoder(
165                     EventProtocolParams.builder()
166                     .groupId(getGroupId())
167                     .artifactId(getArtifactId())
168                     .topic(tcfc.getTopic())
169                     .eventClass(pcf.getCodedClass())
170                     .protocolFilter(pcf.getFilter())
171                     .customGsonCoder(tcfc.getCustomGsonCoder())
172                     .modelClassLoaderHash(NonDroolsPolicyController.class.getClassLoader().hashCode()));
173             }
174         }
175         for (TopicCoderFilterConfiguration tcfc : encoderConfigurations) {
176             for (PotentialCoderFilter pcf : tcfc.getCoderFilters()) {
177                 getCoderManager().addEncoder(
178                     EventProtocolParams.builder()
179                     .groupId(getGroupId())
180                     .artifactId(getArtifactId())
181                     .topic(tcfc.getTopic())
182                     .eventClass(pcf.getCodedClass())
183                     .protocolFilter(pcf.getFilter())
184                     .customGsonCoder(tcfc.getCustomGsonCoder())
185                     .modelClassLoaderHash(NonDroolsPolicyController.class.getClassLoader().hashCode()));
186             }
187         }
188     }
189
190     /*==============================*/
191     /* 'DroolsController' interface */
192     /*==============================*/
193
194     // methods copied from 'MavenDroolsController' and 'NullDroolsController'
195
196     @Override
197     public boolean start() {
198
199         logger.info("START: {}", this);
200
201         synchronized (this) {
202             this.alive = true;
203         }
204
205         return true;
206     }
207
208     @Override
209     public boolean stop() {
210
211         logger.info("STOP: {}", this);
212
213         synchronized (this) {
214             this.alive = false;
215         }
216
217         return true;
218     }
219
220     @Override
221     public void shutdown() {
222         if (shutdownInProgress) {
223             // avoid infinite recursion
224             return;
225         }
226         logger.info("{}: SHUTDOWN", this);
227
228         try {
229             this.stop();
230             this.removeCoders();
231             shutdownInProgress = true;
232
233             // the following method calls 'this.shutdown' recursively
234             getDroolsFactory().shutdown(this);
235         } catch (Exception e) {
236             logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
237         } finally {
238             shutdownInProgress = false;
239         }
240     }
241
242     @Override
243     public void halt() {
244         if (shutdownInProgress) {
245             // avoid infinite recursion
246             return;
247         }
248         logger.info("{}: HALT", this);
249
250         try {
251             this.stop();
252             this.removeCoders();
253             shutdownInProgress = true;
254
255             // the following method calls 'this.halt' recursively
256             getDroolsFactory().destroy(this);
257         } catch (Exception e) {
258             logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
259         } finally {
260             shutdownInProgress = false;
261         }
262     }
263
264     @Override
265     public boolean isAlive() {
266         return this.alive;
267     }
268
269     @Override
270     public boolean lock() {
271         logger.info("LOCK: {}",  this);
272
273         this.locked = true;
274         return true;
275     }
276
277     @Override
278     public boolean unlock() {
279         logger.info("UNLOCK: {}",  this);
280
281         this.locked = false;
282         return true;
283     }
284
285     @Override
286     public boolean isLocked() {
287         return this.locked;
288     }
289
290     @Override
291     public String getGroupId() {
292         return "NonDroolsPolicyController";
293     }
294
295     @Override
296     public String getArtifactId() {
297         return getName();
298     }
299
300     @Override
301     public String getVersion() {
302         return "1.0";
303     }
304
305     @Override
306     public List<String> getSessionNames() {
307         return new ArrayList<>();
308     }
309
310     @Override
311     public List<String> getCanonicalSessionNames() {
312         return new ArrayList<>();
313     }
314
315     @Override
316     public List<String> getBaseDomainNames() {
317         return Collections.emptyList();
318     }
319
320     @Override
321     public boolean offer(String topic, String event) {
322         return false;
323     }
324
325     @Override
326     public <T> boolean offer(T event) {
327         return false;
328     }
329
330     @Override
331     public boolean deliver(TopicSink sink, Object event) {
332
333         // this one is from 'MavenDroolsController'
334
335         logger.info("{} DELIVER: {} FROM {} TO {}", this, event, this, sink);
336
337         for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
338             try {
339                 if (feature.beforeDeliver(this, sink, event)) {
340                     return true;
341                 }
342             } catch (Exception e) {
343                 logger.error("{}: feature {} before-deliver failure because of {}", this, feature.getClass().getName(),
344                         e.getMessage(), e);
345             }
346         }
347
348         if (sink == null) {
349             throw new IllegalArgumentException(this +  " invalid sink");
350         }
351
352         if (event == null) {
353             throw new IllegalArgumentException(this +  " invalid event");
354         }
355
356         if (this.locked) {
357             throw new IllegalStateException(this +  " is locked");
358         }
359
360         if (!this.alive) {
361             throw new IllegalStateException(this +  " is stopped");
362         }
363
364         String json =
365                 getCoderManager().encode(sink.getTopic(), event, this);
366
367         synchronized (this.recentSinkEvents) {
368             this.recentSinkEvents.add(json);
369         }
370
371         boolean success = sink.send(json);
372
373         for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
374             try {
375                 if (feature.afterDeliver(this, sink, event, json, success)) {
376                     return true;
377                 }
378             } catch (Exception e) {
379                 logger.error("{}: feature {} after-deliver failure because of {}", this, feature.getClass().getName(),
380                         e.getMessage(), e);
381             }
382         }
383
384         return success;
385
386     }
387
388     @Override
389     public Object[] getRecentSourceEvents() {
390         return new String[0];
391     }
392
393     @Override
394     public PolicyContainer getContainer() {
395         return null;
396     }
397
398     @Override
399     public String[] getRecentSinkEvents() {
400         synchronized (this.recentSinkEvents) {
401             String[] events = new String[recentSinkEvents.size()];
402             return recentSinkEvents.toArray(events);
403         }
404     }
405
406     @Override
407     public boolean ownsCoder(Class<?> coderClass, int modelHash) {
408         //throw new IllegalStateException(makeInvokeMsg());
409         return true;
410     }
411
412     @Override
413     public Class<?> fetchModelClass(String className) {
414         try {
415             return Class.forName(className);
416         } catch (ClassNotFoundException e) {
417             throw new IllegalArgumentException(makeInvokeMsg());
418         }
419     }
420
421     @Override
422     public boolean isBrained() {
423         return true;
424     }
425
426     @Override
427     public String toString() {
428         StringBuilder builder = new StringBuilder();
429         builder.append("NonDroolsPolicyController []");
430         return builder.toString();
431     }
432
433     @Override
434     public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
435             List<TopicCoderFilterConfiguration> decoderConfigurations,
436             List<TopicCoderFilterConfiguration> encoderConfigurations)
437                     throws LinkageError {
438         throw new IllegalStateException(makeInvokeMsg());
439     }
440
441     @Override
442     public Map<String, Integer> factClassNames(String sessionName) {
443         return new HashMap<>();
444     }
445
446     @Override
447     public long factCount(String sessionName) {
448         return 0;
449     }
450
451     @Override
452     public List<Object> facts(String sessionName, String className, boolean delete) {
453         return new ArrayList<>();
454     }
455
456     @Override
457     public <T> List<T> facts(@NonNull String sessionName, @NonNull Class<T> clazz) {
458         return new ArrayList<>();
459     }
460
461     @Override
462     public List<Object> factQuery(String sessionName, String queryName,
463             String queriedEntity,
464             boolean delete, Object... queryParams) {
465         return new ArrayList<>();
466     }
467
468     @Override
469     public <T> boolean delete(@NonNull String sessionName, @NonNull T fact) {
470         return false;
471     }
472
473     @Override
474     public <T> boolean delete(@NonNull T fact) {
475         return false;
476     }
477
478     @Override
479     public <T> boolean delete(@NonNull String sessionName, @NonNull Class<T> fact) {
480         return false;
481     }
482
483     @Override
484     public <T> boolean delete(@NonNull Class<T> fact) {
485         return false;
486     }
487
488     private String makeInvokeMsg() {
489         return this.getClass().getName() + " invoked";
490     }
491
492     /**
493      * remove decoders.
494      */
495     protected void removeDecoders() {
496         logger.info("REMOVE-DECODERS: {}", this);
497
498         if (this.decoderConfigurations == null) {
499             return;
500         }
501
502
503         for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
504             String topic = coderConfig.getTopic();
505             getCoderManager().removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
506         }
507     }
508
509     /**
510      * remove decoders.
511      */
512     protected void removeEncoders() {
513
514         logger.info("REMOVE-ENCODERS: {}", this);
515
516         if (this.encoderConfigurations == null) {
517             return;
518         }
519
520         for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
521             String topic = coderConfig.getTopic();
522             getCoderManager().removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
523         }
524     }
525
526     /**
527      * removes this drools controllers and encoders and decoders from operation.
528      */
529     protected void removeCoders() {
530         logger.info("{}: REMOVE-CODERS", this);
531
532         try {
533             this.removeDecoders();
534         } catch (IllegalArgumentException e) {
535             logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
536         }
537
538         try {
539             this.removeEncoders();
540         } catch (IllegalArgumentException e) {
541             logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
542         }
543     }
544
545     protected List<TopicCoderFilterConfiguration> codersAndFilters(Properties properties,
546             List<? extends Topic> topicEntities) {
547
548         List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = new ArrayList<>();
549
550         if (topicEntities == null || topicEntities.isEmpty()) {
551             return topics2DecodedClasses2Filters;
552         }
553
554         for (Topic topic : topicEntities) {
555
556             // 1. first the topic
557
558             String firstTopic = topic.getTopic();
559
560             String propertyTopicEntityPrefix = getPropertyTopicPrefix(topic) + firstTopic;
561
562             // 2. check if there is a custom decoder for this topic that the user prefers to use
563             // instead of the ones provided in the platform
564
565             CustomGsonCoder customGsonCoder = getCustomCoder(properties, propertyTopicEntityPrefix);
566
567             // 3. second the list of classes associated with each topic
568
569             String eventClasses = properties
570                     .getProperty(propertyTopicEntityPrefix + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX);
571
572             if (eventClasses == null || eventClasses.isEmpty()) {
573                 logger.warn("There are no event classes for topic {}", firstTopic);
574                 continue;
575             }
576
577             List<PotentialCoderFilter> classes2Filters =
578                             getFilterExpressions(properties, propertyTopicEntityPrefix, eventClasses);
579
580             TopicCoderFilterConfiguration topic2Classes2Filters =
581                     new TopicCoderFilterConfiguration(firstTopic, classes2Filters, customGsonCoder);
582             topics2DecodedClasses2Filters.add(topic2Classes2Filters);
583         }
584
585         return topics2DecodedClasses2Filters;
586     }
587
588     private String getPropertyTopicPrefix(Topic topic) {
589         boolean isSource = topic instanceof TopicSource;
590         CommInfrastructure commInfra = topic.getTopicCommInfrastructure();
591         if (commInfra == CommInfrastructure.UEB) {
592             if (isSource) {
593                 return PolicyEndPointProperties.PROPERTY_UEB_SOURCE_TOPICS + ".";
594             } else {
595                 return PolicyEndPointProperties.PROPERTY_UEB_SINK_TOPICS + ".";
596             }
597         } else if (commInfra == CommInfrastructure.DMAAP) {
598             if (isSource) {
599                 return PolicyEndPointProperties.PROPERTY_DMAAP_SOURCE_TOPICS + ".";
600             } else {
601                 return PolicyEndPointProperties.PROPERTY_DMAAP_SINK_TOPICS + ".";
602             }
603         } else if (commInfra == CommInfrastructure.NOOP) {
604             if (isSource) {
605                 return PolicyEndPointProperties.PROPERTY_NOOP_SOURCE_TOPICS + ".";
606             } else {
607                 return PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS + ".";
608             }
609         } else {
610             throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra);
611         }
612     }
613
614     private CustomGsonCoder getCustomCoder(Properties properties, String propertyPrefix) {
615         String customGson = properties.getProperty(propertyPrefix
616                 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX);
617
618         CustomGsonCoder customGsonCoder = null;
619         if (customGson != null && !customGson.isEmpty()) {
620             try {
621                 customGsonCoder = new CustomGsonCoder(customGson);
622             } catch (IllegalArgumentException e) {
623                 logger.warn("{}: cannot create custom-gson-coder {} because of {}", this, customGson,
624                         e.getMessage(), e);
625             }
626         }
627         return customGsonCoder;
628     }
629
630     private List<PotentialCoderFilter> getFilterExpressions(Properties properties, String propertyPrefix,
631                     String eventClasses) {
632
633         List<PotentialCoderFilter> classes2Filters = new ArrayList<>();
634
635         List<String> topicClasses = new ArrayList<>(Arrays.asList(eventClasses.split("\\s*,\\s*")));
636
637         for (String theClass : topicClasses) {
638
639             // 4. for each coder class, get the filter expression
640
641             String filter = properties
642                     .getProperty(propertyPrefix
643                             + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX
644                             + "." + theClass + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX);
645
646             JsonProtocolFilter protocolFilter = new JsonProtocolFilter(filter);
647             PotentialCoderFilter class2Filters = new PotentialCoderFilter(theClass, protocolFilter);
648             classes2Filters.add(class2Filters);
649         }
650
651         return classes2Filters;
652     }
653
654     // these may be overridden by junit tests
655
656     protected EventProtocolCoder getCoderManager() {
657         return EventProtocolCoderConstants.getManager();
658     }
659
660     protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
661         return DroolsControllerFeatureApiConstants.getProviders();
662     }
663 }