2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2018 Samsung Electronics Co., Ltd.
7 * Modifications Copyright (C) 2024 Nordix Foundation.
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.policy.drools.core;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.concurrent.ConcurrentHashMap;
31 import org.kie.api.KieServices;
32 import org.kie.api.builder.KieScanner;
33 import org.kie.api.builder.Message;
34 import org.kie.api.builder.ReleaseId;
35 import org.kie.api.builder.Results;
36 import org.kie.api.definition.KiePackage;
37 import org.kie.api.runtime.KieContainer;
38 import org.kie.api.runtime.KieSession;
39 import org.onap.policy.common.capabilities.Startable;
40 import org.onap.policy.drools.util.KieUtils;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * This class is a wrapper around 'KieContainer', which adds the ability to automatically create and
46 * track KieSession instances.
48 public class PolicyContainer implements Startable {
49 // get an instance of logger
50 private static final Logger logger = LoggerFactory.getLogger(PolicyContainer.class);
51 // 'KieServices' singleton
52 private static KieServices kieServices = KieServices.Factory.get();
54 // set of all 'PolicyContainer' instances
55 private static final HashSet<PolicyContainer> containers = new HashSet<>();
57 // maps feature objects to per-PolicyContainer data
58 private ConcurrentHashMap<Object, Object> adjuncts = new ConcurrentHashMap<>();
60 // 'KieContainer' associated with this 'PolicyContainer'
62 private KieContainer kieContainer;
64 // indicates whether the PolicyContainer is 'started'
65 // (started = sessions created, threads running)
66 private volatile boolean isStarted = false;
68 // maps session name into the associated 'PolicySession' instance
69 private final HashMap<String, PolicySession> sessions = new HashMap<>();
71 // if not null, this is a 'KieScanner' looking for updates
72 private KieScanner scanner = null;
74 // indicates whether the scanner has been started
75 // (it can block for a long time)
76 private boolean scannerStarted = false;
78 private static final String ERROR_STRING = "ERROR: Feature API: {}";
80 // packages that are included in all 'KieContainer' instances
81 private static Collection<KiePackage> commonPackages = null;
83 // all resources with this name consist of rules that are added to each container
84 private static final String COMMON_PACKAGES_RESOURCE_NAME = "META-INF/drools/drl";
87 * uses 'groupId', 'artifactId' and 'version', and fetches the associated artifact and remaining
88 * dependencies from the Maven repository to create the 'PolicyContainer' and associated
91 * <p>An exception occurs if the creation of the 'KieContainer' fails.
93 * @param groupId the 'groupId' associated with the artifact
94 * @param artifactId the artifact name
95 * @param version a comma-separated list of possible versions
97 public PolicyContainer(String groupId, String artifactId, String version) {
98 this(kieServices.newReleaseId(groupId, artifactId, version));
102 * uses the 'groupId', 'artifactId' and 'version' information in 'ReleaseId', and fetches the
103 * associated artifact and remaining dependencies from the Maven repository to create the
104 * 'PolicyContainer' and associated 'KieContainer'.
106 * <p>An exception occurs if the creation of the 'KieContainer' fails.
108 * @param releaseId indicates the artifact that is to be installed in this container
110 public PolicyContainer(ReleaseId releaseId) {
111 var newReleaseId = releaseId;
112 if (newReleaseId.getVersion().contains(",")) {
113 // this is actually a comma-separated list of release ids
115 loadArtifact(newReleaseId.getGroupId(), newReleaseId.getArtifactId(), newReleaseId.getVersion());
117 kieContainer = kieServices.newKieContainer(newReleaseId);
120 // add common KiePackage instances
122 synchronized (containers) {
123 if (newReleaseId != null) {
124 logger.info("Add a new kieContainer in containers: releaseId: {}", newReleaseId);
126 logger.warn("input releaseId is null");
128 containers.add(this);
130 // 'startScanner(releaseId)' was called at this point, but we have seen
131 // at least one case where the Drools container was repeatedly updated
132 // every 60 seconds. It isn't clear what conditions resulted in this
133 // behavior, so the call was removed. If needed, it can be explicitly
134 // called from a feature.
138 * Load an artifact into a new KieContainer. This method handles the case where the 'version' is
139 * actually a comma-separated list of versions.
141 * @param groupId the 'groupId' associated with the artifact
142 * @param artifactId the artifact name
143 * @param version a comma-separated list of possible versions
145 private ReleaseId loadArtifact(String groupId, String artifactId, String version) {
146 String[] versions = version.split(",");
147 if (versions.length > 1) {
148 logger.info("Multiple KieContainer versions are specified: {}", version);
151 // indicates a 'newKieContainer' call failed
152 RuntimeException exception = null;
154 // set prior to every 'newKieContainer' invocation
155 // (if we are able to create the container, it will be the last
156 // one that was successful)
157 ReleaseId releaseId = null;
158 for (String ver : versions) {
160 // Create a 'ReleaseId' object describing the artifact, and
161 // create a 'KieContainer' based upon it.
162 logger.info("Create new KieContainer start, version = {} ...", ver);
164 releaseId = kieServices.newReleaseId(groupId, artifactId, ver);
165 kieContainer = kieServices.newKieContainer(releaseId);
167 // clear any exception, and break out of the loop
170 } catch (RuntimeException e) {
174 if (exception != null) {
175 // all the 'newKieContainer' invocations failed -- throw the
176 // most recent exception
185 * @return the name of the container, which is the String equivalent of the 'ReleaseId'. It has
188 * (groupId + ":" + artifactId + ":" + version)
190 * Note that the name changes after a successful call to 'updateToVersion', although
191 * typically only the 'version' part changes.
193 public String getName() {
194 return kieContainer.getReleaseId().toString();
200 * @return the 'ClassLoader' associated with the 'KieContainer' instance
202 public ClassLoader getClassLoader() {
203 return kieContainer.getClassLoader();
209 * @return the Maven GroupId of the top-level artifact wrapped by the container.
211 public String getGroupId() {
212 return kieContainer.getReleaseId().getGroupId();
218 * @return the Maven ArtifactId of the top-level artifact wrapped by the container.
220 public String getArtifactId() {
221 return kieContainer.getReleaseId().getArtifactId();
227 * @return the version of the top-level artifact wrapped by the container (this may change as
230 public String getVersion() {
231 return kieContainer.getReleaseId().getVersion();
235 * Fetch the named 'PolicySession'.
237 * @param name the name of the KieSession (which is also the name of the associated
239 * @return a PolicySession if found, 'null' if not
241 public PolicySession getPolicySession(String name) {
242 return sessions.get(name);
246 * Internal method to create a PolicySession, possibly restoring it from persistent storage.
248 * @param name of the KieSession and PolicySession
249 * @param kieBaseName name of the associated 'KieBase' instance
250 * @return a new or existing PolicySession, or 'null' if not found
252 private PolicySession activatePolicySession(String name, String kieBaseName) {
253 synchronized (sessions) {
254 logger.info("activatePolicySession:name :{}", name);
255 PolicySession session = sessions.computeIfAbsent(name, key -> makeSession(name, kieBaseName));
257 logger.info("activatePolicySession:session - {} is returned.",
258 session == null ? "null" : session.getFullName());
263 private PolicySession makeSession(String name, String kieBaseName) {
264 PolicySession session = null;
265 KieSession kieSession = null;
267 // loop through all the features, and give each one
268 // a chance to create the 'KieSession'
269 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
271 if ((kieSession = feature.activatePolicySession(this, name, kieBaseName)) != null) {
274 } catch (Exception e) {
275 logger.error(ERROR_STRING, feature.getClass().getName(), e);
279 // if none of the features created the session, create one now
280 if (kieSession == null) {
281 kieSession = kieContainer.newKieSession(name);
284 if (kieSession != null) {
285 // creation of 'KieSession' was successful - build
287 session = new PolicySession(name, this, kieSession);
290 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
292 feature.newPolicySession(session);
293 } catch (Exception e) {
294 logger.error(ERROR_STRING, feature.getClass().getName(), e);
297 logger.info("activatePolicySession:new session was added in sessions with name {}", name);
304 * This creates a 'PolicySession' instance within this 'PolicyContainer', and ties it to the
305 * specified 'KieSession'. 'name' must not currently exist within the 'PolicyContainer', and the
306 * 'KieBase' object associated with 'KieSession' must belong to the 'KieContainer'. This method
307 * provides a way for 'KieSession' instances that are created programmatically to fit into this
310 * @param name the name for the new 'PolicySession'
311 * @param kieSession a 'KieSession' instance, that will be included in this infrastructure
312 * @return the new 'PolicySession'
313 * @throws IllegalArgumentException if 'kieSession' does not reside within this container
314 * @throws IllegalStateException if a 'PolicySession' already exists with this name
316 public PolicySession adoptKieSession(String name, KieSession kieSession) {
319 logger.warn("adoptKieSession:input name is null");
320 throw new IllegalArgumentException("KieSession input name is null " + getName());
321 } else if (kieSession == null) {
322 logger.warn("adoptKieSession:input kieSession is null");
323 throw new IllegalArgumentException("KieSession '" + name + "' is null " + getName());
325 logger.info("adoptKieSession:name: {} kieSession: {}", name, kieSession);
327 // fetch KieBase, and verify it belongs to this KieContainer
329 var kieBase = kieSession.getKieBase();
330 logger.info("adoptKieSession:kieBase: {}", kieBase);
331 for (String kieBaseName : kieContainer.getKieBaseNames()) {
332 logger.info("adoptKieSession:kieBaseName: {}", kieBaseName);
333 if (kieBase == kieContainer.getKieBase(kieBaseName)) {
338 logger.info("adoptKieSession:match {}", match);
339 // if we don't have a match yet, the last chance is to look at the
340 // default KieBase, if it exists
341 if (!match && kieBase != kieContainer.getKieBase()) {
342 throw new IllegalArgumentException(
343 "KieSession '" + name + "' does not reside within container " + getName());
346 synchronized (sessions) {
347 if (sessions.get(name) != null) {
348 throw new IllegalStateException("PolicySession '" + name + "' already exists");
351 // create the new 'PolicySession', add it to the table,
352 // and return the object to the caller
353 logger.info("adoptKieSession:create a new policySession with name {}", name);
354 var policySession = new PolicySession(name, this, kieSession);
355 sessions.put(name, policySession);
358 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
360 feature.newPolicySession(policySession);
361 } catch (Exception e) {
362 logger.error(ERROR_STRING, feature.getClass().getName(), e);
365 return policySession;
370 * This call 'KieContainer.updateToVersion()', and returns the associated response as a String.
371 * If successful, the name of this 'PolicyContainer' changes to match the new version.
373 * @param newVersion this is the version to update to (the 'groupId' and 'artifactId' remain the
375 * @return the list of messages associated with the update (not sure if this can be 'null', or
376 * how to determine success/failure)
378 public String updateToVersion(String newVersion) {
379 var releaseId = kieContainer.getReleaseId();
380 var results = this.updateToVersion(
381 kieServices.newReleaseId(releaseId.getGroupId(), releaseId.getArtifactId(), newVersion));
383 List<Message> messages = results == null ? null : results.getMessages();
384 return messages == null ? null : messages.toString();
388 * This calls 'KieContainer.updateToVersion()', and returns the associated response. If
389 * successful, the name of this 'PolicyContainer' changes to match the new version.
391 * @param releaseId the new artifact (usually new version) to be installed
392 * @return the 'Results' parameter from 'KieContainer.updateToVersion'
394 public Results updateToVersion(ReleaseId releaseId) {
395 if (releaseId == null) {
396 logger.warn("updateToVersion:input releaseId is null");
398 logger.info("updateToVersion:releaseId {}", releaseId);
401 // stop all session threads
402 for (PolicySession session : sessions.values()) {
403 session.stopThread();
406 // update the version
407 var results = kieContainer.updateToVersion(releaseId);
410 // add common KiePackage instances
413 // restart all session threads, and notify the sessions
414 for (PolicySession session : sessions.values()) {
415 session.startThread();
423 * Get policy containers.
425 * @return all existing 'PolicyContainer' instances
427 public static Collection<PolicyContainer> getPolicyContainers() {
428 synchronized (containers) {
429 return new HashSet<>(containers);
434 * Get policy sessions.
436 * @return all the 'PolicySession' instances
438 public Collection<PolicySession> getPolicySessions() {
439 // KLUDGE WARNING: this is a temporary workaround -- if there are
440 // no features, we don't have persistence, and 'activate' is never
441 // called. In this case, make sure the container is started.
442 if (PolicySessionFeatureApiConstants.getImpl().getList().isEmpty()) {
446 // return current set of PolicySessions
447 synchronized (sessions) {
448 return new HashSet<>(sessions.values());
453 * This method will start a 'KieScanner' (if not currently running), provided that the ReleaseId
454 * version is 'LATEST' or 'RELEASE', or refers to a SNAPSHOT version.
456 * @param releaseId the release id used to create the container
458 public synchronized void startScanner(ReleaseId releaseId) {
459 String version = releaseId.getVersion();
461 if (scannerStarted || scanner != null || version == null) {
465 if (!("LATEST".equals(version) || "RELEASE".equals(version) || version.endsWith("-SNAPSHOT"))) {
469 // create the scanner, and poll at 60 second intervals
471 scannerStarted = true;
473 // start this in a separate thread -- it can block for a long time
474 new Thread("Scanner Starter " + getName()) {
477 scanner = kieServices.newKieScanner(kieContainer);
478 scanner.start(60000L);
481 } catch (Exception e) {
482 // sometimes the scanner initialization fails for some reason
483 logger.error("startScanner error", e);
488 * Insert a fact into a specific named session.
490 * @param name this is the session name
491 * @param object this is the fact to be inserted into the session
492 * @return 'true' if the named session was found, 'false' if not
494 public boolean insert(String name, Object object) {
495 // TODO: Should the definition of 'name' be expanded to include an
496 // alternate entry point as well? For example, 'name.entryPoint' (or
497 // something other than '.' if that is a problem).
498 synchronized (sessions) {
499 PolicySession session = sessions.get(name);
500 if (session != null) {
501 session.insertDrools(object);
509 * Insert a fact into all sessions associated with this container.
511 * @param object this is the fact to be inserted into the sessions
512 * @return 'true' if the fact was inserted into at least one session, 'false' if not
514 public boolean insertAll(Object object) {
516 synchronized (sessions) {
517 for (PolicySession session : sessions.values()) {
518 session.insertDrools(object);
525 /*=======================*/
526 /* 'Startable' interface */
527 /*=======================*/
533 public synchronized boolean start() { // NOSONAR
535 * disabling sonar about returning the same value, because we prefer the code to
536 * be structured this way
543 // This will create all 'PolicySession' instances specified in the
544 // 'kmodule.xml' file that don't exist yet
545 for (String kieBaseName : kieContainer.getKieBaseNames()) {
546 for (String kieSessionName : kieContainer.getKieSessionNamesInKieBase(kieBaseName)) {
547 // if the 'PolicySession' does not currently exist, this method
548 // call will attempt to create it
549 var session = activatePolicySession(kieSessionName, kieBaseName);
550 if (session != null) {
551 session.startThread();
563 public synchronized boolean stop() {
564 return (!isStarted || doStop());
567 private boolean doStop() {
568 Collection<PolicySession> localSessions;
570 synchronized (sessions) {
571 // local set containing all of the sessions
572 localSessions = new HashSet<>(sessions.values());
574 // clear the 'name->session' map in 'PolicyContainer'
577 for (PolicySession session : localSessions) {
578 // stop session thread
579 session.stopThread();
581 // free KieSession resources
582 session.getKieSession().dispose();
585 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
587 feature.disposeKieSession(session);
588 } catch (Exception e) {
589 logger.error(ERROR_STRING, feature.getClass().getName(), e);
602 public synchronized void shutdown() {
603 // Note that this method does not call 'destroy' on the 'KieSession'
604 // instances, which would remove any associated information in persistent
605 // storage. Should it do this?
608 synchronized (containers) {
609 containers.remove(this);
612 // How do we free the resources associated with the KieContainer?
613 // Is garbage collection sufficient?
620 public boolean isAlive() {
625 * This method is similar to 'shutdown', but it also frees any persistence resources as well.
627 public synchronized void destroy() {
628 // we need all KieSession instances running in order to free
629 // resources associated with persistence
631 Collection<PolicySession> localSessions;
633 synchronized (sessions) {
634 // local set containing all of the sessions
635 localSessions = new HashSet<>(sessions.values());
637 // clear the 'name->session' map in 'PolicyContainer'
640 for (PolicySession session : localSessions) {
641 // stop session thread
642 session.stopThread();
644 // free KieSession resources
645 session.getKieSession().destroy();
648 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
650 feature.destroyKieSession(session);
651 } catch (Exception e) {
652 logger.error(ERROR_STRING, feature.getClass().getName(), e);
658 synchronized (containers) {
659 containers.remove(this);
662 // How do we free the resources associated with the KieContainer?
663 // Is garbage collection sufficient?
667 * This method is called when the host goes from the 'standby->active' state.
669 public static void activate() {
670 // start all of the 'PolicyContainer' instances
671 for (PolicyContainer container : containers) {
674 } catch (Exception e) {
675 logger.error("PolicyContainer.start() error in activate", e);
681 * This method is called when the host goes from the 'active->standby' state.
683 public static void deactivate() {
684 // deactivate all of the 'PolicyContainer' instances
685 for (PolicyContainer container : containers) {
688 } catch (Exception e) {
689 logger.error("PolicyContainer.start() error in deactivate", e);
695 * This method does the following:
697 * <p>1) Initializes logging 2) Starts the DroolsPDP Integrity Monitor 3) Initilaizes persistence
699 * <p>It no longer reads in properties files, o creates 'PolicyContainer' instances.
701 * @param args standard 'main' arguments, which are currently ignored
703 public static void globalInit(String[] args) {
704 var configDir = "config";
705 logger.info("PolicyContainer.main: configDir={}", configDir);
707 // invoke 'globalInit' on all the features
708 for (PolicySessionFeatureApi feature : PolicySessionFeatureApiConstants.getImpl().getList()) {
710 feature.globalInit(args, configDir);
711 } catch (Exception e) {
712 logger.error(ERROR_STRING, feature.getClass().getName(), e);
718 * Fetch the adjunct object associated with a given feature.
720 * @param object this is typically the singleton feature object that is used as a key, but it
721 * might also be useful to use nested objects within the feature as keys.
722 * @return a feature-specific object associated with the key, or 'null' if it is not found.
724 public Object getAdjunct(Object object) {
725 return adjuncts.get(object);
729 * Store the adjunct object associated with a given feature.
731 * @param object this is typically the singleton feature object that is used as a key, but it
732 * might also be useful to use nested objects within the feature as keys.
733 * @param value a feature-specific object associated with the key, or 'null' if the
734 * feature-specific object should be removed
736 public void setAdjunct(Object object, Object value) {
738 adjuncts.remove(object);
740 adjuncts.put(object, value);
745 * Add 'KiePackages' that are common to all containers.
747 private void addCommonPackages() {
748 // contains the list of 'KiePackages' to add to each 'KieBase'
749 Collection<KiePackage> kiePackages;
750 synchronized (PolicyContainer.class) {
751 if (commonPackages == null) {
752 commonPackages = KieUtils.resourceToPackages(
753 PolicyContainer.class.getClassLoader(), COMMON_PACKAGES_RESOURCE_NAME).orElse(null);
754 if (commonPackages == null) {
755 // a problem occurred, which has already been logged --
756 // just store an empty collection, so we don't keep doing
758 commonPackages = new HashSet<>();
762 kiePackages = commonPackages;
765 // if we reach this point, 'kiePackages' contains a non-null list
766 // of packages to add
767 for (String name : kieContainer.getKieBaseNames()) {
768 KieUtils.addKiePackages(kieContainer.getKieBase(name), kiePackages);