707a8e7719d885a3ac41419ebd0f6bacd0e0bd76
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * policy-management
4  * ================================================================================
5  * Copyright (C) 2017-2018 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 org.apache.commons.collections4.queue.CircularFifoQueue;
31 import org.drools.core.ClassObjectFilter;
32 import org.kie.api.definition.KiePackage;
33 import org.kie.api.definition.rule.Query;
34 import org.kie.api.runtime.KieSession;
35 import org.kie.api.runtime.rule.FactHandle;
36 import org.kie.api.runtime.rule.QueryResults;
37 import org.kie.api.runtime.rule.QueryResultsRow;
38 import org.onap.policy.common.endpoints.event.comm.TopicSink;
39 import org.onap.policy.drools.controller.DroolsController;
40 import org.onap.policy.drools.core.PolicyContainer;
41 import org.onap.policy.drools.core.PolicySession;
42 import org.onap.policy.drools.core.jmx.PdpJmx;
43 import org.onap.policy.drools.features.DroolsControllerFeatureAPI;
44 import org.onap.policy.drools.protocol.coders.EventProtocolCoder;
45 import org.onap.policy.drools.protocol.coders.JsonProtocolFilter;
46 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
47 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
48 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder;
49 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
50 import org.onap.policy.drools.utils.ReflectionUtil;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * Maven-based Drools Controller that interacts with the 
56  * policy-core PolicyContainer and PolicySession to manage
57  * Drools containers instantiated using Maven.
58  */
59 public class MavenDroolsController implements DroolsController {
60
61     /**
62      * logger.
63      */
64     private static Logger  logger = LoggerFactory.getLogger(MavenDroolsController.class);
65
66     /**
67      * Policy Container, the access object to the policy-core layer.
68      */
69     @JsonIgnore
70     protected final PolicyContainer policyContainer;
71
72     /**
73      * alive status of this drools controller, 
74      * reflects invocation of start()/stop() only.
75      */
76     protected volatile boolean alive = false;
77
78     /**
79      * locked status of this drools controller,
80      * reflects if i/o drools related operations are permitted,
81      * more specifically: offer() and deliver().
82      * It does not affect the ability to start and stop 
83      * underlying drools infrastructure
84      */
85     protected volatile boolean locked = false;
86
87     /**
88      * list of topics, each with associated decoder classes, each
89      * with a list of associated filters.
90      */
91     protected List<TopicCoderFilterConfiguration> decoderConfigurations;
92
93     /**
94      * list of topics, each with associated encoder classes, each
95      * with a list of associated filters.
96      */
97     protected List<TopicCoderFilterConfiguration> encoderConfigurations;
98
99     /**
100      * recent source events processed.
101      */
102     protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<>(10);
103
104     /**
105      * recent sink events processed.
106      */
107     protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<>(10);
108
109     /**
110      * original Drools Model/Rules classloader hash.
111      */
112     protected int modelClassLoaderHash;
113
114     /**
115      * Expanded version of the constructor.
116      * 
117      * @param groupId maven group id
118      * @param artifactId maven artifact id
119      * @param version maven version
120      * @param decoderConfigurations list of topic -> decoders -> filters mapping
121      * @param encoderConfigurations list of topic -> encoders -> filters mapping
122      * 
123      * @throws IllegalArgumentException invalid arguments passed in
124      */
125     public MavenDroolsController(String groupId, 
126             String artifactId, 
127             String version,
128             List<TopicCoderFilterConfiguration> decoderConfigurations,
129             List<TopicCoderFilterConfiguration> encoderConfigurations) {
130
131         logger.info("drools-controller instantiation [{}:{}:{}]", groupId, artifactId, version);
132
133         if (groupId == null || groupId.isEmpty()) {
134             throw new IllegalArgumentException("Missing maven group-id coordinate");
135         }
136
137         if (artifactId == null || artifactId.isEmpty()) {
138             throw new IllegalArgumentException("Missing maven artifact-id coordinate");
139         }
140
141         if (version == null || version.isEmpty()) {
142             throw new IllegalArgumentException("Missing maven version coordinate");
143         }
144
145         this.policyContainer = new PolicyContainer(groupId, artifactId, version);
146         this.init(decoderConfigurations, encoderConfigurations);
147
148         logger.debug("{}: instantiation completed ", this);
149     }
150
151     /**
152      * init encoding/decoding configuration.
153      * 
154      * @param decoderConfigurations list of topic -> decoders -> filters mapping
155      * @param encoderConfigurations list of topic -> encoders -> filters mapping
156      */
157     protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations,
158             List<TopicCoderFilterConfiguration> encoderConfigurations) {
159
160         this.decoderConfigurations = decoderConfigurations;
161         this.encoderConfigurations = encoderConfigurations;
162
163         this.initCoders(decoderConfigurations, true);
164         this.initCoders(encoderConfigurations, false);
165
166         this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode();
167     }
168
169     @Override
170     public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
171             List<TopicCoderFilterConfiguration> decoderConfigurations,
172             List<TopicCoderFilterConfiguration> encoderConfigurations) 
173                     throws LinkageError {
174
175         logger.info("updating version -> [{}:{}:{}]", newGroupId, newArtifactId, newVersion);
176
177         if (newGroupId == null || newGroupId.isEmpty()) {
178             throw new IllegalArgumentException("Missing maven group-id coordinate");
179         }
180
181         if (newArtifactId == null || newArtifactId.isEmpty()) {
182             throw new IllegalArgumentException("Missing maven artifact-id coordinate");
183         }
184
185         if (newVersion == null || newVersion.isEmpty()) {
186             throw new IllegalArgumentException("Missing maven version coordinate");
187         }
188
189         if (newGroupId.equalsIgnoreCase(DroolsController.NO_GROUP_ID) 
190                 || newArtifactId.equalsIgnoreCase(DroolsController.NO_ARTIFACT_ID) 
191                 || newVersion.equalsIgnoreCase(DroolsController.NO_VERSION)) {
192             throw new IllegalArgumentException("BRAINLESS maven coordinates provided: " 
193                     + newGroupId + ":" + newArtifactId + ":" 
194                     + newVersion);
195         }
196
197         if (newGroupId.equalsIgnoreCase(this.getGroupId())
198                 && newArtifactId.equalsIgnoreCase(this.getArtifactId())
199                 && newVersion.equalsIgnoreCase(this.getVersion())) {
200             logger.warn("Al in the right version: " + newGroupId + ":" 
201                     + newArtifactId + ":" +  newVersion + " vs. " + this);
202             return;
203         }
204
205         if (!newGroupId.equalsIgnoreCase(this.getGroupId()) 
206                 || !newArtifactId.equalsIgnoreCase(this.getArtifactId())) {
207             throw new IllegalArgumentException(
208                     "Group ID and Artifact ID maven coordinates must be identical for the upgrade: " 
209                     + newGroupId + ":" + newArtifactId + ":" 
210                     + newVersion + " vs. " + this);
211         }
212
213         /* upgrade */
214         String messages = this.policyContainer.updateToVersion(newVersion);
215         if (logger.isWarnEnabled()) {
216             logger.warn("{} UPGRADE results: {}", this, messages);
217         }
218
219         /*
220          * If all sucessful (can load new container), now we can remove all coders from previous sessions
221          */
222         this.removeCoders();
223
224         /*
225          * add the new coders
226          */
227         this.init(decoderConfigurations, encoderConfigurations);
228
229         if (logger.isInfoEnabled()) {
230             logger.info("UPDATE-TO-VERSION: completed " +  this);
231         }
232     }
233
234     /**
235      * initialize decoders for all the topics supported by this controller
236      * Note this is critical to be done after the Policy Container is
237      * instantiated to be able to fetch the corresponding classes.
238      * 
239      * @param coderConfigurations list of topic -> decoders -> filters mapping
240      */
241     protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations, 
242             boolean decoder) {
243
244         if (logger.isInfoEnabled()) {
245             logger.info("INIT-CODERS: " +  this);
246         }
247
248         if (coderConfigurations == null) {
249             return;
250         }
251
252
253         for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) {
254             String topic = coderConfig.getTopic();
255
256             CustomGsonCoder customGsonCoder = coderConfig.getCustomGsonCoder();
257             if (coderConfig.getCustomGsonCoder() != null 
258                     && coderConfig.getCustomGsonCoder().getClassContainer() != null
259                     && !coderConfig.getCustomGsonCoder().getClassContainer().isEmpty()) {
260
261                 String customGsonCoderClass = coderConfig.getCustomGsonCoder().getClassContainer();
262                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(),
263                         customGsonCoderClass)) {
264                     throw makeRetrieveEx(customGsonCoderClass);
265                 } else {
266                     if (logger.isInfoEnabled()) {
267                         logClassFetched(customGsonCoderClass);
268                     }
269                 }
270             }
271
272             CustomJacksonCoder customJacksonCoder = coderConfig.getCustomJacksonCoder();
273             if (coderConfig.getCustomJacksonCoder() != null 
274                     && coderConfig.getCustomJacksonCoder().getClassContainer() != null
275                     && !coderConfig.getCustomJacksonCoder().getClassContainer().isEmpty()) {
276
277                 String customJacksonCoderClass = coderConfig.getCustomJacksonCoder().getClassContainer();
278                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(),
279                         customJacksonCoderClass)) {
280                     throw makeRetrieveEx(customJacksonCoderClass);
281                 } else {
282                     if (logger.isInfoEnabled()) {
283                         logClassFetched(customJacksonCoderClass);
284                     }
285                 }
286             }
287
288             List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters();
289             if (coderFilters == null || coderFilters.isEmpty()) {
290                 continue;
291             }
292
293             for (PotentialCoderFilter coderFilter : coderFilters) {
294                 String potentialCodedClass = coderFilter.getCodedClass();
295                 JsonProtocolFilter protocolFilter = coderFilter.getFilter();
296
297                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), 
298                         potentialCodedClass)) {
299                     throw makeRetrieveEx(potentialCodedClass);
300                 } else {
301                     if (logger.isInfoEnabled()) {
302                         logClassFetched(potentialCodedClass);
303                     }
304                 }
305
306                 if (decoder) {
307                     EventProtocolCoder.manager.addDecoder(this.getGroupId(), this.getArtifactId(), 
308                             topic, potentialCodedClass, protocolFilter,
309                             customGsonCoder,
310                             customJacksonCoder,
311                             this.policyContainer.getClassLoader().hashCode());
312                 } else {
313                     EventProtocolCoder.manager.addEncoder(this.getGroupId(), this.getArtifactId(), 
314                             topic, potentialCodedClass, protocolFilter,
315                             customGsonCoder,
316                             customJacksonCoder,
317                             this.policyContainer.getClassLoader().hashCode());
318                 }
319             }
320         }
321     }
322
323     /**
324      * Logs an error and makes an exception for an item that cannot be retrieved.
325      * @param itemName the item to retrieve
326      * @return a new exception
327      */
328     private IllegalArgumentException makeRetrieveEx(String itemName) {
329         logger.error("{} cannot be retrieved", itemName);
330         return new IllegalArgumentException(itemName + " cannot be retrieved");
331     }
332
333     /**
334      * Logs the name of the class that was fetched.
335      * @param className class name fetched
336      */
337     private void logClassFetched(String className) {
338         logger.info("CLASS FETCHED {}", className);
339     }
340
341
342     /**
343      * remove decoders.
344      */
345     protected void removeDecoders() {
346         if (logger.isInfoEnabled()) {
347             logger.info("REMOVE-DECODERS: {}",  this);
348         }
349
350         if (this.decoderConfigurations == null) {
351             return;
352         }
353
354
355         for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
356             String topic = coderConfig.getTopic();
357             EventProtocolCoder.manager.removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
358         }
359     }
360
361     /**
362      * remove decoders.
363      */
364     protected void removeEncoders() {
365
366         if (logger.isInfoEnabled()) {
367             logger.info("REMOVE-ENCODERS: {}",  this);
368         }
369
370         if (this.encoderConfigurations == null) {
371             return;
372         }
373
374         for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
375             String topic = coderConfig.getTopic();
376             EventProtocolCoder.manager.removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
377         }
378     }
379
380
381     @Override
382     public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) {
383         if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), coderClass.getCanonicalName())) {
384             logger.error("{}{} cannot be retrieved. ", this, coderClass.getCanonicalName());
385             return false;
386         }
387
388         if (modelHash == this.modelClassLoaderHash) {
389             if (logger.isInfoEnabled()) {
390                 logger.info(coderClass.getCanonicalName() 
391                         + this + " class loader matches original drools controller rules classloader "
392                         + coderClass.getClassLoader());
393             }
394             return true;
395         } else {
396             if (logger.isWarnEnabled()) {
397                 logger.warn(this + coderClass.getCanonicalName() + " class loaders don't match  "
398                         + coderClass.getClassLoader() + " vs " 
399                         + this.policyContainer.getClassLoader());
400             }
401             return false;
402         }
403     }
404
405     @Override
406     public boolean start() {
407
408         if (logger.isInfoEnabled()) {
409             logger.info("START: {}", this);
410         }
411
412         synchronized (this) {
413             if (this.alive) {
414                 return true;
415             }
416             this.alive = true;
417         }
418
419         return this.policyContainer.start();
420     }
421
422     @Override
423     public boolean stop() {
424
425         logger.info("STOP: {}", this);
426
427         synchronized (this) {
428             if (!this.alive) {
429                 return true;
430             }
431             this.alive = false;
432         }
433
434         return this.policyContainer.stop();
435     }
436
437     @Override
438     public void shutdown() {
439         logger.info("{}: SHUTDOWN", this);
440
441         try {
442             this.stop();
443             this.removeCoders();
444         } catch (Exception e) {
445             logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
446         } finally {
447             this.policyContainer.shutdown();
448         }
449
450     }
451
452     @Override
453     public void halt() {
454         logger.info("{}: HALT", this);
455
456         try {
457             this.stop();
458             this.removeCoders();
459         } catch (Exception e) {
460             logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
461         } finally {
462             this.policyContainer.destroy();
463         }
464     }
465
466     /**
467      * removes this drools controllers and encoders and decoders from operation.
468      */
469     protected void removeCoders() {
470         logger.info("{}: REMOVE-CODERS", this);
471
472         try {
473             this.removeDecoders();
474         } catch (IllegalArgumentException e) {
475             logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
476         }
477
478         try {
479             this.removeEncoders();
480         } catch (IllegalArgumentException e) {
481             logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
482         }
483     }
484
485     @Override
486     public boolean isAlive() {
487         return this.alive;
488     }
489
490     @Override
491     public boolean offer(String topic, String event) {
492         logger.debug("{}: OFFER: {} <- {}", this, topic, event);
493
494         if (this.locked) {
495             return true;
496         }
497         if (!this.alive) {
498             return true;
499         }
500
501         // 0. Check if the policy container has any sessions
502
503         if (this.policyContainer.getPolicySessions().isEmpty()) {
504             // no sessions
505             return true;
506         }
507
508         // 1. Now, check if this topic has a decoder:
509
510         if (!EventProtocolCoder.manager.isDecodingSupported(this.getGroupId(), 
511                 this.getArtifactId(), 
512                 topic)) {
513
514             logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this, 
515                     topic, this.getGroupId(), this.getArtifactId());
516             return true;
517         }
518
519         // 2. Decode
520
521         Object anEvent;
522         try {
523             anEvent = EventProtocolCoder.manager.decode(this.getGroupId(), 
524                     this.getArtifactId(), 
525                     topic, 
526                     event);
527         } catch (UnsupportedOperationException uoe) {
528             logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic, 
529                     event, uoe.getMessage(), uoe);
530             return true;
531         } catch (Exception e) {
532             logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic, 
533                     event, e.getMessage(), e);
534             return true;
535         }
536
537         synchronized (this.recentSourceEvents) {
538             this.recentSourceEvents.add(anEvent);
539         }
540
541         // increment event count for Nagios monitoring
542         PdpJmx.getInstance().updateOccured();  
543
544         // Broadcast
545
546         if (logger.isInfoEnabled()) {
547             logger.info("{} BROADCAST-INJECT of {} FROM {} INTO {}", 
548                     this, event, topic, this.policyContainer.getName());
549         }
550
551         for (DroolsControllerFeatureAPI feature : DroolsControllerFeatureAPI.providers.getList()) {
552             try {
553                 if (feature.beforeInsert(this, anEvent)) {
554                     return true;
555                 }
556             } catch (Exception e) {
557                 logger.error("{}: feature {} before-insert failure because of {}",
558                         this, feature.getClass().getName(), e.getMessage(), e);
559             }
560         }
561
562         boolean successInject = this.policyContainer.insertAll(anEvent);
563         if (!successInject) {
564             logger.warn(this + "Failed to inject into PolicyContainer {}", this.getSessionNames());
565         }
566
567         for (DroolsControllerFeatureAPI feature : DroolsControllerFeatureAPI.providers.getList()) {
568             try {
569                 if (feature.afterInsert(this, anEvent, successInject)) {
570                     return true;
571                 }
572             } catch (Exception e) {
573                 logger.error("{}: feature {} after-insert failure because of {}",
574                         this, feature.getClass().getName(), e.getMessage(), e);
575             }
576         }
577
578         return true;
579     }
580
581     @Override
582     public boolean deliver(TopicSink sink, Object event) {
583
584         if (logger.isInfoEnabled()) {
585             logger.info("{}DELIVER: {} FROM {} TO {}", this, event, this, sink);
586         }
587         
588         if (sink == null) {
589             throw new IllegalArgumentException(this +  " invalid sink");
590         }
591
592         if (event == null) {
593             throw new IllegalArgumentException(this +  " invalid event");
594         }
595         
596         if (this.locked) {
597             throw new IllegalStateException(this +  " is locked");
598         }
599
600         if (!this.alive) {
601             throw new IllegalStateException(this +  " is stopped");
602         }
603
604         String json =
605                 EventProtocolCoder.manager.encode(sink.getTopic(), event, this);
606
607         synchronized (this.recentSinkEvents) {
608             this.recentSinkEvents.add(json);
609         }
610
611         return sink.send(json);
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     /**
631      * Get model class loader hash.
632      * 
633      * @return the modelClassLoaderHash
634      */
635     public int getModelClassLoaderHash() {
636         return modelClassLoaderHash;
637     }
638
639     @Override
640     public synchronized boolean lock() {
641         logger.info("LOCK: {}",  this);
642
643         this.locked = true;
644         return true;
645     }
646
647     @Override
648     public synchronized boolean unlock() {
649         logger.info("UNLOCK: {}",  this);
650
651         this.locked = false;
652         return true;
653     }
654
655     @Override
656     public boolean isLocked() {
657         return this.locked;
658     }
659
660     @JsonIgnore
661     @Override
662     public PolicyContainer getContainer() {
663         return this.policyContainer;
664     }
665
666     @JsonProperty("sessions")
667     @Override
668     public List<String> getSessionNames() {
669         return getSessionNames(true);
670     }
671
672     /**
673      * get session names.
674      * 
675      * @param abbreviated true for the short form, otherwise the long form
676      * @return session names
677      */
678     protected List<String> getSessionNames(boolean abbreviated) {
679         List<String> sessionNames = new ArrayList<>();
680         try {
681             for (PolicySession session: this.policyContainer.getPolicySessions()) {
682                 if (abbreviated) {
683                     sessionNames.add(session.getName());
684                 } else {
685                     sessionNames.add(session.getFullName());
686                 }
687             }
688         } catch (Exception e) {
689             logger.warn("Can't retrieve CORE sessions: " + e.getMessage(), e);
690             sessionNames.add(e.getMessage());
691         }
692         return sessionNames;
693     }
694
695     @JsonProperty("sessionCoordinates")
696     @Override
697     public List<String> getCanonicalSessionNames() {
698         return getSessionNames(false);
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         if (sessionName == null || sessionName.isEmpty()) {
742             throw invalidSessNameEx(sessionName);
743         }
744
745         Map<String,Integer> classNames = new HashMap<>();
746
747         PolicySession session = getSession(sessionName);
748         KieSession kieSession = session.getKieSession();
749
750         Collection<FactHandle> facts = session.getKieSession().getFactHandles();
751         for (FactHandle fact : facts) {
752             try {
753                 String className = kieSession.getObject(fact).getClass().getName();
754                 if (classNames.containsKey(className)) {
755                     classNames.put(className, classNames.get(className) + 1);
756                 } else {
757                     classNames.put(className, 1);
758                 }
759             } catch (Exception e) {
760                 logger.warn("Object cannot be retrieved from fact {}", fact, e);
761             }
762         }
763
764         return classNames;
765     }
766
767     @Override
768     public long factCount(String sessionName) {
769         if (sessionName == null || sessionName.isEmpty()) {
770             throw invalidSessNameEx(sessionName);
771         }
772
773         PolicySession session = getSession(sessionName);
774         return session.getKieSession().getFactCount();
775     }
776
777     @Override
778     public List<Object> facts(String sessionName, String className, boolean delete) {
779         if (sessionName == null || sessionName.isEmpty()) {
780             throw invalidSessNameEx(sessionName);
781         }
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 List<Object> factQuery(String sessionName, String queryName, String queriedEntity, 
815             boolean delete, Object... queryParams) {
816         if (sessionName == null || sessionName.isEmpty()) {
817             throw invalidSessNameEx(sessionName);
818         }
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         PolicySession session = getSession(sessionName);
829         KieSession kieSession = session.getKieSession();
830
831         boolean found = false;
832         for (KiePackage kiePackage : kieSession.getKieBase().getKiePackages()) {
833             for (Query q : kiePackage.getQueries()) {
834                 if (q.getName() != null && q.getName().equals(queryName)) {
835                     found = true;
836                     break;
837                 }
838             }
839         }
840         if (!found) {
841             throw new IllegalArgumentException("Invalid Query Name: " + queryName);
842         }
843
844         List<Object> factObjects = new ArrayList<>();
845
846         QueryResults queryResults = kieSession.getQueryResults(queryName, queryParams);
847         for (QueryResultsRow row : queryResults) {
848             try {
849                 factObjects.add(row.get(queriedEntity));
850                 if (delete) {
851                     kieSession.delete(row.getFactHandle(queriedEntity));
852                 }
853             } catch (Exception e) {
854                 logger.warn("Object cannot be retrieved from row: {}", row, e);
855             }
856         }
857
858         return factObjects;
859     }
860
861     @Override
862     public Class<?> fetchModelClass(String className) {
863         return ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
864     }
865
866     /**
867      * Get recent source events.
868      * 
869      * @return the recentSourceEvents
870      */
871     @Override
872     public Object[] getRecentSourceEvents() {
873         synchronized (this.recentSourceEvents) {
874             Object[] events = new Object[recentSourceEvents.size()];
875             return recentSourceEvents.toArray(events);
876         }
877     }
878
879     /**
880      * Get recent sink events.
881      * 
882      * @return the recentSinkEvents
883      */
884     @Override
885     public String[] getRecentSinkEvents() {
886         synchronized (this.recentSinkEvents) {
887             String[] events = new String[recentSinkEvents.size()];
888             return recentSinkEvents.toArray(events);
889         }
890     }
891
892     @Override
893     public boolean isBrained() {
894         return true;
895     }
896
897
898     @Override
899     public String toString() {
900         StringBuilder builder = new StringBuilder();
901         builder
902             .append("MavenDroolsController [policyContainer=")
903             .append((policyContainer != null) ? policyContainer.getName() : "NULL")
904             .append(":")
905             .append(", alive=")
906             .append(alive)
907             .append(", locked=")
908             .append(", modelClassLoaderHash=")
909             .append(modelClassLoaderHash)
910             .append("]");
911         return builder.toString();
912     }
913 }