2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.drools.controller;
23 import com.google.re2j.Pattern;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
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;
49 * Factory of Drools Controllers indexed by the Maven coordinates.
51 class IndexedDroolsControllerFactory implements DroolsControllerFactory {
53 private static final Logger logger = LoggerFactory.getLogger(IndexedDroolsControllerFactory.class);
54 private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
57 * Policy Controller Name Index.
59 protected Map<String, DroolsController> droolsControllers = new HashMap<>();
62 * Null Drools Controller.
64 protected NullDroolsController nullDroolsController = new NullDroolsController();
67 * Constructs the object.
69 public IndexedDroolsControllerFactory() {
71 /* Add a NULL controller which will always be present in the hash */
73 DroolsController controller = new NullDroolsController();
74 String controllerId = controller.getGroupId() + ":" + controller.getArtifactId();
77 droolsControllers.put(controllerId, controller);
82 public DroolsController build(Properties properties, List<? extends TopicSource> eventSources,
83 List<? extends TopicSink> eventSinks) throws LinkageError {
85 String groupId = properties.getProperty(DroolsPropertyConstants.RULES_GROUPID);
86 if (groupId == null || groupId.isEmpty()) {
87 groupId = DroolsControllerConstants.NO_GROUP_ID;
90 String artifactId = properties.getProperty(DroolsPropertyConstants.RULES_ARTIFACTID);
91 if (artifactId == null || artifactId.isEmpty()) {
92 artifactId = DroolsControllerConstants.NO_ARTIFACT_ID;
95 String version = properties.getProperty(DroolsPropertyConstants.RULES_VERSION);
96 if (version == null || version.isEmpty()) {
97 version = DroolsControllerConstants.NO_VERSION;
100 List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = codersAndFilters(properties, eventSources);
101 List<TopicCoderFilterConfiguration> topics2EncodedClasses2Filters = codersAndFilters(properties, eventSinks);
103 return this.build(properties, groupId, artifactId, version,
104 topics2DecodedClasses2Filters, topics2EncodedClasses2Filters);
108 public DroolsController build(Properties properties, String newGroupId, String newArtifactId, String newVersion,
109 List<TopicCoderFilterConfiguration> decoderConfigurations,
110 List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
112 if (newGroupId == null || newGroupId.isEmpty()) {
113 throw new IllegalArgumentException("Missing maven group-id coordinate");
116 if (newArtifactId == null || newArtifactId.isEmpty()) {
117 throw new IllegalArgumentException("Missing maven artifact-id coordinate");
120 if (newVersion == null || newVersion.isEmpty()) {
121 throw new IllegalArgumentException("Missing maven version coordinate");
124 String controllerId = newGroupId + ":" + newArtifactId;
125 DroolsController controllerCopy = null;
126 synchronized (this) {
128 * The Null Drools Controller for no maven coordinates is always here so when no
129 * coordinates present, this is the return point
131 * assert (controllerCopy instanceof NullDroolsController)
133 if (droolsControllers.containsKey(controllerId)) {
134 controllerCopy = droolsControllers.get(controllerId);
135 if (controllerCopy.getVersion().equalsIgnoreCase(newVersion)) {
136 return controllerCopy;
141 if (controllerCopy != null) {
143 * a controller keyed by group id + artifact id exists but with different version =>
144 * version upgrade/downgrade
147 controllerCopy.updateToVersion(newGroupId, newArtifactId, newVersion, decoderConfigurations,
148 encoderConfigurations);
150 return controllerCopy;
153 /* new drools controller */
155 DroolsController controller = applyBeforeInstance(properties, newGroupId, newArtifactId, newVersion,
156 decoderConfigurations, encoderConfigurations);
158 if (controller == null) {
159 controller = new MavenDroolsController(newGroupId, newArtifactId, newVersion, decoderConfigurations,
160 encoderConfigurations);
163 synchronized (this) {
164 droolsControllers.put(controllerId, controller);
167 final DroolsController controllerFinal = controller;
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));
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()) {
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);
193 } catch (RuntimeException r) {
194 logger.error("feature {} ({}) beforeInstance() of drools controller {}:{}:{} failed",
195 feature.getName(), feature.getSequenceNumber(),
196 newGroupId, newArtifactId, newVersion, r);
202 protected List<DroolsControllerFeatureApi> getProviders() {
203 return DroolsControllerFeatureApiConstants.getProviders().getList();
207 * find out decoder classes and filters.
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
213 * @throws IllegalArgumentException invalid input data
215 protected List<TopicCoderFilterConfiguration> codersAndFilters(Properties properties,
216 List<? extends Topic> topicEntities) {
218 List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = new ArrayList<>();
220 if (topicEntities == null || topicEntities.isEmpty()) {
221 return topics2DecodedClasses2Filters;
224 for (Topic topic : topicEntities) {
226 // 1. first the topic
228 String firstTopic = topic.getTopic();
230 String propertyTopicEntityPrefix = getPropertyTopicPrefix(topic) + firstTopic;
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
235 CustomGsonCoder customGsonCoder = getCustomCoder(properties, propertyTopicEntityPrefix);
237 // 3. second the list of classes associated with each topic
239 String eventClasses = properties
240 .getProperty(propertyTopicEntityPrefix + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX);
242 if (eventClasses == null || eventClasses.isEmpty()) {
243 logger.warn("There are no event classes for topic {}", firstTopic);
247 List<PotentialCoderFilter> classes2Filters =
248 getFilterExpressions(properties, propertyTopicEntityPrefix, eventClasses);
250 TopicCoderFilterConfiguration topic2Classes2Filters =
251 new TopicCoderFilterConfiguration(firstTopic, classes2Filters, customGsonCoder);
252 topics2DecodedClasses2Filters.add(topic2Classes2Filters);
255 return topics2DecodedClasses2Filters;
258 private String getPropertyTopicPrefix(Topic topic) {
259 boolean isSource = topic instanceof TopicSource;
260 CommInfrastructure commInfra = topic.getTopicCommInfrastructure();
261 if (commInfra == CommInfrastructure.UEB) {
263 return PolicyEndPointProperties.PROPERTY_UEB_SOURCE_TOPICS + ".";
265 return PolicyEndPointProperties.PROPERTY_UEB_SINK_TOPICS + ".";
267 } else if (commInfra == CommInfrastructure.DMAAP) {
269 return PolicyEndPointProperties.PROPERTY_DMAAP_SOURCE_TOPICS + ".";
271 return PolicyEndPointProperties.PROPERTY_DMAAP_SINK_TOPICS + ".";
273 } else if (commInfra == CommInfrastructure.NOOP) {
275 return PolicyEndPointProperties.PROPERTY_NOOP_SOURCE_TOPICS + ".";
277 return PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS + ".";
280 throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra);
284 private CustomGsonCoder getCustomCoder(Properties properties, String propertyPrefix) {
285 String customGson = properties.getProperty(propertyPrefix
286 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX);
288 CustomGsonCoder customGsonCoder = null;
289 if (customGson != null && !customGson.isEmpty()) {
291 customGsonCoder = new CustomGsonCoder(customGson);
292 } catch (IllegalArgumentException e) {
293 logger.warn("{}: cannot create custom-gson-coder {} because of {}", this, customGson,
297 return customGsonCoder;
300 private List<PotentialCoderFilter> getFilterExpressions(Properties properties, String propertyPrefix,
301 @NonNull String eventClasses) {
303 List<PotentialCoderFilter> classes2Filters = new ArrayList<>();
304 for (String theClass : COMMA_SPACE_PAT.split(eventClasses)) {
306 // 4. for each coder class, get the filter expression
308 String filter = properties
309 .getProperty(propertyPrefix
310 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX
311 + "." + theClass + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX);
313 JsonProtocolFilter protocolFilter = new JsonProtocolFilter(filter);
314 PotentialCoderFilter class2Filters = new PotentialCoderFilter(theClass, protocolFilter);
315 classes2Filters.add(class2Filters);
318 return classes2Filters;
322 public void destroy(DroolsController controller) {
323 unmanage(controller);
328 public void destroy() {
329 List<DroolsController> controllers = this.inventory();
330 for (DroolsController controller : controllers) {
334 synchronized (this) {
335 this.droolsControllers.clear();
340 * unmanage the drools controller.
342 * @param controller the controller
344 protected void unmanage(DroolsController controller) {
345 if (controller == null) {
346 throw new IllegalArgumentException("No controller provided");
349 if (!controller.isBrained()) {
350 logger.info("Drools Controller is NOT OPERATIONAL - nothing to destroy");
354 String controllerId = controller.getGroupId() + ":" + controller.getArtifactId();
355 synchronized (this) {
356 if (!this.droolsControllers.containsKey(controllerId)) {
360 droolsControllers.remove(controllerId);
365 public void shutdown(DroolsController controller) {
366 this.unmanage(controller);
367 controller.shutdown();
371 public void shutdown() {
372 List<DroolsController> controllers = this.inventory();
373 for (DroolsController controller : controllers) {
374 controller.shutdown();
377 synchronized (this) {
378 this.droolsControllers.clear();
383 public DroolsController get(String groupId, String artifactId, String version) {
385 if (groupId == null || artifactId == null || groupId.isEmpty() || artifactId.isEmpty()) {
386 throw new IllegalArgumentException("Missing maven coordinates: " + groupId + ":" + artifactId);
389 String controllerId = groupId + ":" + artifactId;
391 synchronized (this) {
392 if (this.droolsControllers.containsKey(controllerId)) {
393 return droolsControllers.get(controllerId);
395 throw new IllegalStateException("DroolController for " + controllerId + " not found");
401 public List<DroolsController> inventory() {
402 return new ArrayList<>(this.droolsControllers.values());
406 public String toString() {
407 StringBuilder builder = new StringBuilder();
408 builder.append("IndexedDroolsControllerFactory [#droolsControllers=").append(droolsControllers.size())
410 return builder.toString();