2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019-2020 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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.drools.protocol.coders;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
28 import org.onap.policy.drools.controller.DroolsController;
29 import org.onap.policy.drools.controller.DroolsControllerConstants;
30 import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * This protocol Coder that does its best attempt to decode/encode, selecting the best class and best fitted json
38 abstract class GenericEventProtocolCoder {
39 private static final String INVALID_ARTIFACT_ID_MSG = "Invalid artifact id";
40 private static final String INVALID_GROUP_ID_MSG = "Invalid group id";
41 private static final String INVALID_TOPIC_MSG = "Invalid Topic";
42 private static final String UNSUPPORTED_MSG = "Unsupported";
43 private static final String UNSUPPORTED_EX_MSG = "Unsupported:";
44 private static final String MISSING_CLASS = "class must be provided";
46 private static Logger logger = LoggerFactory.getLogger(GenericEventProtocolCoder.class);
49 * Mapping topic:controller-id -> /<protocol-decoder-toolset/> where protocol-coder-toolset contains
50 * a gson-protocol-coder-toolset.
52 protected final HashMap<String, ProtocolCoderToolset> coders =
56 * Mapping topic + classname -> Protocol Set.
58 protected final HashMap<String, List<ProtocolCoderToolset>>
59 reverseCoders = new HashMap<>();
61 GenericEventProtocolCoder() {
68 * @param eventProtocolParams parameter object for event encoder
69 * @throw IllegalArgumentException if an invalid parameter is passed
71 public void add(EventProtocolParams eventProtocolParams) {
72 if (eventProtocolParams.getGroupId() == null || eventProtocolParams.getGroupId().isEmpty()) {
73 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
76 if (eventProtocolParams.getArtifactId() == null || eventProtocolParams.getArtifactId().isEmpty()) {
77 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
80 if (eventProtocolParams.getTopic() == null || eventProtocolParams.getTopic().isEmpty()) {
81 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
84 if (eventProtocolParams.getEventClass() == null) {
85 throw new IllegalArgumentException("Invalid Event Class");
88 String key = this.codersKey(eventProtocolParams.getGroupId(), eventProtocolParams.getArtifactId(),
89 eventProtocolParams.getTopic());
90 String reverseKey = this.reverseCodersKey(eventProtocolParams.getTopic(), eventProtocolParams.getEventClass());
93 if (coders.containsKey(key)) {
94 ProtocolCoderToolset toolset = coders.get(key);
96 logger.info("{}: adding coders for existing {}: {}", this, key, toolset);
100 eventProtocolParams.getEventClass(),
101 eventProtocolParams.getProtocolFilter(),
102 eventProtocolParams.getModelClassLoaderHash());
104 if (!reverseCoders.containsKey(reverseKey)) {
106 "{}: adding new reverse coders (multiple classes case) for {}:{}: {}",
112 List<ProtocolCoderToolset> reverseMappings =
114 reverseMappings.add(toolset);
115 reverseCoders.put(reverseKey, reverseMappings);
120 GsonProtocolCoderToolset coderTools =
121 new GsonProtocolCoderToolset(eventProtocolParams, key);
123 logger.info("{}: adding coders for new {}: {}", this, key, coderTools);
125 coders.put(key, coderTools);
127 addReverseCoder(coderTools, key, reverseKey);
131 private void addReverseCoder(GsonProtocolCoderToolset coderTools, String key, String reverseKey) {
132 if (reverseCoders.containsKey(reverseKey)) {
133 // There is another controller (different group id/artifact id/topic)
134 // that shares the class and the topic.
136 List<ProtocolCoderToolset> toolsets =
137 reverseCoders.get(reverseKey);
138 boolean present = false;
139 for (ProtocolCoderToolset parserSet : toolsets) {
141 present = parserSet.getControllerId().equals(key);
145 "{}: unexpected toolset reverse mapping found for {}:{}: {}",
154 logger.info("{}: adding coder set for {}: {} ", this, reverseKey, coderTools);
155 toolsets.add(coderTools);
158 List<ProtocolCoderToolset> toolsets = new ArrayList<>();
159 toolsets.add(coderTools);
161 logger.info("{}: adding toolset for reverse key {}: {}", this, reverseKey, toolsets);
162 reverseCoders.put(reverseKey, toolsets);
167 * produces key for indexing toolset entries.
169 * @param groupId group id
170 * @param artifactId artifact id
174 protected String codersKey(String groupId, String artifactId, String topic) {
175 return groupId + ":" + artifactId + ":" + topic;
179 * produces a key for the reverse index.
182 * @param eventClass coded class
183 * @return reverse index key
185 protected String reverseCodersKey(String topic, String eventClass) {
186 return topic + ":" + eventClass;
192 * @param groupId group id
193 * @param artifactId artifact id
195 * @throws IllegalArgumentException if invalid input
197 public void remove(String groupId, String artifactId, String topic) {
199 if (groupId == null || groupId.isEmpty()) {
200 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
203 if (artifactId == null || artifactId.isEmpty()) {
204 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
207 if (topic == null || topic.isEmpty()) {
208 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
211 String key = this.codersKey(groupId, artifactId, topic);
213 synchronized (this) {
214 if (coders.containsKey(key)) {
215 ProtocolCoderToolset coderToolset = coders.remove(key);
217 logger.info("{}: removed toolset for {}: {}", this, key, coderToolset);
219 for (CoderFilters codeFilter : coderToolset.getCoders()) {
220 String className = codeFilter.getCodedClass();
221 String reverseKey = this.reverseCodersKey(topic, className);
222 removeReverseCoder(key, reverseKey);
228 private void removeReverseCoder(String key, String reverseKey) {
229 if (!this.reverseCoders.containsKey(reverseKey)) {
233 List<ProtocolCoderToolset> toolsets =
234 this.reverseCoders.get(reverseKey);
235 Iterator<ProtocolCoderToolset> toolsetsIter =
237 while (toolsetsIter.hasNext()) {
238 ProtocolCoderToolset toolset = toolsetsIter.next();
239 if (toolset.getControllerId().equals(key)) {
241 "{}: removed coder from toolset for {} from reverse mapping", this, reverseKey);
242 toolsetsIter.remove();
246 if (this.reverseCoders.get(reverseKey).isEmpty()) {
247 logger.info("{}: removing reverse mapping for {}: ", this, reverseKey);
248 this.reverseCoders.remove(reverseKey);
253 * does it support coding.
255 * @param groupId group id
256 * @param artifactId artifact id
258 * @return true if its is codable
260 public boolean isCodingSupported(String groupId, String artifactId, String topic) {
262 if (groupId == null || groupId.isEmpty()) {
263 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
266 if (artifactId == null || artifactId.isEmpty()) {
267 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
270 if (topic == null || topic.isEmpty()) {
271 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
274 String key = this.codersKey(groupId, artifactId, topic);
275 synchronized (this) {
276 return coders.containsKey(key);
281 * decode a json string into an Object.
283 * @param groupId group id
284 * @param artifactId artifact id
286 * @param json json string to convert to object
287 * @return the decoded object
288 * @throws IllegalArgumentException if invalid argument is provided
289 * @throws UnsupportedOperationException if the operation cannot be performed
291 public Object decode(String groupId, String artifactId, String topic, String json) {
293 if (!isCodingSupported(groupId, artifactId, topic)) {
294 throw new IllegalArgumentException(
295 UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic) + " for encoding");
298 String key = this.codersKey(groupId, artifactId, topic);
299 ProtocolCoderToolset coderTools = coders.get(key);
301 Object event = coderTools.decode(json);
305 } catch (Exception e) {
306 logger.debug("{}, cannot decode {}", this, json, e);
309 throw new UnsupportedOperationException("Cannot decode with gson");
313 * encode an object into a json string.
315 * @param groupId group id
316 * @param artifactId artifact id
318 * @param event object to convert to string
319 * @return the json string
320 * @throws IllegalArgumentException if invalid argument is provided
321 * @throws UnsupportedOperationException if the operation cannot be performed
323 public String encode(String groupId, String artifactId, String topic, Object event) {
325 if (!isCodingSupported(groupId, artifactId, topic)) {
326 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
330 throw new IllegalArgumentException("Unsupported topic:" + topic);
333 // reuse the decoder set, since there must be affinity in the model
334 String key = this.codersKey(groupId, artifactId, topic);
335 return this.encodeInternal(key, event);
339 * encode an object into a json string.
342 * @param event object to convert to string
343 * @return the json string
344 * @throws IllegalArgumentException if invalid argument is provided
345 * @throws UnsupportedOperationException if the operation cannot be performed
347 public String encode(String topic, Object event) {
350 throw new IllegalArgumentException("Invalid encoded class");
353 if (topic == null || topic.isEmpty()) {
354 throw new IllegalArgumentException("Invalid topic");
357 String reverseKey = this.reverseCodersKey(topic, event.getClass().getName());
358 if (!this.reverseCoders.containsKey(reverseKey)) {
359 throw new IllegalArgumentException("no reverse coder has been found");
362 List<ProtocolCoderToolset> toolsets =
363 this.reverseCoders.get(reverseKey);
367 toolsets.get(0).getGroupId(), toolsets.get(0).getArtifactId(), topic);
368 return this.encodeInternal(key, event);
372 * encode an object into a json string.
375 * @param encodedClass object to convert to string
376 * @return the json string
377 * @throws IllegalArgumentException if invalid argument is provided
378 * @throws UnsupportedOperationException if the operation cannot be performed
380 public String encode(String topic, Object encodedClass, DroolsController droolsController) {
382 if (encodedClass == null) {
383 throw new IllegalArgumentException("Invalid encoded class");
386 if (topic == null || topic.isEmpty()) {
387 throw new IllegalArgumentException("Invalid topic");
390 String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic);
391 return this.encodeInternal(key, encodedClass);
395 * encode an object into a json string.
397 * @param key identifier
398 * @param event object to convert to string
399 * @return the json string
400 * @throws IllegalArgumentException if invalid argument is provided
401 * @throws UnsupportedOperationException if the operation cannot be performed
403 protected String encodeInternal(String key, Object event) {
405 logger.debug("{}: encode for {}: {}", this, key, event);
407 ProtocolCoderToolset coderTools = coders.get(key);
409 String json = coderTools.encode(event);
410 if (json != null && !json.isEmpty()) {
413 } catch (Exception e) {
414 logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e);
417 throw new UnsupportedOperationException("Cannot decode with gson");
424 * @param encodedClass encoded class
425 * @return list of controllers
426 * @throws IllegalStateException illegal state
427 * @throws IllegalArgumentException argument
429 protected List<DroolsController> droolsCreators(String topic, Object encodedClass) {
431 List<DroolsController> droolsControllers = new ArrayList<>();
433 String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getName());
434 if (!this.reverseCoders.containsKey(reverseKey)) {
435 logger.warn("{}: no reverse mapping for {}", this, reverseKey);
436 return droolsControllers;
439 List<ProtocolCoderToolset> toolsets =
440 this.reverseCoders.get(reverseKey);
442 // There must be multiple toolsets associated with <topic,classname> reverseKey
443 // case 2 different controllers use the same models and register the same encoder for
444 // the same topic. This is assumed not to occur often but for the purpose of encoding
445 // but there should be no side-effects. Ownership is crosscheck against classname and
446 // classloader reference.
448 if (toolsets == null || toolsets.isEmpty()) {
449 throw new IllegalStateException(
450 "No Encoders toolsets available for topic "
453 + encodedClass.getClass().getName());
456 for (ProtocolCoderToolset encoderSet : toolsets) {
457 addToolsetControllers(droolsControllers, encodedClass, encoderSet);
460 if (droolsControllers.isEmpty()) {
461 throw new IllegalStateException(
462 "No Encoders toolsets available for "
465 + encodedClass.getClass().getName());
468 return droolsControllers;
471 private void addToolsetControllers(List<DroolsController> droolsControllers, Object encodedClass,
472 ProtocolCoderToolset encoderSet) {
473 // figure out the right toolset
474 String groupId = encoderSet.getGroupId();
475 String artifactId = encoderSet.getArtifactId();
476 List<CoderFilters> coderFilters = encoderSet.getCoders();
477 for (CoderFilters coder : coderFilters) {
478 if (coder.getCodedClass().equals(encodedClass.getClass().getName())) {
479 DroolsController droolsController =
480 DroolsControllerConstants.getFactory().get(groupId, artifactId, "");
481 if (droolsController.ownsCoder(
482 encodedClass.getClass(), coder.getModelClassLoaderHash())) {
483 droolsControllers.add(droolsController);
490 * get all filters by maven coordinates and topic.
492 * @param groupId group id
493 * @param artifactId artifact id
495 * @return list of coders
496 * @throws IllegalArgumentException if invalid input
498 public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) {
500 if (!isCodingSupported(groupId, artifactId, topic)) {
501 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
504 String key = this.codersKey(groupId, artifactId, topic);
505 ProtocolCoderToolset coderTools = coders.get(key);
506 return coderTools.getCoders();
510 * get all coders by maven coordinates and topic.
512 * @param groupId group id
513 * @param artifactId artifact id
514 * @return list of coders
515 * @throws IllegalArgumentException if invalid input
517 public List<CoderFilters> getFilters(String groupId, String artifactId) {
519 if (groupId == null || groupId.isEmpty()) {
520 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
523 if (artifactId == null || artifactId.isEmpty()) {
524 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
527 String key = this.codersKey(groupId, artifactId, "");
529 List<CoderFilters> codersFilters = new ArrayList<>();
530 for (Map.Entry<String, ProtocolCoderToolset> entry :
532 if (entry.getKey().startsWith(key)) {
533 codersFilters.addAll(entry.getValue().getCoders());
537 return codersFilters;
541 * get all filters by maven coordinates, topic, and classname.
543 * @param groupId group id
544 * @param artifactId artifact id
546 * @param classname classname
547 * @return list of coders
548 * @throws IllegalArgumentException if invalid input
550 public CoderFilters getFilters(
551 String groupId, String artifactId, String topic, String classname) {
553 if (!isCodingSupported(groupId, artifactId, topic)) {
554 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
557 if (classname == null || classname.isEmpty()) {
558 throw new IllegalArgumentException("classname must be provided");
561 String key = this.codersKey(groupId, artifactId, topic);
562 ProtocolCoderToolset coderTools = coders.get(key);
563 return coderTools.getCoder(classname);
567 * get all coders by maven coordinates and topic.
569 * @param groupId group id
570 * @param artifactId artifact id
572 * @return list of coders
573 * @throws IllegalArgumentException if invalid input
575 public ProtocolCoderToolset getCoders(
576 String groupId, String artifactId, String topic) {
578 if (!isCodingSupported(groupId, artifactId, topic)) {
579 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
582 String key = this.codersKey(groupId, artifactId, topic);
583 return coders.get(key);
587 * get all coders by maven coordinates and topic.
589 * @param groupId group id
590 * @param artifactId artifact id
591 * @return list of coders
592 * @throws IllegalArgumentException if invalid input
594 public List<ProtocolCoderToolset> getCoders(
595 String groupId, String artifactId) {
597 if (groupId == null || groupId.isEmpty()) {
598 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
601 if (artifactId == null || artifactId.isEmpty()) {
602 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
605 String key = this.codersKey(groupId, artifactId, "");
607 List<ProtocolCoderToolset> coderToolset = new ArrayList<>();
608 for (Map.Entry<String, ProtocolCoderToolset> entry :
610 if (entry.getKey().startsWith(key)) {
611 coderToolset.add(entry.getValue());
619 * get coded based on class and topic.
622 * @param codedClass class
623 * @return list of reverse filters
625 public List<CoderFilters> getReverseFilters(String topic, String codedClass) {
627 if (topic == null || topic.isEmpty()) {
628 throw new IllegalArgumentException(UNSUPPORTED_MSG);
631 if (codedClass == null) {
632 throw new IllegalArgumentException(MISSING_CLASS);
635 String key = this.reverseCodersKey(topic, codedClass);
636 List<ProtocolCoderToolset> toolsets = this.reverseCoders.get(key);
637 if (toolsets == null) {
638 throw new IllegalArgumentException("No Coder found for " + key);
641 List<CoderFilters> coderFilters = new ArrayList<>();
642 for (ProtocolCoderToolset toolset : toolsets) {
643 coderFilters.addAll(toolset.getCoders());
650 * returns group and artifact id of the creator of the encoder.
654 * @return the drools controller
656 DroolsController getDroolsController(String topic, Object fact) {
658 if (topic == null || topic.isEmpty()) {
659 throw new IllegalArgumentException(UNSUPPORTED_MSG);
663 throw new IllegalArgumentException(MISSING_CLASS);
666 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
668 if (droolsControllers.isEmpty()) {
669 throw new IllegalArgumentException("Invalid Topic: " + topic);
672 if (droolsControllers.size() > 1) {
674 "{}: multiple drools-controller {} for {}:{} ",
678 fact.getClass().getName());
681 return droolsControllers.get(0);
685 * returns group and artifact id of the creator of the encoder.
689 * @return list of drools controllers
691 List<DroolsController> getDroolsControllers(String topic, Object fact) {
693 if (topic == null || topic.isEmpty()) {
694 throw new IllegalArgumentException(UNSUPPORTED_MSG);
698 throw new IllegalArgumentException(MISSING_CLASS);
701 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
702 if (droolsControllers.size() > 1) {
705 "{}: multiple drools-controller {} for {}:{} ",
709 fact.getClass().getName());
712 return droolsControllers;
716 public String toString() {
717 return "GenericEventProtocolCoder [coders="
720 + reverseCoders.keySet()