Removing deprecated DMAAP library
[policy/drools-pdp.git] / policy-management / src / main / java / org / onap / policy / drools / controller / internal / MavenDroolsController.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  */
21
22 package org.onap.policy.drools.controller.internal;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.stream.Collectors;
31 import lombok.Getter;
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;
62
63 /**
64  * Maven-based Drools Controller that interacts with the
65  * policy-core PolicyContainer and PolicySession to manage
66  * Drools containers instantiated using Maven.
67  */
68 public class MavenDroolsController implements DroolsController {
69
70     private static final String FACT_RETRIEVE_ERROR = "Object cannot be retrieved from fact {}";
71
72     /**
73      * logger.
74      */
75     private static final Logger logger = LoggerFactory.getLogger(MavenDroolsController.class);
76
77     /**
78      * Policy Container, the access object to the policy-core layer.
79      */
80     @GsonJsonIgnore
81     protected final PolicyContainer policyContainer;
82
83     /**
84      * alive status of this drools controller,
85      * reflects invocation of start()/stop() only.
86      */
87     protected volatile boolean alive = false;
88
89     /**
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
95      */
96     protected volatile boolean locked = false;
97
98     /**
99      * list of topics, each with associated decoder classes, each
100      * with a list of associated filters.
101      */
102     protected List<TopicCoderFilterConfiguration> decoderConfigurations;
103
104     /**
105      * list of topics, each with associated encoder classes, each
106      * with a list of associated filters.
107      */
108     protected List<TopicCoderFilterConfiguration> encoderConfigurations;
109
110     /**
111      * recent source events processed.
112      */
113     protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<>(10);
114
115     /**
116      * recent sink events processed.
117      */
118     protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
119
120     /**
121      * original Drools Model/Rules classloader hash.
122      */
123     @Getter
124     protected int modelClassLoaderHash;
125
126     /**
127      * Expanded version of the constructor.
128      *
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
134      *
135      * @throws IllegalArgumentException invalid arguments passed in
136      */
137     public MavenDroolsController(String groupId,
138             String artifactId,
139             String version,
140             List<TopicCoderFilterConfiguration> decoderConfigurations,
141             List<TopicCoderFilterConfiguration> encoderConfigurations) {
142
143         logger.info("drools-controller instantiation [{}:{}:{}]", groupId, artifactId, version);
144
145         if (groupId == null || groupId.isEmpty()) {
146             throw new IllegalArgumentException("Missing maven group-id coordinate");
147         }
148
149         if (artifactId == null || artifactId.isEmpty()) {
150             throw new IllegalArgumentException("Missing maven artifact-id coordinate");
151         }
152
153         if (version == null || version.isEmpty()) {
154             throw new IllegalArgumentException("Missing maven version coordinate");
155         }
156
157         this.policyContainer = makePolicyContainer(groupId, artifactId, version);
158         this.init(decoderConfigurations, encoderConfigurations);
159
160         logger.debug("{}: instantiation completed ", this);
161     }
162
163     /**
164      * init encoding/decoding configuration.
165      *
166      * @param decoderConfigurations list of topic -> decoders -> filters mapping
167      * @param encoderConfigurations list of topic -> encoders -> filters mapping
168      */
169     protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations,
170             List<TopicCoderFilterConfiguration> encoderConfigurations) {
171
172         this.decoderConfigurations = decoderConfigurations;
173         this.encoderConfigurations = encoderConfigurations;
174
175         this.initCoders(decoderConfigurations, true);
176         this.initCoders(encoderConfigurations, false);
177
178         this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode();
179     }
180
181     @Override
182     public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
183             List<TopicCoderFilterConfiguration> decoderConfigurations,
184             List<TopicCoderFilterConfiguration> encoderConfigurations)
185                     throws LinkageError {
186
187         logger.info("updating version -> [{}:{}:{}]", newGroupId, newArtifactId, newVersion);
188
189         validateText(newGroupId, "Missing maven group-id coordinate");
190         validateText(newArtifactId, "Missing maven artifact-id coordinate");
191         validateText(newVersion, "Missing maven version coordinate");
192
193         validateHasBrain(newGroupId, newArtifactId, newVersion);
194
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);
199             return;
200         }
201
202         validateNewVersion(newGroupId, newArtifactId, newVersion);
203
204         /* upgrade */
205         String messages = this.policyContainer.updateToVersion(newVersion);
206         logger.warn("{} UPGRADE results: {}", this, messages);
207
208         /*
209          * If all successful (can load new container), now we can remove all coders from previous sessions
210          */
211         this.removeCoders();
212
213         /*
214          * add the new coders
215          */
216         this.init(decoderConfigurations, encoderConfigurations);
217
218         logger.info("UPDATE-TO-VERSION: completed {}", this);
219     }
220
221     private void validateText(String text, String errorMessage) {
222         if (text == null || text.isEmpty()) {
223             throw new IllegalArgumentException(errorMessage);
224         }
225     }
226
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 + ":"
233                     + newVersion);
234         }
235     }
236
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);
244         }
245     }
246
247     /**
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.
251      *
252      * @param coderConfigurations list of topic -> decoders -> filters mapping
253      */
254     protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations,
255             boolean decoder) {
256
257         logger.info("INIT-CODERS: {}", this);
258
259         if (coderConfigurations == null) {
260             return;
261         }
262
263
264         for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) {
265             String topic = coderConfig.getTopic();
266
267             var customGsonCoder = getCustomCoder(coderConfig);
268
269             List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters();
270             if (coderFilters == null || coderFilters.isEmpty()) {
271                 continue;
272             }
273
274             for (PotentialCoderFilter coderFilter : coderFilters) {
275                 String potentialCodedClass = coderFilter.getCodedClass();
276                 JsonProtocolFilter protocolFilter = coderFilter.getFilter();
277
278                 if (isNotAClass(potentialCodedClass)) {
279                     throw makeRetrieveEx(potentialCodedClass);
280                 } else {
281                     logClassFetched(potentialCodedClass);
282                 }
283
284                 if (decoder) {
285                     getCoderManager().addDecoder(EventProtocolParams.builder()
286                             .groupId(this.getGroupId())
287                             .artifactId(this.getArtifactId())
288                             .topic(topic)
289                             .eventClass(potentialCodedClass)
290                             .protocolFilter(protocolFilter)
291                             .customGsonCoder(customGsonCoder)
292                             .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()).build());
293                 } else {
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());
300                 }
301             }
302         }
303     }
304
305     private CustomGsonCoder getCustomCoder(TopicCoderFilterConfiguration coderConfig) {
306         var customGsonCoder = coderConfig.getCustomGsonCoder();
307         if (customGsonCoder != null
308                 && customGsonCoder.getClassContainer() != null
309                 && !customGsonCoder.getClassContainer().isEmpty()) {
310
311             String customGsonCoderClass = customGsonCoder.getClassContainer();
312             if (isNotAClass(customGsonCoderClass)) {
313                 throw makeRetrieveEx(customGsonCoderClass);
314             } else {
315                 logClassFetched(customGsonCoderClass);
316             }
317         }
318         return customGsonCoder;
319     }
320
321     /**
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
325      */
326     private IllegalArgumentException makeRetrieveEx(String itemName) {
327         logger.error("{} cannot be retrieved", itemName);
328         return new IllegalArgumentException(itemName + " cannot be retrieved");
329     }
330
331     /**
332      * Logs the name of the class that was fetched.
333      * @param className class name fetched
334      */
335     private void logClassFetched(String className) {
336         logger.info("CLASS FETCHED {}", className);
337     }
338
339
340     /**
341      * remove decoders.
342      */
343     protected void removeDecoders() {
344         logger.info("REMOVE-DECODERS: {}", this);
345
346         if (this.decoderConfigurations == null) {
347             return;
348         }
349
350
351         for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
352             String topic = coderConfig.getTopic();
353             getCoderManager().removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
354         }
355     }
356
357     /**
358      * remove decoders.
359      */
360     protected void removeEncoders() {
361
362         logger.info("REMOVE-ENCODERS: {}", this);
363
364         if (this.encoderConfigurations == null) {
365             return;
366         }
367
368         for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
369             String topic = coderConfig.getTopic();
370             getCoderManager().removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
371         }
372     }
373
374
375     @Override
376     public boolean ownsCoder(Class<?> coderClass, int modelHash) {
377         if (isNotAClass(coderClass.getName())) {
378             logger.error("{}{} cannot be retrieved. ", this, coderClass.getName());
379             return false;
380         }
381
382         if (modelHash == this.modelClassLoaderHash) {
383             logger.info("{}{} class loader matches original drools controller rules classloader {}",
384                             coderClass.getName(), this, coderClass.getClassLoader());
385             return true;
386         } else {
387             logger.warn("{}{} class loaders don't match {} vs {}", this, coderClass.getName(),
388                             coderClass.getClassLoader(), this.policyContainer.getClassLoader());
389             return false;
390         }
391     }
392
393     @Override
394     public boolean start() {
395
396         logger.info("START: {}", this);
397
398         synchronized (this) {
399             if (this.alive) {
400                 return true;
401             }
402             this.alive = true;
403         }
404
405         return this.policyContainer.start();
406     }
407
408     @Override
409     public boolean stop() {
410
411         logger.info("STOP: {}", this);
412
413         synchronized (this) {
414             if (!this.alive) {
415                 return true;
416             }
417             this.alive = false;
418         }
419
420         return this.policyContainer.stop();
421     }
422
423     @Override
424     public void shutdown() {
425         logger.info("{}: SHUTDOWN", this);
426
427         try {
428             this.stop();
429             this.removeCoders();
430         } catch (Exception e) {
431             logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
432         } finally {
433             this.policyContainer.shutdown();
434         }
435
436     }
437
438     @Override
439     public void halt() {
440         logger.info("{}: HALT", this);
441
442         try {
443             this.stop();
444             this.removeCoders();
445         } catch (Exception e) {
446             logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
447         } finally {
448             this.policyContainer.destroy();
449         }
450     }
451
452     /**
453      * removes this drools controllers and encoders and decoders from operation.
454      */
455     protected void removeCoders() {
456         logger.info("{}: REMOVE-CODERS", this);
457
458         try {
459             this.removeDecoders();
460         } catch (IllegalArgumentException e) {
461             logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
462         }
463
464         try {
465             this.removeEncoders();
466         } catch (IllegalArgumentException e) {
467             logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
468         }
469     }
470
471     @Override
472     public boolean isAlive() {
473         return this.alive;
474     }
475
476     @Override
477     public boolean offer(String topic, String event) {
478         logger.debug("{}: OFFER raw event from {}", this, topic);
479
480         if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
481             return true;
482         }
483
484         // 1. Now, check if this topic has a decoder:
485
486         if (!getCoderManager().isDecodingSupported(this.getGroupId(),
487                 this.getArtifactId(),
488                 topic)) {
489
490             logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this,              // NOSONAR
491                     topic, this.getGroupId(), this.getArtifactId());
492             return true;
493         }
494
495         // 2. Decode
496
497         Object anEvent;
498         try {
499             anEvent = getCoderManager().decode(this.getGroupId(),
500                     this.getArtifactId(),
501                     topic,
502                     event);
503         } catch (UnsupportedOperationException uoe) {
504             logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
505                     event, uoe.getMessage(), uoe);
506             return true;
507         } catch (Exception e) {
508             logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic,
509                     event, e.getMessage(), e);
510             return true;
511         }
512
513         return offer(anEvent);
514
515     }
516
517     /*
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.
521      */
522     @Override
523     public <T> boolean offer(T event) {     // NOSONAR
524         logger.debug("{}: OFFER event", this);
525
526         if (this.locked || !this.alive || this.policyContainer.getPolicySessions().isEmpty()) {
527             return true;
528         }
529
530         synchronized (this.recentSourceEvents) {
531             this.recentSourceEvents.add(event);
532         }
533
534         PdpJmx.getInstance().updateOccurred();
535
536         // Broadcast
537
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))) {
542             return true;
543         }
544
545         boolean successInject = this.policyContainer.insertAll(event);
546         if (!successInject) {
547             logger.warn("{} Failed to inject into PolicyContainer {}", this, this.getSessionNames());
548         }
549
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));
554
555         return true;
556
557     }
558
559     @Override
560     public boolean deliver(TopicSink sink, Object event) {
561
562         logger.info("{}DELIVER: {} FROM {} TO {}", this, event, this, sink);
563
564         for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
565             try {
566                 if (feature.beforeDeliver(this, sink, event)) {
567                     return true;
568                 }
569             } catch (Exception e) {
570                 logger.error("{}: feature {} before-deliver failure because of {}", this, feature.getClass().getName(),
571                         e.getMessage(), e);
572             }
573         }
574
575         if (sink == null) {
576             throw new IllegalArgumentException(this +  " invalid sink");
577         }
578
579         if (event == null) {
580             throw new IllegalArgumentException(this +  " invalid event");
581         }
582
583         if (this.locked) {
584             throw new IllegalStateException(this +  " is locked");
585         }
586
587         if (!this.alive) {
588             throw new IllegalStateException(this +  " is stopped");
589         }
590
591         String json =
592                 getCoderManager().encode(sink.getTopic(), event, this);
593
594         synchronized (this.recentSinkEvents) {
595             this.recentSinkEvents.add(json);
596         }
597
598         boolean success = sink.send(json);
599
600         for (DroolsControllerFeatureApi feature : getDroolsProviders().getList()) {
601             try {
602                 if (feature.afterDeliver(this, sink, event, json, success)) {
603                     return true;
604                 }
605             } catch (Exception e) {
606                 logger.error("{}: feature {} after-deliver failure because of {}", this, feature.getClass().getName(),
607                         e.getMessage(), e);
608             }
609         }
610
611         return success;
612
613     }
614
615     @Override
616     public String getVersion() {
617         return this.policyContainer.getVersion();
618     }
619
620     @Override
621     public String getArtifactId() {
622         return this.policyContainer.getArtifactId();
623     }
624
625     @Override
626     public String getGroupId() {
627         return this.policyContainer.getGroupId();
628     }
629
630     @Override
631     public synchronized boolean lock() {
632         logger.info("LOCK: {}",  this);
633
634         this.locked = true;
635         return true;
636     }
637
638     @Override
639     public synchronized boolean unlock() {
640         logger.info("UNLOCK: {}",  this);
641
642         this.locked = false;
643         return true;
644     }
645
646     @Override
647     public boolean isLocked() {
648         return this.locked;
649     }
650
651     @GsonJsonIgnore
652     @Override
653     public PolicyContainer getContainer() {
654         return this.policyContainer;
655     }
656
657     @GsonJsonProperty("sessions")
658     @Override
659     public List<String> getSessionNames() {
660         return getSessionNames(true);
661     }
662
663     /**
664      * get session names.
665      *
666      * @param abbreviated true for the short form, otherwise the long form
667      * @return session names
668      */
669     protected List<String> getSessionNames(boolean abbreviated) {
670         List<String> sessionNames = new ArrayList<>();
671         try {
672             for (PolicySession session: this.policyContainer.getPolicySessions()) {
673                 if (abbreviated) {
674                     sessionNames.add(session.getName());
675                 } else {
676                     sessionNames.add(session.getFullName());
677                 }
678             }
679         } catch (Exception e) {
680             logger.warn("Can't retrieve CORE sessions", e);
681             sessionNames.add(e.getMessage());
682         }
683         return sessionNames;
684     }
685
686     @GsonJsonProperty("sessionCoordinates")
687     @Override
688     public List<String> getCanonicalSessionNames() {
689         return getSessionNames(false);
690     }
691
692     @Override
693     public List<String> getBaseDomainNames() {
694         return new ArrayList<>(this.policyContainer.getKieContainer().getKieBaseNames());
695     }
696
697     /**
698      * provides the underlying core layer container sessions.
699      *
700      * @return the attached Policy Container
701      */
702     protected List<PolicySession> getSessions() {
703         return new ArrayList<>(this.policyContainer.getPolicySessions());
704     }
705
706     /**
707      * provides the underlying core layer container session with name sessionName.
708      *
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
713      */
714     protected PolicySession getSession(String sessionName) {
715         if (sessionName == null || sessionName.isEmpty()) {
716             throw new IllegalArgumentException("A Session Name must be provided");
717         }
718
719         List<PolicySession> sessions = this.getSessions();
720         for (PolicySession session : sessions) {
721             if (sessionName.equals(session.getName()) || sessionName.equals(session.getFullName())) {
722                 return session;
723             }
724         }
725
726         throw invalidSessNameEx(sessionName);
727     }
728
729     private IllegalArgumentException invalidSessNameEx(String sessionName) {
730         return new IllegalArgumentException("Invalid Session Name: " + sessionName);
731     }
732
733     @Override
734     public Map<String, Integer> factClassNames(String sessionName) {
735         validateSessionName(sessionName);
736
737         Map<String, Integer> classNames = new HashMap<>();
738
739         var session = getSession(sessionName);
740         var kieSession = session.getKieSession();
741
742         Collection<FactHandle> facts = kieSession.getFactHandles();
743         for (FactHandle fact : facts) {
744             try {
745                 String className = kieSession.getObject(fact).getClass().getName();
746                 if (classNames.containsKey(className)) {
747                     classNames.put(className, classNames.get(className) + 1);
748                 } else {
749                     classNames.put(className, 1);
750                 }
751             } catch (Exception e) {
752                 logger.warn(FACT_RETRIEVE_ERROR, fact, e);
753             }
754         }
755
756         return classNames;
757     }
758
759     private void validateSessionName(String sessionName) {
760         if (sessionName == null || sessionName.isEmpty()) {
761             throw invalidSessNameEx(sessionName);
762         }
763     }
764
765     @Override
766     public long factCount(String sessionName) {
767         validateSessionName(sessionName);
768
769         return getSession(sessionName).getKieSession().getFactCount();
770     }
771
772     @Override
773     public List<Object> facts(String sessionName, String className, boolean delete) {
774         validateSessionName(sessionName);
775
776         if (className == null || className.isEmpty()) {
777             throw new IllegalArgumentException("Invalid Class Name: " + className);
778         }
779
780         Class<?> factClass =
781                 ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
782         if (factClass == null) {
783             throw new IllegalArgumentException("Class cannot be fetched in model's classloader: " + className);
784         }
785
786         var session = getSession(sessionName);
787         var kieSession = session.getKieSession();
788
789         List<Object> factObjects = new ArrayList<>();
790
791         Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(factClass));
792         for (FactHandle factHandle : factHandles) {
793             try {
794                 factObjects.add(kieSession.getObject(factHandle));
795                 if (delete) {
796                     kieSession.delete(factHandle);
797                 }
798             } catch (Exception e) {
799                 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
800             }
801         }
802
803         return factObjects;
804     }
805
806     @Override
807     public <T> List<T> facts(@NonNull String sessionName, @NonNull Class<T> clazz) {
808         return facts(sessionName, clazz.getName(), false)
809             .stream()
810             .filter(clazz::isInstance)
811             .map(clazz::cast)
812             .collect(Collectors.toList());
813     }
814
815     @Override
816     public List<Object> factQuery(String sessionName, String queryName, String queriedEntity,
817             boolean delete, Object... queryParams) {
818         validateSessionName(sessionName);
819
820         if (queryName == null || queryName.isEmpty()) {
821             throw new IllegalArgumentException("Invalid Query Name: " + queryName);
822         }
823
824         if (queriedEntity == null || queriedEntity.isEmpty()) {
825             throw new IllegalArgumentException("Invalid Queried Entity: " + queriedEntity);
826         }
827
828         var session = getSession(sessionName);
829         var kieSession = session.getKieSession();
830
831         validateQueryName(kieSession, queryName);
832
833         List<Object> factObjects = new ArrayList<>();
834
835         var queryResults = kieSession.getQueryResults(queryName, queryParams);
836         for (QueryResultsRow row : queryResults) {
837             try {
838                 factObjects.add(row.get(queriedEntity));
839                 if (delete) {
840                     kieSession.delete(row.getFactHandle(queriedEntity));
841                 }
842             } catch (Exception e) {
843                 logger.warn("Object cannot be retrieved from row: {}", row, e);
844             }
845         }
846
847         return factObjects;
848     }
849
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)) {
854                     return;
855                 }
856             }
857         }
858
859         throw new IllegalArgumentException("Invalid Query Name: " + queryName);
860     }
861
862     @Override
863     public <T> boolean delete(@NonNull String sessionName, @NonNull T objFact) {
864         var kieSession = getSession(sessionName).getKieSession();
865
866         // try first to get the object to delete first by reference
867
868         var quickFact = kieSession.getFactHandle(objFact);
869         if (quickFact != null) {
870             logger.info("Fast delete of {} from {}", objFact, sessionName);
871             kieSession.delete(quickFact);
872             return true;
873         }
874
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
878
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);
885                 return true;
886             }
887         }
888
889         return false;
890     }
891
892     @Override
893     public <T> boolean delete(@NonNull T fact) {
894         return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
895     }
896
897     @Override
898     public <T> boolean delete(@NonNull String sessionName, @NonNull Class<T> fact) {
899         var session = getSession(sessionName);
900         var kieSession = session.getKieSession();
901
902         var success = true;
903         Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(fact));
904         for (FactHandle factHandle : factHandles) {
905             try {
906                 kieSession.delete(factHandle);
907             } catch (Exception e) {
908                 logger.warn(FACT_RETRIEVE_ERROR, factHandle, e);
909                 success = false;
910             }
911         }
912         return success;
913     }
914
915     @Override
916     public <T> boolean delete(@NonNull Class<T> fact) {
917         return this.getSessionNames().stream().map(ss -> delete(ss, fact)).reduce(false, Boolean::logicalOr);
918     }
919
920     @Override
921     public <T> boolean exists(@NonNull String sessionName, @NonNull T objFact) {
922         var kieSession = getSession(sessionName).getKieSession();
923         if (kieSession.getFactHandle(objFact) != null) {
924             return true;
925         }
926
927         // try to find the object by equality comparison instead if it could not be
928         // found by reference
929
930         Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(objFact.getClass()));
931         for (FactHandle factHandle : factHandles) {
932             if (Objects.equals(objFact, kieSession.getObject(factHandle))) {
933                 return true;
934             }
935         }
936
937         return false;
938     }
939
940     @Override
941     public <T> boolean exists(@NonNull T fact) {
942         return this.getSessionNames().stream().anyMatch(ss -> exists(ss, fact));
943     }
944
945     @Override
946     public Class<?> fetchModelClass(String className) {
947         return ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
948     }
949
950     /**
951      * Get recent source events.
952      *
953      * @return the recentSourceEvents
954      */
955     @Override
956     public Object[] getRecentSourceEvents() {
957         synchronized (this.recentSourceEvents) {
958             var events = new Object[recentSourceEvents.size()];
959             return recentSourceEvents.toArray(events);
960         }
961     }
962
963     /**
964      * Get recent sink events.
965      *
966      * @return the recentSinkEvents
967      */
968     @Override
969     public String[] getRecentSinkEvents() {
970         synchronized (this.recentSinkEvents) {
971             var events = new String[recentSinkEvents.size()];
972             return recentSinkEvents.toArray(events);
973         }
974     }
975
976     @Override
977     public boolean isBrained() {
978         return true;
979     }
980
981
982     @Override
983     public String toString() {
984         return "MavenDroolsController [policyContainer=" + policyContainer.getName() + ":" + ", alive=" + alive
985                         + ", locked=" + ", modelClassLoaderHash=" + modelClassLoaderHash + "]";
986     }
987
988     // these may be overridden by junit tests
989
990     protected EventProtocolCoder getCoderManager() {
991         return EventProtocolCoderConstants.getManager();
992     }
993
994     protected OrderedServiceImpl<DroolsControllerFeatureApi> getDroolsProviders() {
995         return DroolsControllerFeatureApiConstants.getProviders();
996     }
997
998     protected PolicyContainer makePolicyContainer(String groupId, String artifactId, String version) {
999         return new PolicyContainer(groupId, artifactId, version);
1000     }
1001
1002     protected boolean isNotAClass(String className) {
1003         return !ReflectionUtil.isClass(this.policyContainer.getClassLoader(), className);
1004     }
1005 }