2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-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.internal;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.Objects;
29 import java.util.stream.Collectors;
30 import org.apache.commons.collections4.queue.CircularFifoQueue;
31 import org.checkerframework.checker.nullness.qual.NonNull;
32 import org.drools.core.ClassObjectFilter;
33 import org.kie.api.definition.KiePackage;
34 import org.kie.api.definition.rule.Query;
35 import org.kie.api.runtime.KieSession;
36 import org.kie.api.runtime.rule.FactHandle;
37 import org.kie.api.runtime.rule.QueryResultsRow;
38 import org.onap.policy.common.endpoints.event.comm.TopicSink;
39 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
40 import org.onap.policy.common.gson.annotation.GsonJsonProperty;
41 import org.onap.policy.common.utils.services.FeatureApiUtils;
42 import org.onap.policy.common.utils.services.OrderedServiceImpl;
43 import org.onap.policy.drools.controller.DroolsController;
44 import org.onap.policy.drools.controller.DroolsControllerConstants;
45 import org.onap.policy.drools.core.PolicyContainer;
46 import org.onap.policy.drools.core.PolicySession;
47 import org.onap.policy.drools.core.jmx.PdpJmx;
48 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
49 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
50 import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
51 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
52 import org.onap.policy.drools.protocol.coders.EventProtocolParams;
53 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
54 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
55 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
56 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
57 import org.onap.policy.drools.utils.ReflectionUtil;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * Maven-based Drools Controller that interacts with the
63 * policy-core PolicyContainer and PolicySession to manage
64 * Drools containers instantiated using Maven.
66 public class MavenDroolsController implements DroolsController {
68 private static final String FACT_RETRIEVE_ERROR = "Object cannot be retrieved from fact {}";
73 private static final Logger logger = LoggerFactory.getLogger(MavenDroolsController.class);
76 * Policy Container, the access object to the policy-core layer.
79 protected final PolicyContainer policyContainer;
82 * alive status of this drools controller,
83 * reflects invocation of start()/stop() only.
85 protected volatile boolean alive = false;
88 * locked status of this drools controller,
89 * reflects if i/o drools related operations are permitted,
90 * more specifically: offer() and deliver().
91 * It does not affect the ability to start and stop
92 * underlying drools infrastructure
94 protected volatile boolean locked = false;
97 * list of topics, each with associated decoder classes, each
98 * with a list of associated filters.
100 protected List<TopicCoderFilterConfiguration> decoderConfigurations;
103 * list of topics, each with associated encoder classes, each
104 * with a list of associated filters.
106 protected List<TopicCoderFilterConfiguration> encoderConfigurations;
109 * recent source events processed.
111 protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<>(10);
114 * recent sink events processed.
116 protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
119 * original Drools Model/Rules classloader hash.
121 protected int modelClassLoaderHash;
124 * Expanded version of the constructor.
126 * @param groupId maven group id
127 * @param artifactId maven artifact id
128 * @param version maven version
129 * @param decoderConfigurations list of topic -> decoders -> filters mapping
130 * @param encoderConfigurations list of topic -> encoders -> filters mapping
132 * @throws IllegalArgumentException invalid arguments passed in
134 public MavenDroolsController(String groupId,
137 List<TopicCoderFilterConfiguration> decoderConfigurations,
138 List<TopicCoderFilterConfiguration> encoderConfigurations) {
140 logger.info("drools-controller instantiation [{}:{}:{}]", groupId, artifactId, version);
142 if (groupId == null || groupId.isEmpty()) {
143 throw new IllegalArgumentException("Missing maven group-id coordinate");
146 if (artifactId == null || artifactId.isEmpty()) {
147 throw new IllegalArgumentException("Missing maven artifact-id coordinate");
150 if (version == null || version.isEmpty()) {
151 throw new IllegalArgumentException("Missing maven version coordinate");
154 this.policyContainer = makePolicyContainer(groupId, artifactId, version);
155 this.init(decoderConfigurations, encoderConfigurations);
157 logger.debug("{}: instantiation completed ", this);
161 * init encoding/decoding configuration.
163 * @param decoderConfigurations list of topic -> decoders -> filters mapping
164 * @param encoderConfigurations list of topic -> encoders -> filters mapping
166 protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations,
167 List<TopicCoderFilterConfiguration> encoderConfigurations) {
169 this.decoderConfigurations = decoderConfigurations;
170 this.encoderConfigurations = encoderConfigurations;
172 this.initCoders(decoderConfigurations, true);
173 this.initCoders(encoderConfigurations, false);
175 this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode();
179 public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
180 List<TopicCoderFilterConfiguration> decoderConfigurations,
181 List<TopicCoderFilterConfiguration> encoderConfigurations)
182 throws LinkageError {
184 logger.info("updating version -> [{}:{}:{}]", newGroupId, newArtifactId, newVersion);
186 validateText(newGroupId, "Missing maven group-id coordinate");
187 validateText(newArtifactId, "Missing maven artifact-id coordinate");
188 validateText(newVersion, "Missing maven version coordinate");
190 validateHasBrain(newGroupId, newArtifactId, newVersion);
192 if (newGroupId.equalsIgnoreCase(this.getGroupId())
193 && newArtifactId.equalsIgnoreCase(this.getArtifactId())
194 && newVersion.equalsIgnoreCase(this.getVersion())) {
195 logger.warn("All in the right version: {}:{}:{} vs. {}", newGroupId, newArtifactId, newVersion, this);
199 validateNewVersion(newGroupId, newArtifactId, newVersion);
202 String messages = this.policyContainer.updateToVersion(newVersion);
203 logger.warn("{} UPGRADE results: {}", this, messages);
206 * If all sucessful (can load new container), now we can remove all coders from previous sessions
213 this.init(decoderConfigurations, encoderConfigurations);
215 logger.info("UPDATE-TO-VERSION: completed {}", this);
218 private void validateText(String text, String errorMessage) {
219 if (text == null || text.isEmpty()) {
220 throw new IllegalArgumentException(errorMessage);
224 private void validateHasBrain(String newGroupId, String newArtifactId, String newVersion) {
225 if (newGroupId.equalsIgnoreCase(DroolsControllerConstants.NO_GROUP_ID)
226 || newArtifactId.equalsIgnoreCase(DroolsControllerConstants.NO_ARTIFACT_ID)
227 || newVersion.equalsIgnoreCase(DroolsControllerConstants.NO_VERSION)) {
228 throw new IllegalArgumentException("BRAINLESS maven coordinates provided: "
229 + newGroupId + ":" + newArtifactId + ":"
234 private void validateNewVersion(String newGroupId, String newArtifactId, String newVersion) {
235 if (!newGroupId.equalsIgnoreCase(this.getGroupId())
236 || !newArtifactId.equalsIgnoreCase(this.getArtifactId())) {
237 throw new IllegalArgumentException(
238 "Group ID and Artifact ID maven coordinates must be identical for the upgrade: "
239 + newGroupId + ":" + newArtifactId + ":"
240 + newVersion + " vs. " + this);
245 * initialize decoders for all the topics supported by this controller
246 * Note this is critical to be done after the Policy Container is
247 * instantiated to be able to fetch the corresponding classes.
249 * @param coderConfigurations list of topic -> decoders -> filters mapping
251 protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations,
254 logger.info("INIT-CODERS: {}", this);
256 if (coderConfigurations == null) {
261 for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) {
262 String topic = coderConfig.getTopic();
264 var customGsonCoder = getCustomCoder(coderConfig);
266 List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters();
267 if (coderFilters == null || coderFilters.isEmpty()) {
271 for (PotentialCoderFilter coderFilter : coderFilters) {
272 String potentialCodedClass = coderFilter.getCodedClass();
273 JsonProtocolFilter protocolFilter = coderFilter.getFilter();
275 if (!isClass(potentialCodedClass)) {
276 throw makeRetrieveEx(potentialCodedClass);
278 logClassFetched(potentialCodedClass);
282 getCoderManager().addDecoder(EventProtocolParams.builder()
283 .groupId(this.getGroupId())
284 .artifactId(this.getArtifactId())
286 .eventClass(potentialCodedClass)
287 .protocolFilter(protocolFilter)
288 .customGsonCoder(customGsonCoder)
289 .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()));
291 getCoderManager().addEncoder(
292 EventProtocolParams.builder().groupId(this.getGroupId())
293 .artifactId(this.getArtifactId()).topic(topic)
294 .eventClass(potentialCodedClass).protocolFilter(protocolFilter)
295 .customGsonCoder(customGsonCoder)
296 .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()));
302 private CustomGsonCoder getCustomCoder(TopicCoderFilterConfiguration coderConfig) {
303 var customGsonCoder = coderConfig.getCustomGsonCoder();
304 if (customGsonCoder != null
305 && customGsonCoder.getClassContainer() != null
306 && !customGsonCoder.getClassContainer().isEmpty()) {
308 String customGsonCoderClass = customGsonCoder.getClassContainer();
309 if (!isClass(customGsonCoderClass)) {
310 throw makeRetrieveEx(customGsonCoderClass);
312 logClassFetched(customGsonCoderClass);
315 return customGsonCoder;
319 * Logs an error and makes an exception for an item that cannot be retrieved.
320 * @param itemName the item to retrieve
321 * @return a new exception
323 private IllegalArgumentException makeRetrieveEx(String itemName) {
324 logger.error("{} cannot be retrieved", itemName);
325 return new IllegalArgumentException(itemName + " cannot be retrieved");
329 * Logs the name of the class that was fetched.
330 * @param className class name fetched
332 private void logClassFetched(String className) {
333 logger.info("CLASS FETCHED {}", className);
340 protected void removeDecoders() {
341 logger.info("REMOVE-DECODERS: {}", this);
343 if (this.decoderConfigurations == null) {
348 for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
349 String topic = coderConfig.getTopic();
350 getCoderManager().removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
357 protected void removeEncoders() {
359 logger.info("REMOVE-ENCODERS: {}", this);
361 if (this.encoderConfigurations == null) {
365 for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
366 String topic = coderConfig.getTopic();
367 getCoderManager().removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
373 public boolean ownsCoder(Class<?> coderClass, int modelHash) {
374 if (!isClass(coderClass.getName())) {
375 logger.error("{}{} cannot be retrieved. ", this, coderClass.getName());
379 if (modelHash == this.modelClassLoaderHash) {
380 logger.info("{}{} class loader matches original drools controller rules classloader {}",
381 coderClass.getName(), this, coderClass.getClassLoader());
384 logger.warn("{}{} class loaders don't match {} vs {}", this, coderClass.getName(),
385 coderClass.getClassLoader(), this.policyContainer.getClassLoader());
391 public boolean start() {
393 logger.info("START: {}", this);
395 synchronized (this) {
402 return this.policyContainer.start();
406 public boolean stop() {
408 logger.info("STOP: {}", this);
410 synchronized (this) {
417 return this.policyContainer.stop();
421 public void shutdown() {
422 logger.info("{}: SHUTDOWN", this);
427 } catch (Exception e) {
428 logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
430 this.policyContainer.shutdown();
437 logger.info("{}: HALT", this);
442 } catch (Exception e) {
443 logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
445 this.policyContainer.destroy();
450 * removes this drools controllers and encoders and decoders from operation.
452 protected void removeCoders() {
453 logger.info("{}: REMOVE-CODERS", this);
456 this.removeDecoders();
457 } catch (IllegalArgumentException e) {
458 logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
462 this.removeEncoders();
463 } catch (IllegalArgumentException e) {
464 logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
469 public boolean isAlive() {
474 public boolean offer(String topic, String event) {
475 logger.debug("{}: OFFER raw event from {}", this, topic);
477 if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
481 // 1. Now, check if this topic has a decoder:
483 if (!getCoderManager().isDecodingSupported(this.getGroupId(),
484 this.getArtifactId(),
487 logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this, // NOSONAR
488 topic, this.getGroupId(), this.getArtifactId());
496 anEvent = getCoderManager().decode(this.getGroupId(),
497 this.getArtifactId(),
500 } catch (UnsupportedOperationException uoe) {
501 logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
502 event, uoe.getMessage(), uoe);
504 } catch (Exception e) {
505 logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
506 event, e.getMessage(), e);
510 return offer(anEvent);
515 * This method always returns "true", which causes a sonar complaint. However,
516 * refactoring or restructuring it would unnecessarily complicate it, thus we'll just
517 * disable the sonar complaint.
520 public <T> boolean offer(T event) { // NOSONAR
521 logger.debug("{}: OFFER event", this);
523 if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
527 synchronized (this.recentSourceEvents) {
528 this.recentSourceEvents.add(event);
531 PdpJmx.getInstance().updateOccured();
535 if (FeatureApiUtils.apply(getDroolsProviders().getList(),
536 feature -> feature.beforeInsert(this, event),
537 (feature, ex) -> logger.error("{}: feature {} before-insert failure because of {}", this,
538 feature.getClass().getName(), ex.getMessage(), ex))) {
542 boolean successInject = this.policyContainer.insertAll(event);
543 if (!successInject) {
544 logger.warn("{} Failed to inject into PolicyContainer {}", this, this.getSessionNames());
547 FeatureApiUtils.apply(getDroolsProviders().getList(),
548 feature -> feature.afterInsert(this, event, successInject),
549 (feature, ex) -> logger.error("{}: feature {} after-insert failure because of {}", this,
550 feature.getClass().getName(), ex.getMessage(), ex));
557 public boolean deliver(TopicSink sink, Object event) {
559 logger.info("{}DELIVER: {} FROM {} TO {}", this, event, this, sink);
561 for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
563 if (feature.beforeDeliver(this, sink, event)) {
566 } catch (Exception e) {
567 logger.error("{}: feature {} before-deliver failure because of {}", this, feature.getClass().getName(),
573 throw new IllegalArgumentException(this + " invalid sink");
577 throw new IllegalArgumentException(this + " invalid event");
581 throw new IllegalStateException(this + " is locked");
585 throw new IllegalStateException(this + " is stopped");
589 getCoderManager().encode(sink.getTopic(), event, this);
591 synchronized (this.recentSinkEvents) {
592 this.recentSinkEvents.add(json);
595 boolean success = sink.send(json);
597 for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
599 if (feature.afterDeliver(this, sink, event, json, success)) {
602 } catch (Exception e) {
603 logger.error("{}: feature {} after-deliver failure because of {}", this, feature.getClass().getName(),
613 public String getVersion() {
614 return this.policyContainer.getVersion();
618 public String getArtifactId() {
619 return this.policyContainer.getArtifactId();
623 public String getGroupId() {
624 return this.policyContainer.getGroupId();
628 * Get model class loader hash.
630 * @return the modelClassLoaderHash
632 public int getModelClassLoaderHash() {
633 return modelClassLoaderHash;
637 public synchronized boolean lock() {
638 logger.info("LOCK: {}", this);
645 public synchronized boolean unlock() {
646 logger.info("UNLOCK: {}", this);
653 public boolean isLocked() {
659 public PolicyContainer getContainer() {
660 return this.policyContainer;
663 @GsonJsonProperty("sessions")
665 public List<String> getSessionNames() {
666 return getSessionNames(true);
672 * @param abbreviated true for the short form, otherwise the long form
673 * @return session names
675 protected List<String> getSessionNames(boolean abbreviated) {
676 List<String> sessionNames = new ArrayList<>();
678 for (PolicySession session: this.policyContainer.getPolicySessions()) {
680 sessionNames.add(session.getName());
682 sessionNames.add(session.getFullName());
685 } catch (Exception e) {
686 logger.warn("Can't retrieve CORE sessions", e);
687 sessionNames.add(e.getMessage());
692 @GsonJsonProperty("sessionCoordinates")
694 public List<String> getCanonicalSessionNames() {
695 return getSessionNames(false);
699 public List<String> getBaseDomainNames() {
700 return new ArrayList<>(this.policyContainer.getKieContainer().getKieBaseNames());
704 * provides the underlying core layer container sessions.
706 * @return the attached Policy Container
708 protected List<PolicySession> getSessions() {
709 List<PolicySession> sessions = new ArrayList<>();
710 sessions.addAll(this.policyContainer.getPolicySessions());
715 * provides the underlying core layer container session with name sessionName.
717 * @param sessionName session name
718 * @return the attached Policy Container
719 * @throws IllegalArgumentException when an invalid session name is provided
720 * @throws IllegalStateException when the drools controller is in an invalid state
722 protected PolicySession getSession(String sessionName) {
723 if (sessionName == null || sessionName.isEmpty()) {
724 throw new IllegalArgumentException("A Session Name must be provided");
727 List<PolicySession> sessions = this.getSessions();
728 for (PolicySession session : sessions) {
729 if (sessionName.equals(session.getName()) || sessionName.equals(session.getFullName())) {
734 throw invalidSessNameEx(sessionName);
737 private IllegalArgumentException invalidSessNameEx(String sessionName) {
738 return new IllegalArgumentException("Invalid Session Name: " + sessionName);
742 public Map<String, Integer> factClassNames(String sessionName) {
743 validateSessionName(sessionName);
745 Map<String, Integer> classNames = new HashMap<>();
747 var session = getSession(sessionName);
748 var kieSession = session.getKieSession();
750 Collection<FactHandle> facts = kieSession.getFactHandles();
751 for (FactHandle fact : facts) {
753 String className = kieSession.getObject(fact).getClass().getName();
754 if (classNames.containsKey(className)) {
755 classNames.put(className, classNames.get(className) + 1);
757 classNames.put(className, 1);
759 } catch (Exception e) {
760 logger.warn(FACT_RETRIEVE_ERROR, fact, e);
767 private void validateSessionName(String sessionName) {
768 if (sessionName == null || sessionName.isEmpty()) {
769 throw invalidSessNameEx(sessionName);
774 public long factCount(String sessionName) {
775 validateSessionName(sessionName);
777 return getSession(sessionName).getKieSession().getFactCount();
781 public List<Object> facts(String sessionName, String className, boolean delete) {
782 validateSessionName(sessionName);
784 if (className == null || className.isEmpty()) {
785 throw new IllegalArgumentException("Invalid Class Name: " + className);
789 ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
790 if (factClass == null) {
791 throw new IllegalArgumentException("Class cannot be fetched in model's classloader: " + className);
794 var session = getSession(sessionName);
795 var kieSession = session.getKieSession();
797 List<Object> factObjects = new ArrayList<>();
799 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(factClass));
800 for (FactHandle factHandle : factHandles) {
802 factObjects.add(kieSession.getObject(factHandle));
804 kieSession.delete(factHandle);
806 } catch (Exception e) {
807 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
815 public <T> List<T> facts(@NonNull String sessionName, @NonNull Class<T> clazz) {
816 return facts(sessionName, clazz.getName(), false)
818 .filter(clazz::isInstance)
820 .collect(Collectors.toList());
824 public List<Object> factQuery(String sessionName, String queryName, String queriedEntity,
825 boolean delete, Object... queryParams) {
826 validateSessionName(sessionName);
828 if (queryName == null || queryName.isEmpty()) {
829 throw new IllegalArgumentException("Invalid Query Name: " + queryName);
832 if (queriedEntity == null || queriedEntity.isEmpty()) {
833 throw new IllegalArgumentException("Invalid Queried Entity: " + queriedEntity);
836 var session = getSession(sessionName);
837 var kieSession = session.getKieSession();
839 validateQueryName(kieSession, queryName);
841 List<Object> factObjects = new ArrayList<>();
843 var queryResults = kieSession.getQueryResults(queryName, queryParams);
844 for (QueryResultsRow row : queryResults) {
846 factObjects.add(row.get(queriedEntity));
848 kieSession.delete(row.getFactHandle(queriedEntity));
850 } catch (Exception e) {
851 logger.warn("Object cannot be retrieved from row: {}", row, e);
858 private void validateQueryName(KieSession kieSession, String queryName) {
859 for (KiePackage kiePackage : kieSession.getKieBase().getKiePackages()) {
860 for (Query q : kiePackage.getQueries()) {
861 if (q.getName() != null && q.getName().equals(queryName)) {
867 throw new IllegalArgumentException("Invalid Query Name: " + queryName);
871 public <T> boolean delete(@NonNull String sessionName, @NonNull T objFact) {
872 var kieSession = getSession(sessionName).getKieSession();
874 // try first to get the object to delete first by reference
876 var quickFact = kieSession.getFactHandle(objFact);
877 if (quickFact != null) {
878 logger.info("Fast delete of {} from {}", objFact, sessionName);
879 kieSession.delete(quickFact);
883 // otherwise, try to the delete the fact associated with the object by scanning all
884 // facts from the same type and performing object equality. The set of facts
885 // is restricted to those of the same type
887 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(objFact.getClass()));
888 for (FactHandle factHandle : factHandles) {
889 if (Objects.equals(objFact, kieSession.getObject(factHandle))) {
890 logger.info("Slow delete of {} of type {} from {}",
891 objFact, objFact.getClass().getName(), sessionName);
892 kieSession.delete(factHandle);
901 public <T> boolean delete(@NonNull T fact) {
902 return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
906 public <T> boolean delete(@NonNull String sessionName, @NonNull Class<T> fact) {
907 var session = getSession(sessionName);
908 var kieSession = session.getKieSession();
911 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(fact));
912 for (FactHandle factHandle : factHandles) {
914 kieSession.delete(factHandle);
915 } catch (Exception e) {
916 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
924 public <T> boolean delete(@NonNull Class<T> fact) {
925 return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
929 public <T> boolean exists(@NonNull String sessionName, @NonNull T objFact) {
930 var kieSession = getSession(sessionName).getKieSession();
931 if (kieSession.getFactHandle(objFact) != null) {
935 // try to find the object by equality comparison instead if it could not be
936 // found by reference
938 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(objFact.getClass()));
939 for (FactHandle factHandle : factHandles) {
940 if (Objects.equals(objFact, kieSession.getObject(factHandle))) {
949 public <T> boolean exists(@NonNull T fact) {
950 return this.getSessionNames().stream().anyMatch(ss -> exists(ss, fact));
954 public Class<?> fetchModelClass(String className) {
955 return ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
959 * Get recent source events.
961 * @return the recentSourceEvents
964 public Object[] getRecentSourceEvents() {
965 synchronized (this.recentSourceEvents) {
966 var events = new Object[recentSourceEvents.size()];
967 return recentSourceEvents.toArray(events);
972 * Get recent sink events.
974 * @return the recentSinkEvents
977 public String[] getRecentSinkEvents() {
978 synchronized (this.recentSinkEvents) {
979 var events = new String[recentSinkEvents.size()];
980 return recentSinkEvents.toArray(events);
985 public boolean isBrained() {
991 public String toString() {
992 return "MavenDroolsController [policyContainer=" + policyContainer.getName() + ":" + ", alive=" + alive
993 + ", locked=" + ", modelClassLoaderHash=" + modelClassLoaderHash + "]";
996 // these may be overridden by junit tests
998 protected EventProtocolCoder getCoderManager() {
999 return EventProtocolCoderConstants.getManager();
1002 protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
1003 return DroolsControllerFeatureApiConstants.getProviders();
1006 protected PolicyContainer makePolicyContainer(String groupId, String artifactId, String version) {
1007 return new PolicyContainer(groupId, artifactId, version);
1010 protected boolean isClass(String className) {
1011 return ReflectionUtil.isClass(this.policyContainer.getClassLoader(), className);