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