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();