2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023-2024 Nordix Foundation.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.drools.controller;
24 import com.google.re2j.Pattern;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Properties;
30 import lombok.NonNull;
31 import org.apache.commons.lang3.StringUtils;
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.FeatureApiUtils;
38 import org.onap.policy.drools.controller.internal.MavenDroolsController;
39 import org.onap.policy.drools.controller.internal.NullDroolsController;
40 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
41 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
42 import org.onap.policy.drools.properties.DroolsPropertyConstants;
43 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
44 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
45 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
46 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Factory of Drools Controllers indexed by the Maven coordinates.
53 class IndexedDroolsControllerFactory implements DroolsControllerFactory {
55 private static final Logger logger = LoggerFactory.getLogger(IndexedDroolsControllerFactory.class);
56 private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
59 * Policy Controller Name Index.
61 protected Map<String, DroolsController> droolsControllers = new HashMap<>();
64 * Null Drools Controller.
66 protected NullDroolsController nullDroolsController;
69 * Constructs the object.
71 public IndexedDroolsControllerFactory() {
73 /* Add a NULL controller which will always be present in the hash */
75 nullDroolsController = new NullDroolsController();
76 String controllerId = nullDroolsController.getGroupId() + ":" + nullDroolsController.getArtifactId();
79 droolsControllers.put(controllerId, nullDroolsController);
84 public DroolsController build(Properties properties, List<? extends TopicSource> eventSources,
85 List<? extends TopicSink> eventSinks) throws LinkageError {
87 String groupId = properties.getProperty(DroolsPropertyConstants.RULES_GROUPID);
88 if (StringUtils.isBlank(groupId)) {
89 groupId = DroolsControllerConstants.NO_GROUP_ID;
92 String artifactId = properties.getProperty(DroolsPropertyConstants.RULES_ARTIFACTID);
93 if (StringUtils.isBlank(artifactId)) {
94 artifactId = DroolsControllerConstants.NO_ARTIFACT_ID;
97 String version = properties.getProperty(DroolsPropertyConstants.RULES_VERSION);
98 if (StringUtils.isBlank(version)) {
99 version = DroolsControllerConstants.NO_VERSION;
102 List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = codersAndFilters(properties, eventSources);
103 List<TopicCoderFilterConfiguration> topics2EncodedClasses2Filters = codersAndFilters(properties, eventSinks);
105 return this.build(properties, groupId, artifactId, version,
106 topics2DecodedClasses2Filters, topics2EncodedClasses2Filters);
110 public DroolsController build(Properties properties, String newGroupId, String newArtifactId, String newVersion,
111 List<TopicCoderFilterConfiguration> decoderConfigurations,
112 List<TopicCoderFilterConfiguration> encoderConfigurations) throws LinkageError {
114 if (StringUtils.isBlank(newGroupId)) {
115 throw new IllegalArgumentException("Missing maven group-id coordinate");
118 if (StringUtils.isBlank(newArtifactId)) {
119 throw new IllegalArgumentException("Missing maven artifact-id coordinate");
122 if (StringUtils.isBlank(newVersion)) {
123 throw new IllegalArgumentException("Missing maven version coordinate");
126 String controllerId = newGroupId + ":" + newArtifactId;
127 DroolsController controllerCopy = null;
128 synchronized (this) {
130 * The Null Drools Controller for no maven coordinates is always here so when no
131 * coordinates present, this is the return point
133 * assert (controllerCopy instanceof NullDroolsController)
135 if (droolsControllers.containsKey(controllerId)) {
136 controllerCopy = droolsControllers.get(controllerId);
137 if (controllerCopy.getVersion().equalsIgnoreCase(newVersion)) {
138 return controllerCopy;
143 if (controllerCopy != null) {
145 * a controller keyed by group id + artifact id exists but with different version =>
146 * version upgrade/downgrade
149 controllerCopy.updateToVersion(newGroupId, newArtifactId, newVersion, decoderConfigurations,
150 encoderConfigurations);
152 return controllerCopy;
155 /* new drools controller */
157 DroolsController controller = applyBeforeInstance(properties, newGroupId, newArtifactId, newVersion,
158 decoderConfigurations, encoderConfigurations);
160 if (controller == null) {
161 controller = new MavenDroolsController(newGroupId, newArtifactId, newVersion, decoderConfigurations,
162 encoderConfigurations);
165 synchronized (this) {
166 droolsControllers.put(controllerId, controller);
169 final DroolsController controllerFinal = controller;
171 FeatureApiUtils.apply(getProviders(),
172 feature -> feature.afterInstance(controllerFinal, properties),
173 (feature, ex) -> logger.error("feature {} ({}) afterInstance() of drools controller {}:{}:{} failed",
174 feature.getName(), feature.getSequenceNumber(),
175 newGroupId, newArtifactId, newVersion, ex));
180 private DroolsController applyBeforeInstance(Properties properties, String newGroupId, String newArtifactId,
181 String newVersion, List<TopicCoderFilterConfiguration> decoderConfigurations,
182 List<TopicCoderFilterConfiguration> encoderConfigurations) {
183 DroolsController controller = null;
184 for (DroolsControllerFeatureApi feature: getProviders()) {
186 controller = feature.beforeInstance(properties,
187 newGroupId, newArtifactId, newVersion,
188 decoderConfigurations, encoderConfigurations);
189 if (controller != null) {
190 logger.info("feature {} ({}) beforeInstance() has intercepted drools controller {}:{}:{}",
191 feature.getName(), feature.getSequenceNumber(),
192 newGroupId, newArtifactId, newVersion);
195 } catch (RuntimeException r) {
196 logger.error("feature {} ({}) beforeInstance() of drools controller {}:{}:{} failed",
197 feature.getName(), feature.getSequenceNumber(),
198 newGroupId, newArtifactId, newVersion, r);
204 protected List<DroolsControllerFeatureApi> getProviders() {
205 return DroolsControllerFeatureApiConstants.getProviders().getList();
209 * find out decoder classes and filters.
211 * @param properties properties with information about decoders
212 * @param topicEntities topic sources
213 * @return list of topics, each with associated decoder classes, each with a list of associated
215 * @throws IllegalArgumentException invalid input data
217 protected List<TopicCoderFilterConfiguration> codersAndFilters(Properties properties,
218 List<? extends Topic> topicEntities) {
220 List<TopicCoderFilterConfiguration> topics2DecodedClasses2Filters = new ArrayList<>();
222 if (topicEntities == null || topicEntities.isEmpty()) {
223 return topics2DecodedClasses2Filters;
226 for (Topic topic : topicEntities) {
228 // 1. first the topic
230 String firstTopic = topic.getTopic();
232 String propertyTopicEntityPrefix = getPropertyTopicPrefix(topic) + firstTopic;
234 // 2. check if there is a custom decoder for this topic that the user prefers to use
235 // instead of the ones provided in the platform
237 var customGsonCoder = getCustomCoder(properties, propertyTopicEntityPrefix);
239 // 3. second the list of classes associated with each topic
241 String eventClasses = properties
242 .getProperty(propertyTopicEntityPrefix + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX);
244 if (StringUtils.isBlank(eventClasses)) {
245 logger.warn("There are no event classes for topic {}", firstTopic);
249 List<PotentialCoderFilter> classes2Filters =
250 getFilterExpressions(properties, propertyTopicEntityPrefix, eventClasses);
252 topics2DecodedClasses2Filters
253 .add(new TopicCoderFilterConfiguration(firstTopic, classes2Filters, customGsonCoder));
256 return topics2DecodedClasses2Filters;
259 private String getPropertyTopicPrefix(Topic topic) {
260 boolean isSource = topic instanceof TopicSource;
261 var commInfra = topic.getTopicCommInfrastructure();
262 if (commInfra == CommInfrastructure.NOOP) {
264 return PolicyEndPointProperties.PROPERTY_NOOP_SOURCE_TOPICS + ".";
266 return PolicyEndPointProperties.PROPERTY_NOOP_SINK_TOPICS + ".";
268 } else if (commInfra == CommInfrastructure.KAFKA) {
270 return PolicyEndPointProperties.PROPERTY_KAFKA_SOURCE_TOPICS + ".";
272 return PolicyEndPointProperties.PROPERTY_KAFKA_SINK_TOPICS + ".";
275 throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra);
279 private CustomGsonCoder getCustomCoder(Properties properties, String propertyPrefix) {
280 String customGson = properties.getProperty(propertyPrefix
281 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX);
283 CustomGsonCoder customGsonCoder = null;
284 if (StringUtils.isNotBlank(customGson)) {
286 customGsonCoder = new CustomGsonCoder(customGson);
287 } catch (IllegalArgumentException e) {
288 logger.warn("{}: cannot create custom-gson-coder {} because of {}", this, customGson,
292 return customGsonCoder;
295 private List<PotentialCoderFilter> getFilterExpressions(Properties properties, String propertyPrefix,
296 @NonNull String eventClasses) {
298 List<PotentialCoderFilter> classes2Filters = new ArrayList<>();
299 for (String theClass : COMMA_SPACE_PAT.split(eventClasses)) {
301 // 4. for each coder class, get the filter expression
303 String filter = properties
304 .getProperty(propertyPrefix
305 + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_SUFFIX
306 + "." + theClass + PolicyEndPointProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX);
308 var class2Filters = new PotentialCoderFilter(theClass, new JsonProtocolFilter(filter));
309 classes2Filters.add(class2Filters);
312 return classes2Filters;
316 public void destroy(DroolsController controller) {
317 unmanage(controller);
322 public void destroy() {
323 List<DroolsController> controllers = this.inventory();
324 for (DroolsController controller : controllers) {
328 synchronized (this) {
329 this.droolsControllers.clear();
334 * unmanage the drools controller.
336 * @param controller the controller
338 protected void unmanage(DroolsController controller) {
339 if (controller == null) {
340 throw new IllegalArgumentException("No controller provided");
343 if (!controller.isBrained()) {
344 logger.info("Drools Controller is NOT OPERATIONAL - nothing to destroy");
348 String controllerId = controller.getGroupId() + ":" + controller.getArtifactId();
349 synchronized (this) {
350 if (!this.droolsControllers.containsKey(controllerId)) {
354 droolsControllers.remove(controllerId);
359 public void shutdown(DroolsController controller) {
360 this.unmanage(controller);
361 controller.shutdown();
365 public void shutdown() {
366 List<DroolsController> controllers = this.inventory();
367 for (DroolsController controller : controllers) {
368 controller.shutdown();
371 synchronized (this) {
372 this.droolsControllers.clear();
377 public DroolsController get(String groupId, String artifactId, String version) {
379 if (StringUtils.isBlank(groupId) || StringUtils.isBlank(artifactId)) {
380 throw new IllegalArgumentException("Missing maven coordinates: " + groupId + ":" + artifactId);
383 String controllerId = groupId + ":" + artifactId;
385 synchronized (this) {
386 if (this.droolsControllers.containsKey(controllerId)) {
387 return droolsControllers.get(controllerId);
389 throw new IllegalStateException("DroolController for " + controllerId + " not found");
395 public List<DroolsController> inventory() {
396 return new ArrayList<>(this.droolsControllers.values());
400 public String toString() {
401 return "IndexedDroolsControllerFactory [#droolsControllers=" + droolsControllers.size() + "]";