c707c8fc8d1663684a40d4cd1644e539261c463c
[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(EventProtocolParams.builder()
309                             .groupId(this.getGroupId())
310                             .artifactId(this.getArtifactId())
311                             .topic(topic)
312                             .eventClass(potentialCodedClass)
313                             .protocolFilter(protocolFilter)
314                             .customGsonCoder(customGsonCoder)
315                             .customJacksonCoder(customJacksonCoder)
316                             .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()));
317                 } else {
318                     EventProtocolCoder.manager.addEncoder(
319                             EventProtocolParams.builder().groupId(this.getGroupId())
320                                     .artifactId(this.getArtifactId()).topic(topic)
321                                     .eventClass(potentialCodedClass).protocolFilter(protocolFilter)
322                                     .customGsonCoder(customGsonCoder).customJacksonCoder(customJacksonCoder)
323                                     .modelClassLoaderHash(this.policyContainer.getClassLoader().hashCode()));
324                 }
325             }
326         }
327     }
328
329     /**
330      * Logs an error and makes an exception for an item that cannot be retrieved.
331      * @param itemName the item to retrieve
332      * @return a new exception
333      */
334     private IllegalArgumentException makeRetrieveEx(String itemName) {
335         logger.error("{} cannot be retrieved", itemName);
336         return new IllegalArgumentException(itemName + " cannot be retrieved");
337     }
338
339     /**
340      * Logs the name of the class that was fetched.
341      * @param className class name fetched
342      */
343     private void logClassFetched(String className) {
344         logger.info("CLASS FETCHED {}", className);
345     }
346
347
348     /**
349      * remove decoders.
350      */
351     protected void removeDecoders() {
352         if (logger.isInfoEnabled()) {
353             logger.info("REMOVE-DECODERS: {}",  this);
354         }
355
356         if (this.decoderConfigurations == null) {
357             return;
358         }
359
360
361         for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
362             String topic = coderConfig.getTopic();
363             EventProtocolCoder.manager.removeDecoders(this.getGroupId(), this.getArtifactId(), topic);
364         }
365     }
366
367     /**
368      * remove decoders.
369      */
370     protected void removeEncoders() {
371
372         if (logger.isInfoEnabled()) {
373             logger.info("REMOVE-ENCODERS: {}",  this);
374         }
375
376         if (this.encoderConfigurations == null) {
377             return;
378         }
379
380         for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
381             String topic = coderConfig.getTopic();
382             EventProtocolCoder.manager.removeEncoders(this.getGroupId(), this.getArtifactId(), topic);
383         }
384     }
385
386
387     @Override
388     public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) {
389         if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), coderClass.getCanonicalName())) {
390             logger.error("{}{} cannot be retrieved. ", this, coderClass.getCanonicalName());
391             return false;
392         }
393
394         if (modelHash == this.modelClassLoaderHash) {
395             if (logger.isInfoEnabled()) {
396                 logger.info(coderClass.getCanonicalName() 
397                         + this + " class loader matches original drools controller rules classloader "
398                         + coderClass.getClassLoader());
399             }
400             return true;
401         } else {
402             if (logger.isWarnEnabled()) {
403                 logger.warn(this + coderClass.getCanonicalName() + " class loaders don't match  "
404                         + coderClass.getClassLoader() + " vs " 
405                         + this.policyContainer.getClassLoader());
406             }
407             return false;
408         }
409     }
410
411     @Override
412     public boolean start() {
413
414         if (logger.isInfoEnabled()) {
415             logger.info("START: {}", this);
416         }
417
418         synchronized (this) {
419             if (this.alive) {
420                 return true;
421             }
422             this.alive = true;
423         }
424
425         return this.policyContainer.start();
426     }
427
428     @Override
429     public boolean stop() {
430
431         logger.info("STOP: {}", this);
432
433         synchronized (this) {
434             if (!this.alive) {
435                 return true;
436             }
437             this.alive = false;
438         }
439
440         return this.policyContainer.stop();
441     }
442
443     @Override
444     public void shutdown() {
445         logger.info("{}: SHUTDOWN", this);
446
447         try {
448             this.stop();
449             this.removeCoders();
450         } catch (Exception e) {
451             logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e);
452         } finally {
453             this.policyContainer.shutdown();
454         }
455
456     }
457
458     @Override
459     public void halt() {
460         logger.info("{}: HALT", this);
461
462         try {
463             this.stop();
464             this.removeCoders();
465         } catch (Exception e) {
466             logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e);
467         } finally {
468             this.policyContainer.destroy();
469         }
470     }
471
472     /**
473      * removes this drools controllers and encoders and decoders from operation.
474      */
475     protected void removeCoders() {
476         logger.info("{}: REMOVE-CODERS", this);
477
478         try {
479             this.removeDecoders();
480         } catch (IllegalArgumentException e) {
481             logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e);
482         }
483
484         try {
485             this.removeEncoders();
486         } catch (IllegalArgumentException e) {
487             logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e);
488         }
489     }
490
491     @Override
492     public boolean isAlive() {
493         return this.alive;
494     }
495
496     @Override
497     public boolean offer(String topic, String event) {
498         logger.debug("{}: OFFER: {} <- {}", this, topic, event);
499
500         if (this.locked) {
501             return true;
502         }
503         if (!this.alive) {
504             return true;
505         }
506
507         // 0. Check if the policy container has any sessions
508
509         if (this.policyContainer.getPolicySessions().isEmpty()) {
510             // no sessions
511             return true;
512         }
513
514         // 1. Now, check if this topic has a decoder:
515
516         if (!EventProtocolCoder.manager.isDecodingSupported(this.getGroupId(), 
517                 this.getArtifactId(), 
518                 topic)) {
519
520             logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this, 
521                     topic, this.getGroupId(), this.getArtifactId());
522             return true;
523         }
524
525         // 2. Decode
526
527         Object anEvent;
528         try {
529             anEvent = EventProtocolCoder.manager.decode(this.getGroupId(), 
530                     this.getArtifactId(), 
531                     topic, 
532                     event);
533         } catch (UnsupportedOperationException uoe) {
534             logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic, 
535                     event, uoe.getMessage(), uoe);
536             return true;
537         } catch (Exception e) {
538             logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic, 
539                     event, e.getMessage(), e);
540             return true;
541         }
542
543         synchronized (this.recentSourceEvents) {
544             this.recentSourceEvents.add(anEvent);
545         }
546
547         // increment event count for Nagios monitoring
548         PdpJmx.getInstance().updateOccured();  
549
550         // Broadcast
551
552         if (logger.isInfoEnabled()) {
553             logger.info("{} BROADCAST-INJECT of {} FROM {} INTO {}", 
554                     this, event, topic, this.policyContainer.getName());
555         }
556
557         for (DroolsControllerFeatureAPI feature : DroolsControllerFeatureAPI.providers.getList()) {
558             try {
559                 if (feature.beforeInsert(this, anEvent)) {
560                     return true;
561                 }
562             } catch (Exception e) {
563                 logger.error("{}: feature {} before-insert failure because of {}",
564                         this, feature.getClass().getName(), e.getMessage(), e);
565             }
566         }
567
568         boolean successInject = this.policyContainer.insertAll(anEvent);
569         if (!successInject) {
570             logger.warn(this + "Failed to inject into PolicyContainer {}", this.getSessionNames());
571         }
572
573         for (DroolsControllerFeatureAPI feature : DroolsControllerFeatureAPI.providers.getList()) {
574             try {
575                 if (feature.afterInsert(this, anEvent, successInject)) {
576                     return true;
577                 }
578             } catch (Exception e) {
579                 logger.error("{}: feature {} after-insert failure because of {}",
580                         this, feature.getClass().getName(), e.getMessage(), e);
581             }
582         }
583
584         return true;
585     }
586
587     @Override
588     public boolean deliver(TopicSink sink, Object event) {
589
590         if (logger.isInfoEnabled()) {
591             logger.info("{}DELIVER: {} FROM {} TO {}", this, event, this, sink);
592         }
593         
594         if (sink == null) {
595             throw new IllegalArgumentException(this +  " invalid sink");
596         }
597
598         if (event == null) {
599             throw new IllegalArgumentException(this +  " invalid event");
600         }
601         
602         if (this.locked) {
603             throw new IllegalStateException(this +  " is locked");
604         }
605
606         if (!this.alive) {
607             throw new IllegalStateException(this +  " is stopped");
608         }
609
610         String json =
611                 EventProtocolCoder.manager.encode(sink.getTopic(), event, this);
612
613         synchronized (this.recentSinkEvents) {
614             this.recentSinkEvents.add(json);
615         }
616
617         return sink.send(json);
618
619     }
620
621     @Override
622     public String getVersion() {
623         return this.policyContainer.getVersion();
624     }
625
626     @Override
627     public String getArtifactId() {
628         return this.policyContainer.getArtifactId();
629     }
630
631     @Override
632     public String getGroupId() {
633         return this.policyContainer.getGroupId();
634     }
635
636     /**
637      * Get model class loader hash.
638      * 
639      * @return the modelClassLoaderHash
640      */
641     public int getModelClassLoaderHash() {
642         return modelClassLoaderHash;
643     }
644
645     @Override
646     public synchronized boolean lock() {
647         logger.info("LOCK: {}",  this);
648
649         this.locked = true;
650         return true;
651     }
652
653     @Override
654     public synchronized boolean unlock() {
655         logger.info("UNLOCK: {}",  this);
656
657         this.locked = false;
658         return true;
659     }
660
661     @Override
662     public boolean isLocked() {
663         return this.locked;
664     }
665
666     @JsonIgnore
667     @Override
668     public PolicyContainer getContainer() {
669         return this.policyContainer;
670     }
671
672     @JsonProperty("sessions")
673     @Override
674     public List<String> getSessionNames() {
675         return getSessionNames(true);
676     }
677
678     /**
679      * get session names.
680      * 
681      * @param abbreviated true for the short form, otherwise the long form
682      * @return session names
683      */
684     protected List<String> getSessionNames(boolean abbreviated) {
685         List<String> sessionNames = new ArrayList<>();
686         try {
687             for (PolicySession session: this.policyContainer.getPolicySessions()) {
688                 if (abbreviated) {
689                     sessionNames.add(session.getName());
690                 } else {
691                     sessionNames.add(session.getFullName());
692                 }
693             }
694         } catch (Exception e) {
695             logger.warn("Can't retrieve CORE sessions: " + e.getMessage(), e);
696             sessionNames.add(e.getMessage());
697         }
698         return sessionNames;
699     }
700
701     @JsonProperty("sessionCoordinates")
702     @Override
703     public List<String> getCanonicalSessionNames() {
704         return getSessionNames(false);
705     }
706
707     /**
708      * provides the underlying core layer container sessions.
709      * 
710      * @return the attached Policy Container
711      */
712     protected List<PolicySession> getSessions() {
713         List<PolicySession> sessions = new ArrayList<>();
714         sessions.addAll(this.policyContainer.getPolicySessions());
715         return sessions;
716     }
717
718     /**
719      * provides the underlying core layer container session with name sessionName.
720      * 
721      * @param sessionName session name
722      * @return the attached Policy Container
723      * @throws IllegalArgumentException when an invalid session name is provided
724      * @throws IllegalStateException when the drools controller is in an invalid state
725      */
726     protected PolicySession getSession(String sessionName) {
727         if (sessionName == null || sessionName.isEmpty()) {
728             throw new IllegalArgumentException("A Session Name must be provided");
729         }
730
731         List<PolicySession> sessions = this.getSessions();
732         for (PolicySession session : sessions) {
733             if (sessionName.equals(session.getName()) || sessionName.equals(session.getFullName())) {
734                 return session;
735             }
736         }
737
738         throw invalidSessNameEx(sessionName);
739     }
740
741     private IllegalArgumentException invalidSessNameEx(String sessionName) {
742         return new IllegalArgumentException("Invalid Session Name: " + sessionName);
743     }
744
745     @Override
746     public Map<String,Integer> factClassNames(String sessionName) {
747         if (sessionName == null || sessionName.isEmpty()) {
748             throw invalidSessNameEx(sessionName);
749         }
750
751         Map<String,Integer> classNames = new HashMap<>();
752
753         PolicySession session = getSession(sessionName);
754         KieSession kieSession = session.getKieSession();
755
756         Collection<FactHandle> facts = session.getKieSession().getFactHandles();
757         for (FactHandle fact : facts) {
758             try {
759                 String className = kieSession.getObject(fact).getClass().getName();
760                 if (classNames.containsKey(className)) {
761                     classNames.put(className, classNames.get(className) + 1);
762                 } else {
763                     classNames.put(className, 1);
764                 }
765             } catch (Exception e) {
766                 logger.warn("Object cannot be retrieved from fact {}", fact, e);
767             }
768         }
769
770         return classNames;
771     }
772
773     @Override
774     public long factCount(String sessionName) {
775         if (sessionName == null || sessionName.isEmpty()) {
776             throw invalidSessNameEx(sessionName);
777         }
778
779         PolicySession session = getSession(sessionName);
780         return session.getKieSession().getFactCount();
781     }
782
783     @Override
784     public List<Object> facts(String sessionName, String className, boolean delete) {
785         if (sessionName == null || sessionName.isEmpty()) {
786             throw invalidSessNameEx(sessionName);
787         }
788
789         if (className == null || className.isEmpty()) {
790             throw new IllegalArgumentException("Invalid Class Name: " + className);
791         }
792
793         Class<?> factClass = 
794                 ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
795         if (factClass == null) {
796             throw new IllegalArgumentException("Class cannot be fetched in model's classloader: " + className);
797         }
798
799         PolicySession session = getSession(sessionName);
800         KieSession kieSession = session.getKieSession();
801
802         List<Object> factObjects = new ArrayList<>();
803
804         Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(factClass));
805         for (FactHandle factHandle : factHandles) {
806             try {
807                 factObjects.add(kieSession.getObject(factHandle));
808                 if (delete) {
809                     kieSession.delete(factHandle);
810                 }
811             } catch (Exception e) {
812                 logger.warn("Object cannot be retrieved from fact {}", factHandle, e);
813             }
814         }
815
816         return factObjects;
817     }
818
819     @Override
820     public List<Object> factQuery(String sessionName, String queryName, String queriedEntity, 
821             boolean delete, Object... queryParams) {
822         if (sessionName == null || sessionName.isEmpty()) {
823             throw invalidSessNameEx(sessionName);
824         }
825
826         if (queryName == null || queryName.isEmpty()) {
827             throw new IllegalArgumentException("Invalid Query Name: " + queryName);
828         }
829
830         if (queriedEntity == null || queriedEntity.isEmpty()) {
831             throw new IllegalArgumentException("Invalid Queried Entity: " + queriedEntity);
832         }
833
834         PolicySession session = getSession(sessionName);
835         KieSession kieSession = session.getKieSession();
836
837         boolean found = false;
838         for (KiePackage kiePackage : kieSession.getKieBase().getKiePackages()) {
839             for (Query q : kiePackage.getQueries()) {
840                 if (q.getName() != null && q.getName().equals(queryName)) {
841                     found = true;
842                     break;
843                 }
844             }
845         }
846         if (!found) {
847             throw new IllegalArgumentException("Invalid Query Name: " + queryName);
848         }
849
850         List<Object> factObjects = new ArrayList<>();
851
852         QueryResults queryResults = kieSession.getQueryResults(queryName, queryParams);
853         for (QueryResultsRow row : queryResults) {
854             try {
855                 factObjects.add(row.get(queriedEntity));
856                 if (delete) {
857                     kieSession.delete(row.getFactHandle(queriedEntity));
858                 }
859             } catch (Exception e) {
860                 logger.warn("Object cannot be retrieved from row: {}", row, e);
861             }
862         }
863
864         return factObjects;
865     }
866
867     @Override
868     public Class<?> fetchModelClass(String className) {
869         return ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
870     }
871
872     /**
873      * Get recent source events.
874      * 
875      * @return the recentSourceEvents
876      */
877     @Override
878     public Object[] getRecentSourceEvents() {
879         synchronized (this.recentSourceEvents) {
880             Object[] events = new Object[recentSourceEvents.size()];
881             return recentSourceEvents.toArray(events);
882         }
883     }
884
885     /**
886      * Get recent sink events.
887      * 
888      * @return the recentSinkEvents
889      */
890     @Override
891     public String[] getRecentSinkEvents() {
892         synchronized (this.recentSinkEvents) {
893             String[] events = new String[recentSinkEvents.size()];
894             return recentSinkEvents.toArray(events);
895         }
896     }
897
898     @Override
899     public boolean isBrained() {
900         return true;
901     }
902
903
904     @Override
905     public String toString() {
906         StringBuilder builder = new StringBuilder();
907         builder
908             .append("MavenDroolsController [policyContainer=")
909             .append((policyContainer != null) ? policyContainer.getName() : "NULL")
910             .append(":")
911             .append(", alive=")
912             .append(alive)
913             .append(", locked=")
914             .append(", modelClassLoaderHash=")
915             .append(modelClassLoaderHash)
916             .append("]");
917         return builder.toString();
918     }
919 }