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