8c734d526a8624abfa35e3498f023cd7418ab4f3
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2020, 2021 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.protocol.coders;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import lombok.AccessLevel;
29 import lombok.NoArgsConstructor;
30 import lombok.ToString;
31 import org.onap.policy.drools.controller.DroolsController;
32 import org.onap.policy.drools.controller.DroolsControllerConstants;
33 import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * This protocol Coder that does its best attempt to decode/encode, selecting the best class and best fitted json
39  * parsing tools.
40  */
41 @ToString
42 @NoArgsConstructor(access = AccessLevel.PROTECTED)
43 abstract class GenericEventProtocolCoder {
44     private static final String INVALID_ARTIFACT_ID_MSG = "Invalid artifact id";
45     private static final String INVALID_GROUP_ID_MSG = "Invalid group id";
46     private static final String INVALID_TOPIC_MSG = "Invalid Topic";
47     private static final String UNSUPPORTED_MSG = "Unsupported";
48     private static final String UNSUPPORTED_EX_MSG = "Unsupported:";
49     private static final String MISSING_CLASS = "class must be provided";
50
51     private static final Logger logger = LoggerFactory.getLogger(GenericEventProtocolCoder.class);
52
53     /**
54      * Mapping topic:controller-id -> /<protocol-decoder-toolset/> where protocol-coder-toolset contains
55      * a gson-protocol-coder-toolset.
56      */
57     protected final HashMap<String, ProtocolCoderToolset> coders =
58             new HashMap<>();
59
60     /**
61      * Mapping topic + classname -> Protocol Set.
62      */
63     protected final HashMap<String, List<ProtocolCoderToolset>>
64             reverseCoders = new HashMap<>();
65
66     /**
67      * Index a new coder.
68      */
69     public void add(EventProtocolParams eventProtocolParams) {
70         if (eventProtocolParams.getGroupId() == null || eventProtocolParams.getGroupId().isEmpty()) {
71             throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
72         }
73
74         if (eventProtocolParams.getArtifactId() == null || eventProtocolParams.getArtifactId().isEmpty()) {
75             throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
76         }
77
78         if (eventProtocolParams.getTopic() == null || eventProtocolParams.getTopic().isEmpty()) {
79             throw new IllegalArgumentException(INVALID_TOPIC_MSG);
80         }
81
82         if (eventProtocolParams.getEventClass() == null) {
83             throw new IllegalArgumentException("Invalid Event Class");
84         }
85
86         String key = this.codersKey(eventProtocolParams.getGroupId(), eventProtocolParams.getArtifactId(),
87                 eventProtocolParams.getTopic());
88         String reverseKey = this.reverseCodersKey(eventProtocolParams.getTopic(), eventProtocolParams.getEventClass());
89
90         synchronized (this) {
91             if (coders.containsKey(key)) {
92                 ProtocolCoderToolset toolset = coders.get(key);
93
94                 logger.info("{}: adding coders for existing {}: {}", this, key, toolset);
95
96                 toolset
97                         .addCoder(
98                                 eventProtocolParams.getEventClass(),
99                                 eventProtocolParams.getProtocolFilter(),
100                                 eventProtocolParams.getModelClassLoaderHash());
101
102                 if (!reverseCoders.containsKey(reverseKey)) {
103                     logger.info(
104                             "{}: adding new reverse coders (multiple classes case) for {}:{}: {}",
105                             this,
106                             reverseKey,
107                             key,
108                             toolset);
109
110                     List<ProtocolCoderToolset> reverseMappings =
111                             new ArrayList<>();
112                     reverseMappings.add(toolset);
113                     reverseCoders.put(reverseKey, reverseMappings);
114                 }
115                 return;
116             }
117
118             var coderTools = new GsonProtocolCoderToolset(eventProtocolParams, key);
119
120             logger.info("{}: adding coders for new {}: {}", this, key, coderTools);
121
122             coders.put(key, coderTools);
123
124             addReverseCoder(coderTools, key, reverseKey);
125         }
126     }
127
128     private void addReverseCoder(GsonProtocolCoderToolset coderTools, String key, String reverseKey) {
129         if (reverseCoders.containsKey(reverseKey)) {
130             // There is another controller (different group id/artifact id/topic)
131             // that shares the class and the topic.
132
133             List<ProtocolCoderToolset> toolsets =
134                     reverseCoders.get(reverseKey);
135             var present = false;
136             for (ProtocolCoderToolset parserSet : toolsets) {
137                 // just doublecheck
138                 present = parserSet.getControllerId().equals(key);
139                 if (present) {
140                     /* anomaly */
141                     logger.error(
142                             "{}: unexpected toolset reverse mapping found for {}:{}: {}",
143                             this,
144                             reverseKey,
145                             key,
146                             parserSet);
147                 }
148             }
149
150             if (!present) {
151                 logger.info("{}: adding coder set for {}: {} ", this, reverseKey, coderTools);
152                 toolsets.add(coderTools);
153             }
154         } else {
155             List<ProtocolCoderToolset> toolsets = new ArrayList<>();
156             toolsets.add(coderTools);
157
158             logger.info("{}: adding toolset for reverse key {}: {}", this, reverseKey, toolsets);
159             reverseCoders.put(reverseKey, toolsets);
160         }
161     }
162
163     /**
164      * produces key for indexing toolset entries.
165      *
166      * @param groupId    group id
167      * @param artifactId artifact id
168      * @param topic      topic
169      * @return index key
170      */
171     protected String codersKey(String groupId, String artifactId, String topic) {
172         return groupId + ":" + artifactId + ":" + topic;
173     }
174
175     /**
176      * produces a key for the reverse index.
177      *
178      * @param topic      topic
179      * @param eventClass coded class
180      * @return reverse index key
181      */
182     protected String reverseCodersKey(String topic, String eventClass) {
183         return topic + ":" + eventClass;
184     }
185
186     /**
187      * remove coder.
188      *
189      * @param groupId    group id
190      * @param artifactId artifact id
191      * @param topic      topic
192      * @throws IllegalArgumentException if invalid input
193      */
194     public void remove(String groupId, String artifactId, String topic) {
195
196         if (groupId == null || groupId.isEmpty()) {
197             throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
198         }
199
200         if (artifactId == null || artifactId.isEmpty()) {
201             throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
202         }
203
204         if (topic == null || topic.isEmpty()) {
205             throw new IllegalArgumentException(INVALID_TOPIC_MSG);
206         }
207
208         String key = this.codersKey(groupId, artifactId, topic);
209
210         synchronized (this) {
211             if (coders.containsKey(key)) {
212                 ProtocolCoderToolset coderToolset = coders.remove(key);
213
214                 logger.info("{}: removed toolset for {}: {}", this, key, coderToolset);
215
216                 for (CoderFilters codeFilter : coderToolset.getCoders()) {
217                     String className = codeFilter.getFactClass();
218                     String reverseKey = this.reverseCodersKey(topic, className);
219                     removeReverseCoder(key, reverseKey);
220                 }
221             }
222         }
223     }
224
225     private void removeReverseCoder(String key, String reverseKey) {
226         if (!this.reverseCoders.containsKey(reverseKey)) {
227             return;
228         }
229
230         List<ProtocolCoderToolset> toolsets =
231                 this.reverseCoders.get(reverseKey);
232         Iterator<ProtocolCoderToolset> toolsetsIter =
233                 toolsets.iterator();
234         while (toolsetsIter.hasNext()) {
235             ProtocolCoderToolset toolset = toolsetsIter.next();
236             if (toolset.getControllerId().equals(key)) {
237                 logger.info(
238                         "{}: removed coder from toolset for {} from reverse mapping", this, reverseKey);
239                 toolsetsIter.remove();
240             }
241         }
242
243         if (this.reverseCoders.get(reverseKey).isEmpty()) {
244             logger.info("{}: removing reverse mapping for {}: ", this, reverseKey);
245             this.reverseCoders.remove(reverseKey);
246         }
247     }
248
249     /**
250      * does it support coding.
251      *
252      * @param groupId    group id
253      * @param artifactId artifact id
254      * @param topic      topic
255      * @return true if its is codable
256      */
257     public boolean isCodingSupported(String groupId, String artifactId, String topic) {
258
259         if (groupId == null || groupId.isEmpty()) {
260             throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
261         }
262
263         if (artifactId == null || artifactId.isEmpty()) {
264             throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
265         }
266
267         if (topic == null || topic.isEmpty()) {
268             throw new IllegalArgumentException(INVALID_TOPIC_MSG);
269         }
270
271         String key = this.codersKey(groupId, artifactId, topic);
272         synchronized (this) {
273             return coders.containsKey(key);
274         }
275     }
276
277     /**
278      * decode a json string into an Object.
279      *
280      * @param groupId    group id
281      * @param artifactId artifact id
282      * @param topic      topic
283      * @param json       json string to convert to object
284      * @return the decoded object
285      * @throws IllegalArgumentException      if invalid argument is provided
286      * @throws UnsupportedOperationException if the operation cannot be performed
287      */
288     public Object decode(String groupId, String artifactId, String topic, String json) {
289
290         if (!isCodingSupported(groupId, artifactId, topic)) {
291             throw new IllegalArgumentException(
292                     UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic) + " for encoding");
293         }
294
295         String key = this.codersKey(groupId, artifactId, topic);
296         ProtocolCoderToolset coderTools = coders.get(key);
297         try {
298             Object event = coderTools.decode(json);
299             if (event != null) {
300                 return event;
301             }
302         } catch (Exception e) {
303             logger.debug("{}, cannot decode {}", this, json, e);
304         }
305
306         throw new UnsupportedOperationException("Cannot decode with gson");
307     }
308
309     /**
310      * encode an object into a json string.
311      *
312      * @param groupId    group id
313      * @param artifactId artifact id
314      * @param topic      topic
315      * @param event      object to convert to string
316      * @return the json string
317      * @throws IllegalArgumentException      if invalid argument is provided
318      * @throws UnsupportedOperationException if the operation cannot be performed
319      */
320     public String encode(String groupId, String artifactId, String topic, Object event) {
321
322         if (!isCodingSupported(groupId, artifactId, topic)) {
323             throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
324         }
325
326         if (event == null) {
327             throw new IllegalArgumentException("Unsupported topic:" + topic);
328         }
329
330         // reuse the decoder set, since there must be affinity in the model
331         String key = this.codersKey(groupId, artifactId, topic);
332         return this.encodeInternal(key, event);
333     }
334
335     /**
336      * encode an object into a json string.
337      *
338      * @param topic topic
339      * @param event object to convert to string
340      * @return the json string
341      * @throws IllegalArgumentException      if invalid argument is provided
342      * @throws UnsupportedOperationException if the operation cannot be performed
343      */
344     public String encode(String topic, Object event) {
345
346         if (event == null) {
347             throw new IllegalArgumentException("Invalid encoded class");
348         }
349
350         if (topic == null || topic.isEmpty()) {
351             throw new IllegalArgumentException("Invalid topic");
352         }
353
354         String reverseKey = this.reverseCodersKey(topic, event.getClass().getName());
355         if (!this.reverseCoders.containsKey(reverseKey)) {
356             throw new IllegalArgumentException("no reverse coder has been found");
357         }
358
359         List<ProtocolCoderToolset> toolsets =
360                 this.reverseCoders.get(reverseKey);
361
362         String key =
363                 codersKey(
364                         toolsets.get(0).getGroupId(), toolsets.get(0).getArtifactId(), topic);
365         return this.encodeInternal(key, event);
366     }
367
368     /**
369      * encode an object into a json string.
370      *
371      * @param topic        topic
372      * @param encodedClass object to convert to string
373      * @return the json string
374      * @throws IllegalArgumentException      if invalid argument is provided
375      * @throws UnsupportedOperationException if the operation cannot be performed
376      */
377     public String encode(String topic, Object encodedClass, DroolsController droolsController) {
378
379         if (encodedClass == null) {
380             throw new IllegalArgumentException("Invalid encoded class");
381         }
382
383         if (topic == null || topic.isEmpty()) {
384             throw new IllegalArgumentException("Invalid topic");
385         }
386
387         String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic);
388         return this.encodeInternal(key, encodedClass);
389     }
390
391     /**
392      * encode an object into a json string.
393      *
394      * @param key   identifier
395      * @param event object to convert to string
396      * @return the json string
397      * @throws IllegalArgumentException      if invalid argument is provided
398      * @throws UnsupportedOperationException if the operation cannot be performed
399      */
400     protected String encodeInternal(String key, Object event) {
401
402         logger.debug("{}: encode for {}: {}", this, key, event);    // NOSONAR
403
404         /*
405          * It seems that sonar declares the previous logging line as a security vulnerability
406          * when logging the topic variable.   The static code analysis indicates that
407          * the path starts in org.onap.policy.drools.server.restful.RestManager::decode(),
408          * but the request is rejected if the topic contains invalid characters (the sonar description
409          * mentions "/r/n/t" characters) all of which are validated against in the checkValidNameInput(topic).
410          * Furthermore production instances are assumed not to have debug enabled, nor the REST telemetry API
411          * should be published externally.  An additional note is that Path URLs containing spaces and newlines
412          * will be rejected earlier in the HTTP protocol libraries (jetty) so an URL of the form
413          * "https://../to\npic" won't even make it here.
414          */
415
416         ProtocolCoderToolset coderTools = coders.get(key);
417         try {
418             String json = coderTools.encode(event);
419             if (json != null && !json.isEmpty()) {
420                 return json;
421             }
422         } catch (Exception e) {
423             logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e);
424         }
425
426         throw new UnsupportedOperationException("Cannot decode with gson");
427     }
428
429     /**
430      * Drools creators.
431      *
432      * @param topic        topic
433      * @param encodedClass encoded class
434      * @return list of controllers
435      * @throws IllegalStateException    illegal state
436      * @throws IllegalArgumentException argument
437      */
438     protected List<DroolsController> droolsCreators(String topic, Object encodedClass) {
439
440         List<DroolsController> droolsControllers = new ArrayList<>();
441
442         String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getName());
443         if (!this.reverseCoders.containsKey(reverseKey)) {
444             logger.warn("{}: no reverse mapping for {}", this, reverseKey);
445             return droolsControllers;
446         }
447
448         List<ProtocolCoderToolset> toolsets =
449                 this.reverseCoders.get(reverseKey);
450
451         // There must be multiple toolsets associated with <topic,classname> reverseKey
452         // case 2 different controllers use the same models and register the same encoder for
453         // the same topic.  This is assumed not to occur often but for the purpose of encoding
454         // but there should be no side-effects.  Ownership is crosscheck against classname and
455         // classloader reference.
456
457         if (toolsets == null || toolsets.isEmpty()) {
458             throw new IllegalStateException(
459                     "No Encoders toolsets available for topic "
460                             + topic
461                             + " encoder "
462                             + encodedClass.getClass().getName());
463         }
464
465         for (ProtocolCoderToolset encoderSet : toolsets) {
466             addToolsetControllers(droolsControllers, encodedClass, encoderSet);
467         }
468
469         if (droolsControllers.isEmpty()) {
470             throw new IllegalStateException(
471                     "No Encoders toolsets available for "
472                             + topic
473                             + ":"
474                             + encodedClass.getClass().getName());
475         }
476
477         return droolsControllers;
478     }
479
480     private void addToolsetControllers(List<DroolsController> droolsControllers, Object encodedClass,
481                     ProtocolCoderToolset encoderSet) {
482         // figure out the right toolset
483         String groupId = encoderSet.getGroupId();
484         String artifactId = encoderSet.getArtifactId();
485         List<CoderFilters> coderFilters = encoderSet.getCoders();
486         for (CoderFilters coder : coderFilters) {
487             if (coder.getFactClass().equals(encodedClass.getClass().getName())) {
488                 var droolsController =
489                                 DroolsControllerConstants.getFactory().get(groupId, artifactId, "");
490                 if (droolsController.ownsCoder(
491                         encodedClass.getClass(), coder.getModelClassLoaderHash())) {
492                     droolsControllers.add(droolsController);
493                 }
494             }
495         }
496     }
497
498     /**
499      * get all filters by maven coordinates and topic.
500      *
501      * @param groupId    group id
502      * @param artifactId artifact id
503      * @param topic      topic
504      * @return list of coders
505      * @throws IllegalArgumentException if invalid input
506      */
507     public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) {
508
509         if (!isCodingSupported(groupId, artifactId, topic)) {
510             throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
511         }
512
513         String key = this.codersKey(groupId, artifactId, topic);
514         ProtocolCoderToolset coderTools = coders.get(key);
515         return coderTools.getCoders();
516     }
517
518     /**
519      * get all coders by maven coordinates and topic.
520      *
521      * @param groupId    group id
522      * @param artifactId artifact id
523      * @return list of coders
524      * @throws IllegalArgumentException if invalid input
525      */
526     public List<CoderFilters> getFilters(String groupId, String artifactId) {
527
528         if (groupId == null || groupId.isEmpty()) {
529             throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
530         }
531
532         if (artifactId == null || artifactId.isEmpty()) {
533             throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
534         }
535
536         String key = this.codersKey(groupId, artifactId, "");
537
538         List<CoderFilters> codersFilters = new ArrayList<>();
539         for (Map.Entry<String, ProtocolCoderToolset> entry :
540                 coders.entrySet()) {
541             if (entry.getKey().startsWith(key)) {
542                 codersFilters.addAll(entry.getValue().getCoders());
543             }
544         }
545
546         return codersFilters;
547     }
548
549     /**
550      * get all filters by maven coordinates, topic, and classname.
551      *
552      * @param groupId    group id
553      * @param artifactId artifact id
554      * @param topic      topic
555      * @param classname  classname
556      * @return list of coders
557      * @throws IllegalArgumentException if invalid input
558      */
559     public CoderFilters getFilters(
560             String groupId, String artifactId, String topic, String classname) {
561
562         if (!isCodingSupported(groupId, artifactId, topic)) {
563             throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
564         }
565
566         if (classname == null || classname.isEmpty()) {
567             throw new IllegalArgumentException("classname must be provided");
568         }
569
570         String key = this.codersKey(groupId, artifactId, topic);
571         ProtocolCoderToolset coderTools = coders.get(key);
572         return coderTools.getCoder(classname);
573     }
574
575     /**
576      * get all coders by maven coordinates and topic.
577      *
578      * @param groupId    group id
579      * @param artifactId artifact id
580      * @param topic      topic
581      * @return list of coders
582      * @throws IllegalArgumentException if invalid input
583      */
584     public ProtocolCoderToolset getCoders(
585             String groupId, String artifactId, String topic) {
586
587         if (!isCodingSupported(groupId, artifactId, topic)) {
588             throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
589         }
590
591         String key = this.codersKey(groupId, artifactId, topic);
592         return coders.get(key);
593     }
594
595     /**
596      * get all coders by maven coordinates and topic.
597      *
598      * @param groupId    group id
599      * @param artifactId artifact id
600      * @return list of coders
601      * @throws IllegalArgumentException if invalid input
602      */
603     public List<ProtocolCoderToolset> getCoders(
604             String groupId, String artifactId) {
605
606         if (groupId == null || groupId.isEmpty()) {
607             throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
608         }
609
610         if (artifactId == null || artifactId.isEmpty()) {
611             throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
612         }
613
614         String key = this.codersKey(groupId, artifactId, "");
615
616         List<ProtocolCoderToolset> coderToolset = new ArrayList<>();
617         for (Map.Entry<String, ProtocolCoderToolset> entry :
618                 coders.entrySet()) {
619             if (entry.getKey().startsWith(key)) {
620                 coderToolset.add(entry.getValue());
621             }
622         }
623
624         return coderToolset;
625     }
626
627     /**
628      * get coded based on class and topic.
629      *
630      * @param topic      topic
631      * @param codedClass class
632      * @return list of reverse filters
633      */
634     public List<CoderFilters> getReverseFilters(String topic, String codedClass) {
635
636         if (topic == null || topic.isEmpty()) {
637             throw new IllegalArgumentException(UNSUPPORTED_MSG);
638         }
639
640         if (codedClass == null) {
641             throw new IllegalArgumentException(MISSING_CLASS);
642         }
643
644         String key = this.reverseCodersKey(topic, codedClass);
645         List<ProtocolCoderToolset> toolsets = this.reverseCoders.get(key);
646         if (toolsets == null) {
647             throw new IllegalArgumentException("No Coder found for " + key);
648         }
649
650         List<CoderFilters> coderFilters = new ArrayList<>();
651         for (ProtocolCoderToolset toolset : toolsets) {
652             coderFilters.addAll(toolset.getCoders());
653         }
654
655         return coderFilters;
656     }
657
658     /**
659      * returns group and artifact id of the creator of the encoder.
660      *
661      * @param topic topic
662      * @param fact  fact
663      * @return the drools controller
664      */
665     DroolsController getDroolsController(String topic, Object fact) {
666
667         if (topic == null || topic.isEmpty()) {
668             throw new IllegalArgumentException(UNSUPPORTED_MSG);
669         }
670
671         if (fact == null) {
672             throw new IllegalArgumentException(MISSING_CLASS);
673         }
674
675         List<DroolsController> droolsControllers = droolsCreators(topic, fact);
676
677         if (droolsControllers.isEmpty()) {
678             throw new IllegalArgumentException("Invalid Topic: " + topic);
679         }
680
681         if (droolsControllers.size() > 1) {
682             logger.warn(
683                     "{}: multiple drools-controller {} for {}:{} ",
684                     this,
685                     droolsControllers,
686                     topic,
687                     fact.getClass().getName());
688             // continue
689         }
690         return droolsControllers.get(0);
691     }
692
693     /**
694      * returns group and artifact id of the creator of the encoder.
695      *
696      * @param topic topic
697      * @param fact  fact
698      * @return list of drools controllers
699      */
700     List<DroolsController> getDroolsControllers(String topic, Object fact) {
701
702         if (topic == null || topic.isEmpty()) {
703             throw new IllegalArgumentException(UNSUPPORTED_MSG);
704         }
705
706         if (fact == null) {
707             throw new IllegalArgumentException(MISSING_CLASS);
708         }
709
710         List<DroolsController> droolsControllers = droolsCreators(topic, fact);
711         if (droolsControllers.size() > 1) {
712             // unexpected
713             logger.warn(
714                     "{}: multiple drools-controller {} for {}:{} ",
715                     this,
716                     droolsControllers,
717                     topic,
718                     fact.getClass().getName());
719             // continue
720         }
721         return droolsControllers;
722     }
723 }