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