d21966803c8bdcc755c43de9463e83dadabf98c0
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-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.drools.controller;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Properties;
29 import org.onap.policy.common.endpoints.event.comm.Topic;
30 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
31 import org.onap.policy.common.endpoints.event.comm.TopicSink;
32 import org.onap.policy.common.endpoints.event.comm.TopicSource;
33 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
34 import org.onap.policy.common.utils.services.FeatureApiUtils;
35 import org.onap.policy.drools.controller.internal.MavenDroolsController;
36 import org.onap.policy.drools.controller.internal.NullDroolsController;
37 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
38 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
39 import org.onap.policy.drools.properties.DroolsPropertyConstants;
40 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
41 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
42 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
43 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Factory of Drools Controllers indexed by the Maven coordinates.
49  */
50 class IndexedDroolsControllerFactory implements DroolsControllerFactory {
51
52     /**
53      * logger.
54      */
55     private static final Logger logger = LoggerFactory.getLogger(IndexedDroolsControllerFactory.class);
56
57     /**
58      * Policy Controller Name Index.
59      */
60     protected Map<String, DroolsController> droolsControllers = new HashMap<>();
61
62     /**
63      * Null Drools Controller.
64      */
65     protected NullDroolsController nullDroolsController = new NullDroolsController();
66
67     /**
68      * Constructs the object.
69      */
70     public IndexedDroolsControllerFactory() {
71
72         /* Add a NULL controller which will always be present in the hash */
73
74         DroolsController controller = new NullDroolsController();
75         String controllerId = controller.getGroupId() + ":" + controller.getArtifactId();
76
77         synchronized (this) {
78             droolsControllers.put(controllerId, controller);
79         }
80     }
81
82     @Override
83     public DroolsController build(Properties properties, List<? extends TopicSource> eventSources,
84             List<? extends TopicSink> eventSinks) throws LinkageError {
85
86         String groupId = properties.getProperty(DroolsPropertyConstants.RULES_GROUPID);
87         if (groupId == null || groupId.isEmpty()) {
88             groupId = DroolsControllerConstants.NO_GROUP_ID;
89         }
90
91         String artifactId = properties.getProperty(DroolsPropertyConstants.RULES_ARTIFACTID);
92         if (artifactId == null || artifactId.isEmpty()) {
93             artifactId = DroolsControllerConstants.NO_ARTIFACT_ID;
94         }
95
96         String version = properties.getProperty(DroolsPropertyConstants.RULES_VERSION);
97         if (version == null || version.isEmpty()) {
98             version = DroolsControllerConstants.NO_VERSION;
99         }
100
101         List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = codersAndFilters(properties, eventSources);
102         List<TopicCoderFilterConfiguration> topics2EncodedClasses2Filters = codersAndFilters(properties, eventSinks);
103
104         return this.build(properties, groupId, artifactId, version,
105                 topics2DecodedClasses2Filters, topics2EncodedClasses2Filters);
106     }
107
108     @Override
109     public DroolsController build(Properties properties, String newGroupId, String newArtifactId, String newVersion,
110             List<TopicCoderFilterConfiguration> decoderConfigurations,
111             List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
112
113         if (newGroupId == null || newGroupId.isEmpty()) {
114             throw new IllegalArgumentException("Missing maven group-id coordinate");
115         }
116
117         if (newArtifactId == null || newArtifactId.isEmpty()) {
118             throw new IllegalArgumentException("Missing maven artifact-id coordinate");
119         }
120
121         if (newVersion == null || newVersion.isEmpty()) {
122             throw new IllegalArgumentException("Missing maven version coordinate");
123         }
124
125         String controllerId = newGroupId + ":" + newArtifactId;
126         DroolsController controllerCopy = null;
127         synchronized (this) {
128             /*
129              * The Null Drools Controller for no maven coordinates is always here so when no
130              * coordinates present, this is the return point
131              *
132              * assert (controllerCopy instanceof NullDroolsController)
133              */
134             if (droolsControllers.containsKey(controllerId)) {
135                 controllerCopy = droolsControllers.get(controllerId);
136                 if (controllerCopy.getVersion().equalsIgnoreCase(newVersion)) {
137                     return controllerCopy;
138                 }
139             }
140         }
141
142         if (controllerCopy != null) {
143             /*
144              * a controller keyed by group id + artifact id exists but with different version =>
145              * version upgrade/downgrade
146              */
147
148             controllerCopy.updateToVersion(newGroupId, newArtifactId, newVersion, decoderConfigurations,
149                     encoderConfigurations);
150
151             return controllerCopy;
152         }
153
154         /* new drools controller */
155
156         DroolsController controller = applyBeforeInstance(properties, newGroupId, newArtifactId, newVersion,
157                         decoderConfigurations, encoderConfigurations);
158
159         if (controller == null) {
160             controller = new MavenDroolsController(newGroupId, newArtifactId, newVersion, decoderConfigurations,
161                     encoderConfigurations);
162         }
163
164         synchronized (this) {
165             droolsControllers.put(controllerId, controller);
166         }
167
168         final DroolsController controllerFinal = controller;
169
170         FeatureApiUtils.apply(getProviders(),
171             feature -> feature.afterInstance(controllerFinal, properties),
172             (feature, ex) -> logger.error("feature {} ({}) afterInstance() of drools controller {}:{}:{} failed",
173                             feature.getName(), feature.getSequenceNumber(),
174                             newGroupId, newArtifactId, newVersion, ex));
175
176         return controller;
177     }
178
179     private DroolsController applyBeforeInstance(Properties properties, String newGroupId, String newArtifactId,
180                     String newVersion, List<TopicCoderFilterConfiguration> decoderConfigurations,
181                     List<TopicCoderFilterConfiguration> encoderConfigurations) {
182         DroolsController controller = null;
183         for (DroolsControllerFeatureApi feature: getProviders()) {
184             try {
185                 controller = feature.beforeInstance(properties,
186                         newGroupId, newArtifactId, newVersion,
187                         decoderConfigurations, encoderConfigurations);
188                 if (controller != null) {
189                     logger.info("feature {} ({}) beforeInstance() has intercepted drools controller {}:{}:{}",
190                             feature.getName(), feature.getSequenceNumber(),
191                             newGroupId, newArtifactId, newVersion);
192                     break;
193                 }
194             } catch (RuntimeException r) {
195                 logger.error("feature {} ({}) beforeInstance() of drools controller {}:{}:{} failed",
196                         feature.getName(), feature.getSequenceNumber(),
197                         newGroupId, newArtifactId, newVersion, r);
198             }
199         }
200         return controller;
201     }
202
203     protected List<DroolsControllerFeatureApi> getProviders() {
204         return DroolsControllerFeatureApiConstants.getProviders().getList();
205     }
206
207     /**
208      * find out decoder classes and filters.
209      *
210      * @param properties properties with information about decoders
211      * @param topicEntities topic sources
212      * @return list of topics, each with associated decoder classes, each with a list of associated
213      *         filters
214      * @throws IllegalArgumentException invalid input data
215      */
216     protected List<TopicCoderFilterConfiguration> codersAndFilters(Properties properties,
217             List<? extends Topic> topicEntities) {
218
219         List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = new ArrayList<>();
220
221         if (topicEntities == null || topicEntities.isEmpty()) {
222             return topics2DecodedClasses2Filters;
223         }
224
225         for (Topic topic : topicEntities) {
226
227             // 1. first the topic
228
229             String firstTopic = topic.getTopic();
230
231             String propertyTopicEntityPrefix = getPropertyTopicPrefix(topic) + firstTopic;
232
233             // 2. check if there is a custom decoder for this topic that the user prefers to use
234             // instead of the ones provided in the platform
235
236             CustomGsonCoder customGsonCoder = getCustomCoder(properties, propertyTopicEntityPrefix);
237
238             // 3. second the list of classes associated with each topic
239
240             String eventClasses = properties
241                     .getProperty(propertyTopicEntityPrefix + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX);
242
243             if (eventClasses == null || eventClasses.isEmpty()) {
244                 logger.warn("There are no event classes for topic {}", firstTopic);
245                 continue;
246             }
247
248             List<PotentialCoderFilter> classes2Filters =
249                             getFilterExpressions(properties, propertyTopicEntityPrefix, eventClasses);
250
251             TopicCoderFilterConfiguration topic2Classes2Filters =
252                     new TopicCoderFilterConfiguration(firstTopic, classes2Filters, customGsonCoder);
253             topics2DecodedClasses2Filters.add(topic2Classes2Filters);
254         }
255
256         return topics2DecodedClasses2Filters;
257     }
258
259     private String getPropertyTopicPrefix(Topic topic) {
260         boolean isSource = topic instanceof TopicSource;
261         CommInfrastructure commInfra = topic.getTopicCommInfrastructure();
262         if (commInfra == CommInfrastructure.UEB) {
263             if (isSource) {
264                 return PolicyEndPointProperties.PROPERTY_UEB_SOURCE_TOPICS + ".";
265             } else {
266                 return PolicyEndPointProperties.PROPERTY_UEB_SINK_TOPICS + ".";
267             }
268         } else if (commInfra == CommInfrastructure.DMAAP) {
269             if (isSource) {
270                 return PolicyEndPointProperties.PROPERTY_DMAAP_SOURCE_TOPICS + ".";
271             } else {
272                 return PolicyEndPointProperties.PROPERTY_DMAAP_SINK_TOPICS + ".";
273             }
274         } else if (commInfra == CommInfrastructure.NOOP) {
275             if (isSource) {
276                 return PolicyEndPointProperties.PROPERTY_NOOP_SOURCE_TOPICS + ".";
277             } else {
278                 return PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS + ".";
279             }
280         } else {
281             throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra);
282         }
283     }
284
285     private CustomGsonCoder getCustomCoder(Properties properties, String propertyPrefix) {
286         String customGson = properties.getProperty(propertyPrefix
287                 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX);
288
289         CustomGsonCoder customGsonCoder = null;
290         if (customGson != null && !customGson.isEmpty()) {
291             try {
292                 customGsonCoder = new CustomGsonCoder(customGson);
293             } catch (IllegalArgumentException e) {
294                 logger.warn("{}: cannot create custom-gson-coder {} because of {}", this, customGson,
295                         e.getMessage(), e);
296             }
297         }
298         return customGsonCoder;
299     }
300
301     private List<PotentialCoderFilter> getFilterExpressions(Properties properties, String propertyPrefix,
302                     String eventClasses) {
303
304         List<PotentialCoderFilter> classes2Filters = new ArrayList<>();
305
306         List<String> topicClasses = new ArrayList<>(Arrays.asList(eventClasses.split("\\s*,\\s*")));
307
308         for (String theClass : topicClasses) {
309
310             // 4. for each coder class, get the filter expression
311
312             String filter = properties
313                     .getProperty(propertyPrefix
314                             + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX
315                             + "." + theClass + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX);
316
317             JsonProtocolFilter protocolFilter = new JsonProtocolFilter(filter);
318             PotentialCoderFilter class2Filters = new PotentialCoderFilter(theClass, protocolFilter);
319             classes2Filters.add(class2Filters);
320         }
321
322         return classes2Filters;
323     }
324
325     @Override
326     public void destroy(DroolsController controller) {
327         unmanage(controller);
328         controller.halt();
329     }
330
331     @Override
332     public void destroy() {
333         List<DroolsController> controllers = this.inventory();
334         for (DroolsController controller : controllers) {
335             controller.halt();
336         }
337
338         synchronized (this) {
339             this.droolsControllers.clear();
340         }
341     }
342
343     /**
344      * unmanage the drools controller.
345      *
346      * @param controller the controller
347      */
348     protected void unmanage(DroolsController controller) {
349         if (controller == null) {
350             throw new IllegalArgumentException("No controller provided");
351         }
352
353         if (!controller.isBrained()) {
354             logger.info("Drools Controller is NOT OPERATIONAL - nothing to destroy");
355             return;
356         }
357
358         String controllerId = controller.getGroupId() + ":" + controller.getArtifactId();
359         synchronized (this) {
360             if (!this.droolsControllers.containsKey(controllerId)) {
361                 return;
362             }
363
364             droolsControllers.remove(controllerId);
365         }
366     }
367
368     @Override
369     public void shutdown(DroolsController controller) {
370         this.unmanage(controller);
371         controller.shutdown();
372     }
373
374     @Override
375     public void shutdown() {
376         List<DroolsController> controllers = this.inventory();
377         for (DroolsController controller : controllers) {
378             controller.shutdown();
379         }
380
381         synchronized (this) {
382             this.droolsControllers.clear();
383         }
384     }
385
386     @Override
387     public DroolsController get(String groupId, String artifactId, String version) {
388
389         if (groupId == null || artifactId == null || groupId.isEmpty() || artifactId.isEmpty()) {
390             throw new IllegalArgumentException("Missing maven coordinates: " + groupId + ":" + artifactId);
391         }
392
393         String controllerId = groupId + ":" + artifactId;
394
395         synchronized (this) {
396             if (this.droolsControllers.containsKey(controllerId)) {
397                 return droolsControllers.get(controllerId);
398             } else {
399                 throw new IllegalStateException("DroolController for " + controllerId + " not found");
400             }
401         }
402     }
403
404     @Override
405     public List<DroolsController> inventory() {
406         return new ArrayList<>(this.droolsControllers.values());
407     }
408
409     @Override
410     public String toString() {
411         StringBuilder builder = new StringBuilder();
412         builder.append("IndexedDroolsControllerFactory [#droolsControllers=").append(droolsControllers.size())
413                 .append("]");
414         return builder.toString();
415     }
416
417 }