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 if (reverseCoders.containsKey(reverseKey)) {
128 // There is another controller (different group id/artifact id/topic)
129 // that shares the class and the topic.
131 List<ProtocolCoderToolset> toolsets =
132 reverseCoders.get(reverseKey);
133 boolean present = false;
134 for (ProtocolCoderToolset parserSet : toolsets) {
136 present = parserSet.getControllerId().equals(key);
140 "{}: unexpected toolset reverse mapping found for {}:{}: {}",
151 logger.info("{}: adding coder set for {}: {} ", this, reverseKey, coderTools);
152 toolsets.add(coderTools);
155 List<ProtocolCoderToolset> toolsets = new ArrayList<>();
156 toolsets.add(coderTools);
158 logger.info("{}: adding toolset for reverse key {}: {}", this, reverseKey, toolsets);
159 reverseCoders.put(reverseKey, toolsets);
165 * produces key for indexing toolset entries.
167 * @param groupId group id
168 * @param artifactId artifact id
172 protected String codersKey(String groupId, String artifactId, String topic) {
173 return groupId + ":" + artifactId + ":" + topic;
177 * produces a key for the reverse index.
180 * @param eventClass coded class
181 * @return reverse index key
183 protected String reverseCodersKey(String topic, String eventClass) {
184 return topic + ":" + eventClass;
190 * @param groupId group id
191 * @param artifactId artifact id
193 * @throws IllegalArgumentException if invalid input
195 public void remove(String groupId, String artifactId, String topic) {
197 if (groupId == null || groupId.isEmpty()) {
198 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
201 if (artifactId == null || artifactId.isEmpty()) {
202 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
205 if (topic == null || topic.isEmpty()) {
206 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
209 String key = this.codersKey(groupId, artifactId, topic);
211 synchronized (this) {
212 if (coders.containsKey(key)) {
213 ProtocolCoderToolset coderToolset = coders.remove(key);
215 logger.info("{}: removed toolset for {}: {}", this, key, coderToolset);
217 for (CoderFilters codeFilter : coderToolset.getCoders()) {
218 String className = codeFilter.getCodedClass();
219 String reverseKey = this.reverseCodersKey(topic, className);
220 if (this.reverseCoders.containsKey(reverseKey)) {
221 List<ProtocolCoderToolset> toolsets =
222 this.reverseCoders.get(reverseKey);
223 Iterator<ProtocolCoderToolset> toolsetsIter =
225 while (toolsetsIter.hasNext()) {
226 ProtocolCoderToolset toolset = toolsetsIter.next();
227 if (toolset.getControllerId().equals(key)) {
229 "{}: removed coder from toolset for {} from reverse mapping", this, reverseKey);
230 toolsetsIter.remove();
234 if (this.reverseCoders.get(reverseKey).isEmpty()) {
235 logger.info("{}: removing reverse mapping for {}: ", this, reverseKey);
236 this.reverseCoders.remove(reverseKey);
245 * does it support coding.
247 * @param groupId group id
248 * @param artifactId artifact id
250 * @return true if its is codable
252 public boolean isCodingSupported(String groupId, String artifactId, String topic) {
254 if (groupId == null || groupId.isEmpty()) {
255 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
258 if (artifactId == null || artifactId.isEmpty()) {
259 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
262 if (topic == null || topic.isEmpty()) {
263 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
266 String key = this.codersKey(groupId, artifactId, topic);
267 synchronized (this) {
268 return coders.containsKey(key);
273 * decode a json string into an Object.
275 * @param groupId group id
276 * @param artifactId artifact id
278 * @param json json string to convert to object
279 * @return the decoded object
280 * @throws IllegalArgumentException if invalid argument is provided
281 * @throws UnsupportedOperationException if the operation cannot be performed
283 public Object decode(String groupId, String artifactId, String topic, String json) {
285 if (!isCodingSupported(groupId, artifactId, topic)) {
286 throw new IllegalArgumentException(
287 UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic) + " for encoding");
290 String key = this.codersKey(groupId, artifactId, topic);
291 ProtocolCoderToolset coderTools = coders.get(key);
293 Object event = coderTools.decode(json);
297 } catch (Exception e) {
298 logger.debug("{}, cannot decode {}", this, json, e);
301 throw new UnsupportedOperationException("Cannot decode with gson");
305 * encode an object into a json string.
307 * @param groupId group id
308 * @param artifactId artifact id
310 * @param event object to convert to string
311 * @return the json string
312 * @throws IllegalArgumentException if invalid argument is provided
313 * @throws UnsupportedOperationException if the operation cannot be performed
315 public String encode(String groupId, String artifactId, String topic, Object event) {
317 if (!isCodingSupported(groupId, artifactId, topic)) {
318 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
322 throw new IllegalArgumentException("Unsupported topic:" + topic);
325 // reuse the decoder set, since there must be affinity in the model
326 String key = this.codersKey(groupId, artifactId, topic);
327 return this.encodeInternal(key, event);
331 * encode an object into a json string.
334 * @param event object to convert to string
335 * @return the json string
336 * @throws IllegalArgumentException if invalid argument is provided
337 * @throws UnsupportedOperationException if the operation cannot be performed
339 public String encode(String topic, Object event) {
342 throw new IllegalArgumentException("Invalid encoded class");
345 if (topic == null || topic.isEmpty()) {
346 throw new IllegalArgumentException("Invalid topic");
349 String reverseKey = this.reverseCodersKey(topic, event.getClass().getName());
350 if (!this.reverseCoders.containsKey(reverseKey)) {
351 throw new IllegalArgumentException("no reverse coder has been found");
354 List<ProtocolCoderToolset> toolsets =
355 this.reverseCoders.get(reverseKey);
359 toolsets.get(0).getGroupId(), toolsets.get(0).getArtifactId(), topic);
360 return this.encodeInternal(key, event);
364 * encode an object into a json string.
367 * @param encodedClass object to convert to string
368 * @return the json string
369 * @throws IllegalArgumentException if invalid argument is provided
370 * @throws UnsupportedOperationException if the operation cannot be performed
372 public String encode(String topic, Object encodedClass, DroolsController droolsController) {
374 if (encodedClass == null) {
375 throw new IllegalArgumentException("Invalid encoded class");
378 if (topic == null || topic.isEmpty()) {
379 throw new IllegalArgumentException("Invalid topic");
382 String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic);
383 return this.encodeInternal(key, encodedClass);
387 * encode an object into a json string.
389 * @param key identifier
390 * @param event object to convert to string
391 * @return the json string
392 * @throws IllegalArgumentException if invalid argument is provided
393 * @throws UnsupportedOperationException if the operation cannot be performed
395 protected String encodeInternal(String key, Object event) {
397 logger.debug("{}: encode for {}: {}", this, key, event);
399 ProtocolCoderToolset coderTools = coders.get(key);
401 String json = coderTools.encode(event);
402 if (json != null && !json.isEmpty()) {
405 } catch (Exception e) {
406 logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e);
409 throw new UnsupportedOperationException("Cannot decode with gson");
416 * @param encodedClass encoded class
417 * @return list of controllers
418 * @throws IllegalStateException illegal state
419 * @throws IllegalArgumentException argument
421 protected List<DroolsController> droolsCreators(String topic, Object encodedClass) {
423 List<DroolsController> droolsControllers = new ArrayList<>();
425 String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getName());
426 if (!this.reverseCoders.containsKey(reverseKey)) {
427 logger.warn("{}: no reverse mapping for {}", this, reverseKey);
428 return droolsControllers;
431 List<ProtocolCoderToolset> toolsets =
432 this.reverseCoders.get(reverseKey);
434 // There must be multiple toolsets associated with <topic,classname> reverseKey
435 // case 2 different controllers use the same models and register the same encoder for
436 // the same topic. This is assumed not to occur often but for the purpose of encoding
437 // but there should be no side-effects. Ownership is crosscheck against classname and
438 // classloader reference.
440 if (toolsets == null || toolsets.isEmpty()) {
441 throw new IllegalStateException(
442 "No Encoders toolsets available for topic "
445 + encodedClass.getClass().getName());
448 for (ProtocolCoderToolset encoderSet : toolsets) {
449 // figure out the right toolset
450 String groupId = encoderSet.getGroupId();
451 String artifactId = encoderSet.getArtifactId();
452 List<CoderFilters> coderFilters = encoderSet.getCoders();
453 for (CoderFilters coder : coderFilters) {
454 if (coder.getCodedClass().equals(encodedClass.getClass().getName())) {
455 DroolsController droolsController =
456 DroolsControllerConstants.getFactory().get(groupId, artifactId, "");
457 if (droolsController.ownsCoder(
458 encodedClass.getClass(), coder.getModelClassLoaderHash())) {
459 droolsControllers.add(droolsController);
465 if (droolsControllers.isEmpty()) {
466 throw new IllegalStateException(
467 "No Encoders toolsets available for "
470 + encodedClass.getClass().getName());
473 return droolsControllers;
477 * get all filters by maven coordinates and topic.
479 * @param groupId group id
480 * @param artifactId artifact id
482 * @return list of coders
483 * @throws IllegalArgumentException if invalid input
485 public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) {
487 if (!isCodingSupported(groupId, artifactId, topic)) {
488 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
491 String key = this.codersKey(groupId, artifactId, topic);
492 ProtocolCoderToolset coderTools = coders.get(key);
493 return coderTools.getCoders();
497 * get all coders by maven coordinates and topic.
499 * @param groupId group id
500 * @param artifactId artifact id
501 * @return list of coders
502 * @throws IllegalArgumentException if invalid input
504 public List<CoderFilters> getFilters(String groupId, String artifactId) {
506 if (groupId == null || groupId.isEmpty()) {
507 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
510 if (artifactId == null || artifactId.isEmpty()) {
511 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
514 String key = this.codersKey(groupId, artifactId, "");
516 List<CoderFilters> codersFilters = new ArrayList<>();
517 for (Map.Entry<String, ProtocolCoderToolset> entry :
519 if (entry.getKey().startsWith(key)) {
520 codersFilters.addAll(entry.getValue().getCoders());
524 return codersFilters;
528 * get all filters by maven coordinates, topic, and classname.
530 * @param groupId group id
531 * @param artifactId artifact id
533 * @param classname classname
534 * @return list of coders
535 * @throws IllegalArgumentException if invalid input
537 public CoderFilters getFilters(
538 String groupId, String artifactId, String topic, String classname) {
540 if (!isCodingSupported(groupId, artifactId, topic)) {
541 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
544 if (classname == null || classname.isEmpty()) {
545 throw new IllegalArgumentException("classname must be provided");
548 String key = this.codersKey(groupId, artifactId, topic);
549 ProtocolCoderToolset coderTools = coders.get(key);
550 return coderTools.getCoder(classname);
554 * get all coders by maven coordinates and topic.
556 * @param groupId group id
557 * @param artifactId artifact id
559 * @return list of coders
560 * @throws IllegalArgumentException if invalid input
562 public ProtocolCoderToolset getCoders(
563 String groupId, String artifactId, String topic) {
565 if (!isCodingSupported(groupId, artifactId, topic)) {
566 throw new IllegalArgumentException(UNSUPPORTED_EX_MSG + codersKey(groupId, artifactId, topic));
569 String key = this.codersKey(groupId, artifactId, topic);
570 return coders.get(key);
574 * get all coders by maven coordinates and topic.
576 * @param groupId group id
577 * @param artifactId artifact id
578 * @return list of coders
579 * @throws IllegalArgumentException if invalid input
581 public List<ProtocolCoderToolset> getCoders(
582 String groupId, String artifactId) {
584 if (groupId == null || groupId.isEmpty()) {
585 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
588 if (artifactId == null || artifactId.isEmpty()) {
589 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
592 String key = this.codersKey(groupId, artifactId, "");
594 List<ProtocolCoderToolset> coderToolset = new ArrayList<>();
595 for (Map.Entry<String, ProtocolCoderToolset> entry :
597 if (entry.getKey().startsWith(key)) {
598 coderToolset.add(entry.getValue());
606 * get coded based on class and topic.
609 * @param codedClass class
610 * @return list of reverse filters
612 public List<CoderFilters> getReverseFilters(String topic, String codedClass) {
614 if (topic == null || topic.isEmpty()) {
615 throw new IllegalArgumentException(UNSUPPORTED_MSG);
618 if (codedClass == null) {
619 throw new IllegalArgumentException(MISSING_CLASS);
622 String key = this.reverseCodersKey(topic, codedClass);
623 List<ProtocolCoderToolset> toolsets = this.reverseCoders.get(key);
624 if (toolsets == null) {
625 throw new IllegalArgumentException("No Coder found for " + key);
628 List<CoderFilters> coderFilters = new ArrayList<>();
629 for (ProtocolCoderToolset toolset : toolsets) {
630 coderFilters.addAll(toolset.getCoders());
637 * returns group and artifact id of the creator of the encoder.
641 * @return the drools controller
643 DroolsController getDroolsController(String topic, Object fact) {
645 if (topic == null || topic.isEmpty()) {
646 throw new IllegalArgumentException(UNSUPPORTED_MSG);
650 throw new IllegalArgumentException(MISSING_CLASS);
653 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
655 if (droolsControllers.isEmpty()) {
656 throw new IllegalArgumentException("Invalid Topic: " + topic);
659 if (droolsControllers.size() > 1) {
661 "{}: multiple drools-controller {} for {}:{} ",
665 fact.getClass().getName());
668 return droolsControllers.get(0);
672 * returns group and artifact id of the creator of the encoder.
676 * @return list of drools controllers
678 List<DroolsController> getDroolsControllers(String topic, Object fact) {
680 if (topic == null || topic.isEmpty()) {
681 throw new IllegalArgumentException(UNSUPPORTED_MSG);
685 throw new IllegalArgumentException(MISSING_CLASS);
688 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
689 if (droolsControllers.size() > 1) {
692 "{}: multiple drools-controller {} for {}:{} ",
696 fact.getClass().getName());
699 return droolsControllers;
703 public String toString() {
704 return "GenericEventProtocolCoder [coders="
707 + reverseCoders.keySet()