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