2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2021, 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.system.internal;
24 import com.google.re2j.Pattern;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Properties;
29 import java.util.concurrent.atomic.AtomicReference;
30 import java.util.stream.Collectors;
32 import lombok.ToString;
33 import org.onap.policy.common.endpoints.event.comm.Topic;
34 import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
35 import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
36 import org.onap.policy.common.endpoints.event.comm.TopicListener;
37 import org.onap.policy.common.endpoints.event.comm.TopicSink;
38 import org.onap.policy.common.endpoints.event.comm.TopicSource;
39 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
40 import org.onap.policy.common.utils.services.FeatureApiUtils;
41 import org.onap.policy.drools.controller.DroolsController;
42 import org.onap.policy.drools.controller.DroolsControllerConstants;
43 import org.onap.policy.drools.controller.DroolsControllerFactory;
44 import org.onap.policy.drools.features.PolicyControllerFeatureApi;
45 import org.onap.policy.drools.features.PolicyControllerFeatureApiConstants;
46 import org.onap.policy.drools.persistence.SystemPersistence;
47 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
48 import org.onap.policy.drools.properties.DroolsPropertyConstants;
49 import org.onap.policy.drools.protocol.configuration.DroolsConfiguration;
50 import org.onap.policy.drools.system.PolicyController;
51 import org.onap.policy.drools.utils.PropertyUtil;
52 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * This implementation of the Policy Controller merely aggregates and tracks for management purposes
58 * all underlying resources that this controller depends upon.
60 @ToString(onlyExplicitlyIncluded = true)
61 public class AggregatedPolicyController implements PolicyController, TopicListener {
63 private static final String BEFORE_OFFER_FAILURE = "{}: feature {} before-offer failure because of {}";
64 private static final String AFTER_OFFER_FAILURE = "{}: feature {} after-offer failure because of {}";
69 private static final Logger logger = LoggerFactory.getLogger(AggregatedPolicyController.class);
70 private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
73 * identifier for this policy controller.
77 private final String name;
80 * Abstracted Event Sources List regardless communication technology.
83 protected final List<TopicSource> topicSources;
86 * Abstracted Event Sinks List regardless communication technology.
89 protected final List<TopicSink> topicSinks;
92 * Mapping topics to sinks.
95 private final HashMap<String, TopicSink> topic2Sinks = new HashMap<>();
98 * Is this Policy Controller running (alive) ? reflects invocation of start()/stop() only.
102 private volatile boolean alive;
105 * Is this Policy Controller locked ? reflects if i/o controller related operations and start
106 * are permitted, more specifically: start(), deliver() and onTopicEvent(). It does not affect
107 * the ability to stop the underlying drools infrastructure
111 private volatile boolean locked;
114 * Policy Drools Controller.
117 protected final AtomicReference<DroolsController> droolsController = new AtomicReference<>();
120 * Properties used to initialize controller.
122 private final Properties properties;
127 private final List<ToscaConceptIdentifier> policyTypes;
130 * Constructor version mainly used for bootstrapping at initialization time a policy engine
133 * @param name controller name
134 * @param properties controller properties
136 * @throws IllegalArgumentException when invalid arguments are provided
138 public AggregatedPolicyController(String name, Properties properties) {
143 * 1. Register read topics with network infrastructure
144 * 2. Register write topics with network infrastructure
145 * 3. Register with drools infrastructure
148 // Create/Reuse Readers/Writers for all event sources endpoints
150 this.topicSources = getEndpointManager().addTopicSources(properties);
151 this.topicSinks = getEndpointManager().addTopicSinks(properties);
153 initDrools(properties);
156 /* persist new properties */
157 getPersistenceManager().storeController(name, properties);
158 this.properties = PropertyUtil.getInterpolatedProperties(properties);
160 this.policyTypes = getPolicyTypesFromProperties();
164 public List<ToscaConceptIdentifier> getPolicyTypes() {
165 if (!policyTypes.isEmpty()) {
169 return droolsController
171 .getBaseDomainNames()
173 .map(d -> new ToscaConceptIdentifier(d,
174 DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION))
175 .collect(Collectors.toList());
178 protected List<ToscaConceptIdentifier> getPolicyTypesFromProperties() {
179 List<ToscaConceptIdentifier> policyTypeIds = new ArrayList<>();
181 String ptiPropValue = properties.getProperty(DroolsPropertyConstants.PROPERTY_CONTROLLER_POLICY_TYPES);
182 if (ptiPropValue == null) {
183 return policyTypeIds;
186 for (String pti : COMMA_SPACE_PAT.split(ptiPropValue)) {
187 String[] ptv = pti.split(":");
188 if (ptv.length == 1) {
189 policyTypeIds.add(new ToscaConceptIdentifier(ptv[0],
190 DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION));
191 } else if (ptv.length == 2) {
192 policyTypeIds.add(new ToscaConceptIdentifier(ptv[0], ptv[1]));
196 return policyTypeIds;
200 * initialize drools layer.
202 * @throws IllegalArgumentException if invalid parameters are passed in
204 protected void initDrools(Properties properties) {
206 // Register with drools infrastructure
207 this.droolsController.set(getDroolsFactory().build(properties, topicSources, topicSinks));
208 } catch (Exception | LinkageError e) {
209 logger.error("{}: cannot init-drools", this);
210 throw new IllegalArgumentException(e);
217 * @throws IllegalArgumentException if invalid parameters are passed in
219 private void initSinks() {
220 this.topic2Sinks.clear();
221 for (TopicSink sink : topicSinks) {
222 this.topic2Sinks.put(sink.getTopic(), sink);
230 public boolean updateDrools(DroolsConfiguration newDroolsConfiguration) {
231 DroolsController controller = this.droolsController.get();
232 var oldDroolsConfiguration = new DroolsConfiguration(controller.getArtifactId(),
233 controller.getGroupId(), controller.getVersion());
235 if (oldDroolsConfiguration.getGroupId().equalsIgnoreCase(newDroolsConfiguration.getGroupId())
236 && oldDroolsConfiguration.getArtifactId().equalsIgnoreCase(newDroolsConfiguration.getArtifactId())
237 && oldDroolsConfiguration.getVersion().equalsIgnoreCase(newDroolsConfiguration.getVersion())) {
238 logger.warn("{}: cannot update-drools: identical configuration {} vs {}", this, oldDroolsConfiguration,
239 newDroolsConfiguration);
243 if (FeatureApiUtils.apply(getProviders(),
244 feature -> feature.beforePatch(this, oldDroolsConfiguration, newDroolsConfiguration),
245 (feature, ex) -> logger.error("{}: feature {} before-patch failure because of {}", this,
246 feature.getClass().getName(), ex.getMessage(), ex))) {
250 if (controller.isBrained()
251 && (newDroolsConfiguration.getArtifactId() == null
252 || DroolsControllerConstants.NO_ARTIFACT_ID.equals(newDroolsConfiguration.getArtifactId()))) {
253 // detach maven artifact
254 DroolsControllerConstants.getFactory().destroy(controller);
259 this.properties.setProperty(DroolsPropertyConstants.RULES_GROUPID, newDroolsConfiguration.getGroupId());
260 this.properties.setProperty(DroolsPropertyConstants.RULES_ARTIFACTID,
261 newDroolsConfiguration.getArtifactId());
262 this.properties.setProperty(DroolsPropertyConstants.RULES_VERSION, newDroolsConfiguration.getVersion());
263 getPersistenceManager().storeController(name, this.properties);
265 this.initDrools(this.properties);
267 // have a new controller now - get it
268 controller = this.droolsController.get();
281 } catch (RuntimeException e) {
282 logger.error("{}: cannot update-drools because of {}", this, e.getMessage(), e);
286 boolean finalSuccess = success;
287 FeatureApiUtils.apply(getProviders(),
288 feature -> feature.afterPatch(this, oldDroolsConfiguration, newDroolsConfiguration, finalSuccess),
289 (feature, ex) -> logger.error("{}: feature {} after-patch failure because of {}", this,
290 feature.getClass().getName(), ex.getMessage(), ex));
299 public boolean start() {
300 logger.info("{}: start", this);
302 if (FeatureApiUtils.apply(getProviders(),
303 feature -> feature.beforeStart(this),
304 (feature, ex) -> logger.error("{}: feature {} before-start failure because of {}", this,
305 feature.getClass().getName(), ex.getMessage(), ex))) {
309 if (this.isLocked()) {
310 throw new IllegalStateException("Policy Controller " + name + " is locked");
313 synchronized (this) {
321 final boolean success = this.droolsController.get().start();
323 // register for events
325 for (TopicSource source : topicSources) {
326 source.register(this);
329 for (TopicSink sink : topicSinks) {
332 } catch (Exception e) {
333 logger.error("{}: cannot start {} because of {}", this, sink, e.getMessage(), e);
337 FeatureApiUtils.apply(getProviders(),
338 feature -> feature.afterStart(this),
339 (feature, ex) -> logger.error("{}: feature {} after-start failure because of {}", this,
340 feature.getClass().getName(), ex.getMessage(), ex));
349 public boolean stop() {
350 logger.info("{}: stop", this);
352 if (FeatureApiUtils.apply(getProviders(),
353 feature -> feature.beforeStop(this),
354 (feature, ex) -> logger.error("{}: feature {} before-stop failure because of {}", this,
355 feature.getClass().getName(), ex.getMessage(), ex))) {
359 /* stop regardless locked state */
361 synchronized (this) {
369 // 1. Stop registration
371 for (TopicSource source : topicSources) {
372 source.unregister(this);
375 boolean success = this.droolsController.get().stop();
377 FeatureApiUtils.apply(getProviders(),
378 feature -> feature.afterStop(this),
379 (feature, ex) -> logger.error("{}: feature {} after-stop failure because of {}", this,
380 feature.getClass().getName(), ex.getMessage(), ex));
389 public void shutdown() {
390 logger.info("{}: shutdown", this);
392 if (FeatureApiUtils.apply(getProviders(),
393 feature -> feature.beforeShutdown(this),
394 (feature, ex) -> logger.error("{}: feature {} before-shutdown failure because of {}", this,
395 feature.getClass().getName(), ex.getMessage(), ex))) {
401 getDroolsFactory().shutdown(this.droolsController.get());
403 FeatureApiUtils.apply(getProviders(),
404 feature -> feature.afterShutdown(this),
405 (feature, ex) -> logger.error("{}: feature {} after-shutdown failure because of {}", this,
406 feature.getClass().getName(), ex.getMessage(), ex));
414 logger.info("{}: halt", this);
416 if (FeatureApiUtils.apply(getProviders(),
417 feature -> feature.beforeHalt(this),
418 (feature, ex) -> logger.error("{}: feature {} before-halt failure because of {}", this,
419 feature.getClass().getName(), ex.getMessage(), ex))) {
424 getDroolsFactory().destroy(this.droolsController.get());
425 getPersistenceManager().deleteController(this.name);
427 FeatureApiUtils.apply(getProviders(),
428 feature -> feature.afterHalt(this),
429 (feature, ex) -> logger.error("{}: feature {} after-halt failure because of {}", this,
430 feature.getClass().getName(), ex.getMessage(), ex));
437 public void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event) {
438 logger.debug("{}: raw event offered from {}:{}: {}", this, commType, topic, event);
444 if (FeatureApiUtils.apply(getProviders(),
445 feature -> feature.beforeOffer(this, commType, topic, event),
446 (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
447 feature.getClass().getName(), ex.getMessage(), ex))) {
451 boolean success = this.droolsController.get().offer(topic, event);
453 FeatureApiUtils.apply(getProviders(),
454 feature -> feature.afterOffer(this, commType, topic, event, success),
455 (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
456 feature.getClass().getName(), ex.getMessage(), ex));
460 public <T> boolean offer(T event) {
461 logger.debug("{}: event offered: {}", this, event);
467 if (FeatureApiUtils.apply(getProviders(),
468 feature -> feature.beforeOffer(this, event),
469 (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
470 feature.getClass().getName(), ex.getMessage(), ex))) {
474 boolean success = this.droolsController.get().offer(event);
476 FeatureApiUtils.apply(getProviders(),
477 feature -> feature.afterOffer(this, event, success),
478 (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
479 feature.getClass().getName(), ex.getMessage(), ex));
484 private boolean skipOffer() {
485 return isLocked() || !isAlive();
492 public boolean deliver(Topic.CommInfrastructure commType, String topic, Object event) {
494 logger.debug("{}: deliver event to {}:{}: {}", this, commType, topic, event);
496 if (FeatureApiUtils.apply(getProviders(),
497 feature -> feature.beforeDeliver(this, commType, topic, event),
498 (feature, ex) -> logger.error("{}: feature {} before-deliver failure because of {}", this,
499 feature.getClass().getName(), ex.getMessage(), ex))) {
503 if (topic == null || topic.isEmpty()) {
504 throw new IllegalArgumentException("Invalid Topic");
508 throw new IllegalArgumentException("Invalid Event");
511 if (!this.isAlive()) {
512 throw new IllegalStateException("Policy Engine is stopped");
515 if (this.isLocked()) {
516 throw new IllegalStateException("Policy Engine is locked");
519 if (!this.topic2Sinks.containsKey(topic)) {
520 logger.warn("{}: cannot deliver event because the sink {}:{} is not registered: {}", this, commType, topic,
522 throw new IllegalArgumentException("Unsupported topic " + topic + " for delivery");
525 boolean success = this.droolsController.get().deliver(this.topic2Sinks.get(topic), event);
527 FeatureApiUtils.apply(getProviders(),
528 feature -> feature.afterDeliver(this, commType, topic, event, success),
529 (feature, ex) -> logger.error("{}: feature {} after-deliver failure because of {}", this,
530 feature.getClass().getName(), ex.getMessage(), ex));
539 public boolean lock() {
540 logger.info("{}: lock", this);
542 if (FeatureApiUtils.apply(getProviders(),
543 feature -> feature.beforeLock(this),
544 (feature, ex) -> logger.error("{}: feature {} before-lock failure because of {}", this,
545 feature.getClass().getName(), ex.getMessage(), ex))) {
549 synchronized (this) {
557 // it does not affect associated sources/sinks, they are
558 // autonomous entities
560 boolean success = this.droolsController.get().lock();
562 FeatureApiUtils.apply(getProviders(),
563 feature -> feature.afterLock(this),
564 (feature, ex) -> logger.error("{}: feature {} after-lock failure because of {}", this,
565 feature.getClass().getName(), ex.getMessage(), ex));
574 public boolean unlock() {
576 logger.info("{}: unlock", this);
578 if (FeatureApiUtils.apply(getProviders(),
579 feature -> feature.beforeUnlock(this),
580 (feature, ex) -> logger.error("{}: feature {} before-unlock failure because of {}", this,
581 feature.getClass().getName(), ex.getMessage(), ex))) {
585 synchronized (this) {
593 boolean success = this.droolsController.get().unlock();
595 FeatureApiUtils.apply(getProviders(),
596 feature -> feature.afterUnlock(this),
597 (feature, ex) -> logger.error("{}: feature {} after-unlock failure because of {}", this,
598 feature.getClass().getName(), ex.getMessage(), ex));
607 public DroolsController getDrools() {
608 return this.droolsController.get();
617 public Properties getProperties() {
618 return this.properties;
621 // the following methods may be overridden by junit tests
623 protected SystemPersistence getPersistenceManager() {
624 return SystemPersistenceConstants.getManager();
627 protected TopicEndpoint getEndpointManager() {
628 return TopicEndpointManager.getManager();
631 protected DroolsControllerFactory getDroolsFactory() {
632 return DroolsControllerConstants.getFactory();
635 protected List<PolicyControllerFeatureApi> getProviders() {
636 return PolicyControllerFeatureApiConstants.getProviders().getList();