2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023 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.internal;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Objects;
30 import java.util.stream.Collectors;
32 import lombok.NonNull;
33 import org.apache.commons.collections4.queue.CircularFifoQueue;
34 import org.drools.core.ClassObjectFilter;
35 import org.kie.api.definition.KiePackage;
36 import org.kie.api.definition.rule.Query;
37 import org.kie.api.runtime.KieSession;
38 import org.kie.api.runtime.rule.FactHandle;
39 import org.kie.api.runtime.rule.QueryResultsRow;
40 import org.onap.policy.common.endpoints.event.comm.TopicSink;
41 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
42 import org.onap.policy.common.gson.annotation.GsonJsonProperty;
43 import org.onap.policy.common.utils.services.FeatureApiUtils;
44 import org.onap.policy.common.utils.services.OrderedServiceImpl;
45 import org.onap.policy.drools.controller.DroolsController;
46 import org.onap.policy.drools.controller.DroolsControllerConstants;
47 import org.onap.policy.drools.core.PolicyContainer;
48 import org.onap.policy.drools.core.PolicySession;
49 import org.onap.policy.drools.core.jmx.PdpJmx;
50 import org.onap.policy.drools.features.DroolsControllerFeatureApi;
51 import org.onap.policy.drools.features.DroolsControllerFeatureApiConstants;
52 import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
53 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
54 import org.onap.policy.drools.protocol.coders.EventProtocolParams;
55 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
56 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
57 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
58 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
59 import org.onap.policy.drools.utils.ReflectionUtil;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 * Maven-based Drools Controller that interacts with the
65 * policy-core PolicyContainer and PolicySession to manage
66 * Drools containers instantiated using Maven.
68 public class MavenDroolsController implements DroolsController {
70 private static final String FACT_RETRIEVE_ERROR = "Object cannot be retrieved from fact {}";
75 private static final Logger logger = LoggerFactory.getLogger(MavenDroolsController.class);
78 * Policy Container, the access object to the policy-core layer.
81 protected final PolicyContainer policyContainer;
84 * alive status of this drools controller,
85 * reflects invocation of start()/stop() only.
87 protected volatile boolean alive = false;
90 * locked status of this drools controller,
91 * reflects if i/o drools related operations are permitted,
92 * more specifically: offer() and deliver().
93 * It does not affect the ability to start and stop
94 * underlying drools infrastructure
96 protected volatile boolean locked = false;
99 * list of topics, each with associated decoder classes, each
100 * with a list of associated filters.
102 protected List<TopicCoderFilterConfiguration> decoderConfigurations;
105 * list of topics, each with associated encoder classes, each
106 * with a list of associated filters.
108 protected List<TopicCoderFilterConfiguration> encoderConfigurations;
111 * recent source events processed.
113 protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<>(10);
116 * recent sink events processed.
118 protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
121 * original Drools Model/Rules classloader hash.
124 protected int modelClassLoaderHash;
127 * Expanded version of the constructor.
129 * @param groupId maven group id
130 * @param artifactId maven artifact id
131 * @param version maven version
132 * @param decoderConfigurations list of topic -> decoders -> filters mapping
133 * @param encoderConfigurations list of topic -> encoders -> filters mapping
135 * @throws IllegalArgumentException invalid arguments passed in
137 public MavenDroolsController(String groupId,
140 List<TopicCoderFilterConfiguration> decoderConfigurations,
141 List<TopicCoderFilterConfiguration> encoderConfigurations) {
143 logger.info("drools-controller instantiation [{}:{}:{}]", groupId, artifactId, version);
145 if (groupId == null || groupId.isEmpty()) {
146 throw new IllegalArgumentException("Missing maven group-id coordinate");
149 if (artifactId == null || artifactId.isEmpty()) {
150 throw new IllegalArgumentException("Missing maven artifact-id coordinate");
153 if (version == null || version.isEmpty()) {
154 throw new IllegalArgumentException("Missing maven version coordinate");
157 this.policyContainer = makePolicyContainer(groupId, artifactId, version);
158 this.init(decoderConfigurations, encoderConfigurations);
160 logger.debug("{}: instantiation completed ", this);
164 * init encoding/decoding configuration.
166 * @param decoderConfigurations list of topic -> decoders -> filters mapping
167 * @param encoderConfigurations list of topic -> encoders -> filters mapping
169 protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations,
170 List<TopicCoderFilterConfiguration> encoderConfigurations) {
172 this.decoderConfigurations = decoderConfigurations;
173 this.encoderConfigurations = encoderConfigurations;
175 this.initCoders(decoderConfigurations, true);
176 this.initCoders(encoderConfigurations, false);
178 this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode();
182 public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
183 List<TopicCoderFilterConfiguration> decoderConfigurations,
184 List<TopicCoderFilterConfiguration> encoderConfigurations)
185 throws LinkageError {
187 logger.info("updating version -> [{}:{}:{}]", newGroupId, newArtifactId, newVersion);
189 validateText(newGroupId, "Missing maven group-id coordinate");
190 validateText(newArtifactId, "Missing maven artifact-id coordinate");
191 validateText(newVersion, "Missing maven version coordinate");
193 validateHasBrain(newGroupId, newArtifactId, newVersion);
195 if (newGroupId.equalsIgnoreCase(this.getGroupId())
196 && newArtifactId.equalsIgnoreCase(this.getArtifactId())
197 && newVersion.equalsIgnoreCase(this.getVersion())) {
198 logger.warn("All in the right version: {}:{}:{} vs. {}", newGroupId, newArtifactId, newVersion, this);
202 validateNewVersion(newGroupId, newArtifactId, newVersion);
205 String messages = this.policyContainer.updateToVersion(newVersion);
206 logger.warn("{} UPGRADE results: {}", this, messages);
209 * If all successful (can load new container), now we can remove all coders from previous sessions
216 this.init(decoderConfigurations, encoderConfigurations);
218 logger.info("UPDATE-TO-VERSION: completed {}", this);
221 private void validateText(String text, String errorMessage) {
222 if (text == null || text.isEmpty()) {
223 throw new IllegalArgumentException(errorMessage);
227 private void validateHasBrain(String newGroupId, String newArtifactId, String newVersion) {
228 if (newGroupId.equalsIgnoreCase(DroolsControllerConstants.NO_GROUP_ID)
229 || newArtifactId.equalsIgnoreCase(DroolsControllerConstants.NO_ARTIFACT_ID)
230 || newVersion.equalsIgnoreCase(DroolsControllerConstants.NO_VERSION)) {
231 throw new IllegalArgumentException("BRAINLESS maven coordinates provided: "
232 + newGroupId + ":" + newArtifactId + ":"
237 private void validateNewVersion(String newGroupId, String newArtifactId, String newVersion) {
238 if (!newGroupId.equalsIgnoreCase(this.getGroupId())
239 || !newArtifactId.equalsIgnoreCase(this.getArtifactId())) {
240 throw new IllegalArgumentException(
241 "Group ID and Artifact ID maven coordinates must be identical for the upgrade: "
242 + newGroupId + ":" + newArtifactId + ":"
243 + newVersion + " vs. " + this);
248 * initialize decoders for all the topics supported by this controller
249 * Note this is critical to be done after the Policy Container is
250 * instantiated to be able to fetch the corresponding classes.
252 * @param coderConfigurations list of topic -> decoders -> filters mapping
254 protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations,
257 logger.info("INIT-CODERS: {}", this);
259 if (coderConfigurations == null) {
264 for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) {
265 String topic = coderConfig.getTopic();
267 var customGsonCoder = getCustomCoder(coderConfig);
269 List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters();
270 if (coderFilters == null || coderFilters.isEmpty()) {
274 for (PotentialCoderFilter coderFilter : coderFilters) {
275 String potentialCodedClass = coderFilter.getCodedClass();
276 JsonProtocolFilter protocolFilter = coderFilter.getFilter();
278 if (isNotAClass(potentialCodedClass)) {
279 throw makeRetrieveEx(potentialCodedClass);
281 logClassFetched(potentialCodedClass);
285 getCoderManager().addDecoder(EventProtocolParams.builder()
286 .groupId(this.getGroupId())
287 .artifactId(this.getArtifactId())
289 .eventClass(potentialCodedClass)
290 .protocolFilter(protocolFilter)
291 .customGsonCoder(customGsonCoder)
292 .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()).build());
294 getCoderManager().addEncoder(
295 EventProtocolParams.builder().groupId(this.getGroupId())
296 .artifactId(this.getArtifactId()).topic(topic)
297 .eventClass(potentialCodedClass).protocolFilter(protocolFilter)
298 .customGsonCoder(customGsonCoder)
299 .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()).build());
305 private CustomGsonCoder getCustomCoder(TopicCoderFilterConfiguration coderConfig) {
306 var customGsonCoder = coderConfig.getCustomGsonCoder();
307 if (customGsonCoder != null
308 && customGsonCoder.getClassContainer() != null
309 && !customGsonCoder.getClassContainer().isEmpty()) {
311 String customGsonCoderClass = customGsonCoder.getClassContainer();
312 if (isNotAClass(customGsonCoderClass)) {
313 throw makeRetrieveEx(customGsonCoderClass);
315 logClassFetched(customGsonCoderClass);
318 return customGsonCoder;
322 * Logs an error and makes an exception for an item that cannot be retrieved.
323 * @param itemName the item to retrieve
324 * @return a new exception
326 private IllegalArgumentException makeRetrieveEx(String itemName) {
327 logger.error("{} cannot be retrieved", itemName);
328 return new IllegalArgumentException(itemName + " cannot be retrieved");
332 * Logs the name of the class that was fetched.
333 * @param className class name fetched
335 private void logClassFetched(String className) {
336 logger.info("CLASS FETCHED {}", className);
343 protected void removeDecoders() {
344 logger.info("REMOVE-DECODERS: {}", this);
346 if (this.decoderConfigurations == null) {
351 for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
352 String topic = coderConfig.getTopic();
353 getCoderManager().removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
360 protected void removeEncoders() {
362 logger.info("REMOVE-ENCODERS: {}", this);
364 if (this.encoderConfigurations == null) {
368 for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
369 String topic = coderConfig.getTopic();
370 getCoderManager().removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
376 public boolean ownsCoder(Class<?> coderClass, int modelHash) {
377 if (isNotAClass(coderClass.getName())) {
378 logger.error("{}{} cannot be retrieved. ", this, coderClass.getName());
382 if (modelHash == this.modelClassLoaderHash) {
383 logger.info("{}{} class loader matches original drools controller rules classloader {}",
384 coderClass.getName(), this, coderClass.getClassLoader());
387 logger.warn("{}{} class loaders don't match {} vs {}", this, coderClass.getName(),
388 coderClass.getClassLoader(), this.policyContainer.getClassLoader());
394 public boolean start() {
396 logger.info("START: {}", this);
398 synchronized (this) {
405 return this.policyContainer.start();
409 public boolean stop() {
411 logger.info("STOP: {}", this);
413 synchronized (this) {
420 return this.policyContainer.stop();
424 public void shutdown() {
425 logger.info("{}: SHUTDOWN", this);
430 } catch (Exception e) {
431 logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
433 this.policyContainer.shutdown();
440 logger.info("{}: HALT", this);
445 } catch (Exception e) {
446 logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
448 this.policyContainer.destroy();
453 * removes this drools controllers and encoders and decoders from operation.
455 protected void removeCoders() {
456 logger.info("{}: REMOVE-CODERS", this);
459 this.removeDecoders();
460 } catch (IllegalArgumentException e) {
461 logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
465 this.removeEncoders();
466 } catch (IllegalArgumentException e) {
467 logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
472 public boolean isAlive() {
477 public boolean offer(String topic, String event) {
478 logger.debug("{}: OFFER raw event from {}", this, topic);
480 if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
484 // 1. Now, check if this topic has a decoder:
486 if (!getCoderManager().isDecodingSupported(this.getGroupId(),
487 this.getArtifactId(),
490 logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this, // NOSONAR
491 topic, this.getGroupId(), this.getArtifactId());
499 anEvent = getCoderManager().decode(this.getGroupId(),
500 this.getArtifactId(),
503 } catch (UnsupportedOperationException uoe) {
504 logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
505 event, uoe.getMessage(), uoe);
507 } catch (Exception e) {
508 logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
509 event, e.getMessage(), e);
513 return offer(anEvent);
518 * This method always returns "true", which causes a sonar complaint. However,
519 * refactoring or restructuring it would unnecessarily complicate it, thus we'll just
520 * disable the sonar complaint.
523 public <T> boolean offer(T event) { // NOSONAR
524 logger.debug("{}: OFFER event", this);
526 if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
530 synchronized (this.recentSourceEvents) {
531 this.recentSourceEvents.add(event);
534 PdpJmx.getInstance().updateOccurred();
538 if (FeatureApiUtils.apply(getDroolsProviders().getList(),
539 feature -> feature.beforeInsert(this, event),
540 (feature, ex) -> logger.error("{}: feature {} before-insert failure because of {}", this,
541 feature.getClass().getName(), ex.getMessage(), ex))) {
545 boolean successInject = this.policyContainer.insertAll(event);
546 if (!successInject) {
547 logger.warn("{} Failed to inject into PolicyContainer {}", this, this.getSessionNames());
550 FeatureApiUtils.apply(getDroolsProviders().getList(),
551 feature -> feature.afterInsert(this, event, successInject),
552 (feature, ex) -> logger.error("{}: feature {} after-insert failure because of {}", this,
553 feature.getClass().getName(), ex.getMessage(), ex));
560 public boolean deliver(TopicSink sink, Object event) {
562 logger.info("{}DELIVER: {} FROM {} TO {}", this, event, this, sink);
564 for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
566 if (feature.beforeDeliver(this, sink, event)) {
569 } catch (Exception e) {
570 logger.error("{}: feature {} before-deliver failure because of {}", this, feature.getClass().getName(),
576 throw new IllegalArgumentException(this + " invalid sink");
580 throw new IllegalArgumentException(this + " invalid event");
584 throw new IllegalStateException(this + " is locked");
588 throw new IllegalStateException(this + " is stopped");
592 getCoderManager().encode(sink.getTopic(), event, this);
594 synchronized (this.recentSinkEvents) {
595 this.recentSinkEvents.add(json);
598 boolean success = sink.send(json);
600 for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
602 if (feature.afterDeliver(this, sink, event, json, success)) {
605 } catch (Exception e) {
606 logger.error("{}: feature {} after-deliver failure because of {}", this, feature.getClass().getName(),
616 public String getVersion() {
617 return this.policyContainer.getVersion();
621 public String getArtifactId() {
622 return this.policyContainer.getArtifactId();
626 public String getGroupId() {
627 return this.policyContainer.getGroupId();
631 public synchronized boolean lock() {
632 logger.info("LOCK: {}", this);
639 public synchronized boolean unlock() {
640 logger.info("UNLOCK: {}", this);
647 public boolean isLocked() {
653 public PolicyContainer getContainer() {
654 return this.policyContainer;
657 @GsonJsonProperty("sessions")
659 public List<String> getSessionNames() {
660 return getSessionNames(true);
666 * @param abbreviated true for the short form, otherwise the long form
667 * @return session names
669 protected List<String> getSessionNames(boolean abbreviated) {
670 List<String> sessionNames = new ArrayList<>();
672 for (PolicySession session: this.policyContainer.getPolicySessions()) {
674 sessionNames.add(session.getName());
676 sessionNames.add(session.getFullName());
679 } catch (Exception e) {
680 logger.warn("Can't retrieve CORE sessions", e);
681 sessionNames.add(e.getMessage());
686 @GsonJsonProperty("sessionCoordinates")
688 public List<String> getCanonicalSessionNames() {
689 return getSessionNames(false);
693 public List<String> getBaseDomainNames() {
694 return new ArrayList<>(this.policyContainer.getKieContainer().getKieBaseNames());
698 * provides the underlying core layer container sessions.
700 * @return the attached Policy Container
702 protected List<PolicySession> getSessions() {
703 return new ArrayList<>(this.policyContainer.getPolicySessions());
707 * provides the underlying core layer container session with name sessionName.
709 * @param sessionName session name
710 * @return the attached Policy Container
711 * @throws IllegalArgumentException when an invalid session name is provided
712 * @throws IllegalStateException when the drools controller is in an invalid state
714 protected PolicySession getSession(String sessionName) {
715 if (sessionName == null || sessionName.isEmpty()) {
716 throw new IllegalArgumentException("A Session Name must be provided");
719 List<PolicySession> sessions = this.getSessions();
720 for (PolicySession session : sessions) {
721 if (sessionName.equals(session.getName()) || sessionName.equals(session.getFullName())) {
726 throw invalidSessNameEx(sessionName);
729 private IllegalArgumentException invalidSessNameEx(String sessionName) {
730 return new IllegalArgumentException("Invalid Session Name: " + sessionName);
734 public Map<String, Integer> factClassNames(String sessionName) {
735 validateSessionName(sessionName);
737 Map<String, Integer> classNames = new HashMap<>();
739 var session = getSession(sessionName);
740 var kieSession = session.getKieSession();
742 Collection<FactHandle> facts = kieSession.getFactHandles();
743 for (FactHandle fact : facts) {
745 String className = kieSession.getObject(fact).getClass().getName();
746 if (classNames.containsKey(className)) {
747 classNames.put(className, classNames.get(className) + 1);
749 classNames.put(className, 1);
751 } catch (Exception e) {
752 logger.warn(FACT_RETRIEVE_ERROR, fact, e);
759 private void validateSessionName(String sessionName) {
760 if (sessionName == null || sessionName.isEmpty()) {
761 throw invalidSessNameEx(sessionName);
766 public long factCount(String sessionName) {
767 validateSessionName(sessionName);
769 return getSession(sessionName).getKieSession().getFactCount();
773 public List<Object> facts(String sessionName, String className, boolean delete) {
774 validateSessionName(sessionName);
776 if (className == null || className.isEmpty()) {
777 throw new IllegalArgumentException("Invalid Class Name: " + className);
781 ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
782 if (factClass == null) {
783 throw new IllegalArgumentException("Class cannot be fetched in model's classloader: " + className);
786 var session = getSession(sessionName);
787 var kieSession = session.getKieSession();
789 List<Object> factObjects = new ArrayList<>();
791 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(factClass));
792 for (FactHandle factHandle : factHandles) {
794 factObjects.add(kieSession.getObject(factHandle));
796 kieSession.delete(factHandle);
798 } catch (Exception e) {
799 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
807 public <T> List<T> facts(@NonNull String sessionName, @NonNull Class<T> clazz) {
808 return facts(sessionName, clazz.getName(), false)
810 .filter(clazz::isInstance)
812 .collect(Collectors.toList());
816 public List<Object> factQuery(String sessionName, String queryName, String queriedEntity,
817 boolean delete, Object... queryParams) {
818 validateSessionName(sessionName);
820 if (queryName == null || queryName.isEmpty()) {
821 throw new IllegalArgumentException("Invalid Query Name: " + queryName);
824 if (queriedEntity == null || queriedEntity.isEmpty()) {
825 throw new IllegalArgumentException("Invalid Queried Entity: " + queriedEntity);
828 var session = getSession(sessionName);
829 var kieSession = session.getKieSession();
831 validateQueryName(kieSession, queryName);
833 List<Object> factObjects = new ArrayList<>();
835 var queryResults = kieSession.getQueryResults(queryName, queryParams);
836 for (QueryResultsRow row : queryResults) {
838 factObjects.add(row.get(queriedEntity));
840 kieSession.delete(row.getFactHandle(queriedEntity));
842 } catch (Exception e) {
843 logger.warn("Object cannot be retrieved from row: {}", row, e);
850 private void validateQueryName(KieSession kieSession, String queryName) {
851 for (KiePackage kiePackage : kieSession.getKieBase().getKiePackages()) {
852 for (Query q : kiePackage.getQueries()) {
853 if (q.getName() != null && q.getName().equals(queryName)) {
859 throw new IllegalArgumentException("Invalid Query Name: " + queryName);
863 public <T> boolean delete(@NonNull String sessionName, @NonNull T objFact) {
864 var kieSession = getSession(sessionName).getKieSession();
866 // try first to get the object to delete first by reference
868 var quickFact = kieSession.getFactHandle(objFact);
869 if (quickFact != null) {
870 logger.info("Fast delete of {} from {}", objFact, sessionName);
871 kieSession.delete(quickFact);
875 // otherwise, try to the delete the fact associated with the object by scanning all
876 // facts from the same type and performing object equality. The set of facts
877 // is restricted to those of the same type
879 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(objFact.getClass()));
880 for (FactHandle factHandle : factHandles) {
881 if (Objects.equals(objFact, kieSession.getObject(factHandle))) {
882 logger.info("Slow delete of {} of type {} from {}",
883 objFact, objFact.getClass().getName(), sessionName);
884 kieSession.delete(factHandle);
893 public <T> boolean delete(@NonNull T fact) {
894 return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
898 public <T> boolean delete(@NonNull String sessionName, @NonNull Class<T> fact) {
899 var session = getSession(sessionName);
900 var kieSession = session.getKieSession();
903 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(fact));
904 for (FactHandle factHandle : factHandles) {
906 kieSession.delete(factHandle);
907 } catch (Exception e) {
908 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
916 public <T> boolean delete(@NonNull Class<T> fact) {
917 return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
921 public <T> boolean exists(@NonNull String sessionName, @NonNull T objFact) {
922 var kieSession = getSession(sessionName).getKieSession();
923 if (kieSession.getFactHandle(objFact) != null) {
927 // try to find the object by equality comparison instead if it could not be
928 // found by reference
930 Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(objFact.getClass()));
931 for (FactHandle factHandle : factHandles) {
932 if (Objects.equals(objFact, kieSession.getObject(factHandle))) {
941 public <T> boolean exists(@NonNull T fact) {
942 return this.getSessionNames().stream().anyMatch(ss -> exists(ss, fact));
946 public Class<?> fetchModelClass(String className) {
947 return ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
951 * Get recent source events.
953 * @return the recentSourceEvents
956 public Object[] getRecentSourceEvents() {
957 synchronized (this.recentSourceEvents) {
958 var events = new Object[recentSourceEvents.size()];
959 return recentSourceEvents.toArray(events);
964 * Get recent sink events.
966 * @return the recentSinkEvents
969 public String[] getRecentSinkEvents() {
970 synchronized (this.recentSinkEvents) {
971 var events = new String[recentSinkEvents.size()];
972 return recentSinkEvents.toArray(events);
977 public boolean isBrained() {
983 public String toString() {
984 return "MavenDroolsController [policyContainer=" + policyContainer.getName() + ":" + ", alive=" + alive
985 + ", locked=" + ", modelClassLoaderHash=" + modelClassLoaderHash + "]";
988 // these may be overridden by junit tests
990 protected EventProtocolCoder getCoderManager() {
991 return EventProtocolCoderConstants.getManager();
994 protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
995 return DroolsControllerFeatureApiConstants.getProviders();
998 protected PolicyContainer makePolicyContainer(String groupId, String artifactId, String version) {
999 return new PolicyContainer(groupId, artifactId, version);
1002 protected boolean isNotAClass(String className) {
1003 return !ReflectionUtil.isClass(this.policyContainer.getClassLoader(), className);