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