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