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