2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 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
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 {}:{}: {}",
156 logger.info("{}: adding coder set for {}: {} ", this, reverseKey, coderTools);
157 toolsets.add(coderTools);
160 List<ProtocolCoderToolset> toolsets = new ArrayList<>();
161 toolsets.add(coderTools);
163 logger.info("{}: adding toolset for reverse key {}: {}", this, reverseKey, toolsets);
164 reverseCoders.put(reverseKey, toolsets);
169 * produces key for indexing toolset entries.
171 * @param groupId group id
172 * @param artifactId artifact id
176 protected String codersKey(String groupId, String artifactId, String topic) {
177 return groupId + ":" + artifactId + ":" + topic;
181 * produces a key for the reverse index.
184 * @param eventClass coded class
185 * @return reverse index key
187 protected String reverseCodersKey(String topic, String eventClass) {
188 return topic + ":" + eventClass;
194 * @param groupId group id
195 * @param artifactId artifact id
197 * @throws IllegalArgumentException if invalid input
199 public void remove(String groupId, String artifactId, String topic) {
201 if (groupId == null || groupId.isEmpty()) {
202 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
205 if (artifactId == null || artifactId.isEmpty()) {
206 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
209 if (topic == null || topic.isEmpty()) {
210 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
213 String key = this.codersKey(groupId, artifactId, topic);
215 synchronized (this) {
216 if (coders.containsKey(key)) {
217 ProtocolCoderToolset coderToolset = coders.remove(key);
219 logger.info("{}: removed toolset for {}: {}", this, key, coderToolset);
221 for (CoderFilters codeFilter : coderToolset.getCoders()) {
222 String className = codeFilter.getCodedClass();
223 String reverseKey = this.reverseCodersKey(topic, className);
224 removeReverseCoder(key, reverseKey);
230 private void removeReverseCoder(String key, String reverseKey) {
231 if (!this.reverseCoders.containsKey(reverseKey)) {
235 List<ProtocolCoderToolset> toolsets =
236 this.reverseCoders.get(reverseKey);
237 Iterator<ProtocolCoderToolset> toolsetsIter =
239 while (toolsetsIter.hasNext()) {
240 ProtocolCoderToolset toolset = toolsetsIter.next();
241 if (toolset.getControllerId().equals(key)) {
243 "{}: removed coder from toolset for {} from reverse mapping", this, reverseKey);
244 toolsetsIter.remove();
248 if (this.reverseCoders.get(reverseKey).isEmpty()) {
249 logger.info("{}: removing reverse mapping for {}: ", this, reverseKey);
250 this.reverseCoders.remove(reverseKey);
255 * does it support coding.
257 * @param groupId group id
258 * @param artifactId artifact id
260 * @return true if its is codable
262 public boolean isCodingSupported(String groupId, String artifactId, String topic) {
264 if (groupId == null || groupId.isEmpty()) {
265 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
268 if (artifactId == null || artifactId.isEmpty()) {
269 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
272 if (topic == null || topic.isEmpty()) {
273 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
276 String key = this.codersKey(groupId, artifactId, topic);
277 synchronized (this) {
278 return coders.containsKey(key);
283 * decode a json string into an Object.
285 * @param groupId group id
286 * @param artifactId artifact id
288 * @param json json string to convert to object
289 * @return the decoded object
290 * @throws IllegalArgumentException if invalid argument is provided
291 * @throws UnsupportedOperationException if the operation cannot be performed
293 public Object decode(String groupId, String artifactId, String topic, String json) {
295 if (!isCodingSupported(groupId, artifactId, topic)) {
296 throw new IllegalArgumentException(
297 UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic) + " for encoding");
300 String key = this.codersKey(groupId, artifactId, topic);
301 ProtocolCoderToolset coderTools = coders.get(key);
303 Object event = coderTools.decode(json);
307 } catch (Exception e) {
308 logger.debug("{}, cannot decode {}", this, json, e);
311 throw new UnsupportedOperationException("Cannot decode with gson");
315 * encode an object into a json string.
317 * @param groupId group id
318 * @param artifactId artifact id
320 * @param event object to convert to string
321 * @return the json string
322 * @throws IllegalArgumentException if invalid argument is provided
323 * @throws UnsupportedOperationException if the operation cannot be performed
325 public String encode(String groupId, String artifactId, String topic, Object event) {
327 if (!isCodingSupported(groupId, artifactId, topic)) {
328 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
332 throw new IllegalArgumentException("Unsupported topic:" + topic);
335 // reuse the decoder set, since there must be affinity in the model
336 String key = this.codersKey(groupId, artifactId, topic);
337 return this.encodeInternal(key, event);
341 * encode an object into a json string.
344 * @param event object to convert to string
345 * @return the json string
346 * @throws IllegalArgumentException if invalid argument is provided
347 * @throws UnsupportedOperationException if the operation cannot be performed
349 public String encode(String topic, Object event) {
352 throw new IllegalArgumentException("Invalid encoded class");
355 if (topic == null || topic.isEmpty()) {
356 throw new IllegalArgumentException("Invalid topic");
359 String reverseKey = this.reverseCodersKey(topic, event.getClass().getName());
360 if (!this.reverseCoders.containsKey(reverseKey)) {
361 throw new IllegalArgumentException("no reverse coder has been found");
364 List<ProtocolCoderToolset> toolsets =
365 this.reverseCoders.get(reverseKey);
369 toolsets.get(0).getGroupId(), toolsets.get(0).getArtifactId(), topic);
370 return this.encodeInternal(key, event);
374 * encode an object into a json string.
377 * @param encodedClass object to convert to string
378 * @return the json string
379 * @throws IllegalArgumentException if invalid argument is provided
380 * @throws UnsupportedOperationException if the operation cannot be performed
382 public String encode(String topic, Object encodedClass, DroolsController droolsController) {
384 if (encodedClass == null) {
385 throw new IllegalArgumentException("Invalid encoded class");
388 if (topic == null || topic.isEmpty()) {
389 throw new IllegalArgumentException("Invalid topic");
392 String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic);
393 return this.encodeInternal(key, encodedClass);
397 * encode an object into a json string.
399 * @param key identifier
400 * @param event object to convert to string
401 * @return the json string
402 * @throws IllegalArgumentException if invalid argument is provided
403 * @throws UnsupportedOperationException if the operation cannot be performed
405 protected String encodeInternal(String key, Object event) {
407 logger.debug("{}: encode for {}: {}", this, key, event);
409 ProtocolCoderToolset coderTools = coders.get(key);
411 String json = coderTools.encode(event);
412 if (json != null && !json.isEmpty()) {
415 } catch (Exception e) {
416 logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e);
419 throw new UnsupportedOperationException("Cannot decode with gson");
426 * @param encodedClass encoded class
427 * @return list of controllers
428 * @throws IllegalStateException illegal state
429 * @throws IllegalArgumentException argument
431 protected List<DroolsController> droolsCreators(String topic, Object encodedClass) {
433 List<DroolsController> droolsControllers = new ArrayList<>();
435 String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getName());
436 if (!this.reverseCoders.containsKey(reverseKey)) {
437 logger.warn("{}: no reverse mapping for {}", this, reverseKey);
438 return droolsControllers;
441 List<ProtocolCoderToolset> toolsets =
442 this.reverseCoders.get(reverseKey);
444 // There must be multiple toolsets associated with <topic,classname> reverseKey
445 // case 2 different controllers use the same models and register the same encoder for
446 // the same topic. This is assumed not to occur often but for the purpose of encoding
447 // but there should be no side-effects. Ownership is crosscheck against classname and
448 // classloader reference.
450 if (toolsets == null || toolsets.isEmpty()) {
451 throw new IllegalStateException(
452 "No Encoders toolsets available for topic "
455 + encodedClass.getClass().getName());
458 for (ProtocolCoderToolset encoderSet : toolsets) {
459 addToolsetControllers(droolsControllers, encodedClass, encoderSet);
462 if (droolsControllers.isEmpty()) {
463 throw new IllegalStateException(
464 "No Encoders toolsets available for "
467 + encodedClass.getClass().getName());
470 return droolsControllers;
473 private void addToolsetControllers(List<DroolsController> droolsControllers, Object encodedClass,
474 ProtocolCoderToolset encoderSet) {
475 // figure out the right toolset
476 String groupId = encoderSet.getGroupId();
477 String artifactId = encoderSet.getArtifactId();
478 List<CoderFilters> coderFilters = encoderSet.getCoders();
479 for (CoderFilters coder : coderFilters) {
480 if (coder.getCodedClass().equals(encodedClass.getClass().getName())) {
481 DroolsController droolsController =
482 DroolsControllerConstants.getFactory().get(groupId, artifactId, "");
483 if (droolsController.ownsCoder(
484 encodedClass.getClass(), coder.getModelClassLoaderHash())) {
485 droolsControllers.add(droolsController);
492 * get all filters by maven coordinates and topic.
494 * @param groupId group id
495 * @param artifactId artifact id
497 * @return list of coders
498 * @throws IllegalArgumentException if invalid input
500 public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) {
502 if (!isCodingSupported(groupId, artifactId, topic)) {
503 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
506 String key = this.codersKey(groupId, artifactId, topic);
507 ProtocolCoderToolset coderTools = coders.get(key);
508 return coderTools.getCoders();
512 * get all coders by maven coordinates and topic.
514 * @param groupId group id
515 * @param artifactId artifact id
516 * @return list of coders
517 * @throws IllegalArgumentException if invalid input
519 public List<CoderFilters> getFilters(String groupId, String artifactId) {
521 if (groupId == null || groupId.isEmpty()) {
522 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
525 if (artifactId == null || artifactId.isEmpty()) {
526 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
529 String key = this.codersKey(groupId, artifactId, "");
531 List<CoderFilters> codersFilters = new ArrayList<>();
532 for (Map.Entry<String, ProtocolCoderToolset> entry :
534 if (entry.getKey().startsWith(key)) {
535 codersFilters.addAll(entry.getValue().getCoders());
539 return codersFilters;
543 * get all filters by maven coordinates, topic, and classname.
545 * @param groupId group id
546 * @param artifactId artifact id
548 * @param classname classname
549 * @return list of coders
550 * @throws IllegalArgumentException if invalid input
552 public CoderFilters getFilters(
553 String groupId, String artifactId, String topic, String classname) {
555 if (!isCodingSupported(groupId, artifactId, topic)) {
556 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
559 if (classname == null || classname.isEmpty()) {
560 throw new IllegalArgumentException("classname must be provided");
563 String key = this.codersKey(groupId, artifactId, topic);
564 ProtocolCoderToolset coderTools = coders.get(key);
565 return coderTools.getCoder(classname);
569 * get all coders by maven coordinates and topic.
571 * @param groupId group id
572 * @param artifactId artifact id
574 * @return list of coders
575 * @throws IllegalArgumentException if invalid input
577 public ProtocolCoderToolset getCoders(
578 String groupId, String artifactId, String topic) {
580 if (!isCodingSupported(groupId, artifactId, topic)) {
581 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
584 String key = this.codersKey(groupId, artifactId, topic);
585 return coders.get(key);
589 * get all coders by maven coordinates and topic.
591 * @param groupId group id
592 * @param artifactId artifact id
593 * @return list of coders
594 * @throws IllegalArgumentException if invalid input
596 public List<ProtocolCoderToolset> getCoders(
597 String groupId, String artifactId) {
599 if (groupId == null || groupId.isEmpty()) {
600 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
603 if (artifactId == null || artifactId.isEmpty()) {
604 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
607 String key = this.codersKey(groupId, artifactId, "");
609 List<ProtocolCoderToolset> coderToolset = new ArrayList<>();
610 for (Map.Entry<String, ProtocolCoderToolset> entry :
612 if (entry.getKey().startsWith(key)) {
613 coderToolset.add(entry.getValue());
621 * get coded based on class and topic.
624 * @param codedClass class
625 * @return list of reverse filters
627 public List<CoderFilters> getReverseFilters(String topic, String codedClass) {
629 if (topic == null || topic.isEmpty()) {
630 throw new IllegalArgumentException(UNSUPPORTED_MSG);
633 if (codedClass == null) {
634 throw new IllegalArgumentException(MISSING_CLASS);
637 String key = this.reverseCodersKey(topic, codedClass);
638 List<ProtocolCoderToolset> toolsets = this.reverseCoders.get(key);
639 if (toolsets == null) {
640 throw new IllegalArgumentException("No Coder found for " + key);
643 List<CoderFilters> coderFilters = new ArrayList<>();
644 for (ProtocolCoderToolset toolset : toolsets) {
645 coderFilters.addAll(toolset.getCoders());
652 * returns group and artifact id of the creator of the encoder.
656 * @return the drools controller
658 DroolsController getDroolsController(String topic, Object fact) {
660 if (topic == null || topic.isEmpty()) {
661 throw new IllegalArgumentException(UNSUPPORTED_MSG);
665 throw new IllegalArgumentException(MISSING_CLASS);
668 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
670 if (droolsControllers.isEmpty()) {
671 throw new IllegalArgumentException("Invalid Topic: " + topic);
674 if (droolsControllers.size() > 1) {
676 "{}: multiple drools-controller {} for {}:{} ",
680 fact.getClass().getName());
683 return droolsControllers.get(0);
687 * returns group and artifact id of the creator of the encoder.
691 * @return list of drools controllers
693 List<DroolsController> getDroolsControllers(String topic, Object fact) {
695 if (topic == null || topic.isEmpty()) {
696 throw new IllegalArgumentException(UNSUPPORTED_MSG);
700 throw new IllegalArgumentException(MISSING_CLASS);
703 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
704 if (droolsControllers.size() > 1) {
707 "{}: multiple drools-controller {} for {}:{} ",
711 fact.getClass().getName());
714 return droolsControllers;
718 public String toString() {
719 return "GenericEventProtocolCoder [coders="
722 + reverseCoders.keySet()