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.protocol.coders.EventProtocolCoder.CoderFilters;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * This protocol Coder that does its best attempt to decode/encode, selecting the best class and best fitted json
37 abstract class GenericEventProtocolCoder {
39 private static final String INVALID_ARTIFACT_ID_MSG = "Invalid artifact id";
41 private static final String INVALID_GROUP_ID_MSG = "Invalid group id";
43 private static final String INVALID_TOPIC_MSG = "Invalid Topic";
45 private static final String UNSUPPORTED_MSG = "Unsupported";
47 private static final String MISSING_CLASS = "class must be provided";
49 private static Logger logger = LoggerFactory.getLogger(GenericEventProtocolCoder.class);
52 * Mapping topic:controller-id -> /<protocol-decoder-toolset/> where protocol-coder-toolset contains
53 * a gson-protocol-coder-toolset.
55 protected final HashMap<String, ProtocolCoderToolset> coders =
59 * Mapping topic + classname -> Protocol Set.
61 protected final HashMap<String, List<ProtocolCoderToolset>>
62 reverseCoders = new HashMap<>();
64 GenericEventProtocolCoder() {
71 * @param eventProtocolParams parameter object for event encoder
72 * @throw IllegalArgumentException if an invalid parameter is passed
74 public void add(EventProtocolParams eventProtocolParams) {
75 if (eventProtocolParams.getGroupId() == null || eventProtocolParams.getGroupId().isEmpty()) {
76 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
79 if (eventProtocolParams.getArtifactId() == null || eventProtocolParams.getArtifactId().isEmpty()) {
80 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
83 if (eventProtocolParams.getTopic() == null || eventProtocolParams.getTopic().isEmpty()) {
84 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
87 if (eventProtocolParams.getEventClass() == null) {
88 throw new IllegalArgumentException("Invalid Event Class");
91 String key = this.codersKey(eventProtocolParams.getGroupId(), eventProtocolParams.getArtifactId(),
92 eventProtocolParams.getTopic());
93 String reverseKey = this.reverseCodersKey(eventProtocolParams.getTopic(), eventProtocolParams.getEventClass());
96 if (coders.containsKey(key)) {
97 ProtocolCoderToolset toolset = coders.get(key);
99 logger.info("{}: adding coders for existing {}: {}", this, key, toolset);
103 eventProtocolParams.getEventClass(),
104 eventProtocolParams.getProtocolFilter(),
105 eventProtocolParams.getModelClassLoaderHash());
107 if (!reverseCoders.containsKey(reverseKey)) {
109 "{}: adding new reverse coders (multiple classes case) for {}:{}: {}",
115 List<ProtocolCoderToolset> reverseMappings =
117 reverseMappings.add(toolset);
118 reverseCoders.put(reverseKey, reverseMappings);
123 GsonProtocolCoderToolset coderTools =
124 new GsonProtocolCoderToolset(eventProtocolParams, key);
126 logger.info("{}: adding coders for new {}: {}", this, key, coderTools);
128 coders.put(key, coderTools);
130 if (reverseCoders.containsKey(reverseKey)) {
131 // There is another controller (different group id/artifact id/topic)
132 // that shares the class and the topic.
134 List<ProtocolCoderToolset> toolsets =
135 reverseCoders.get(reverseKey);
136 boolean present = false;
137 for (ProtocolCoderToolset parserSet : toolsets) {
139 present = parserSet.getControllerId().equals(key);
143 "{}: 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);
168 * produces key for indexing toolset entries.
170 * @param groupId group id
171 * @param artifactId artifact id
175 protected String codersKey(String groupId, String artifactId, String topic) {
176 return groupId + ":" + artifactId + ":" + topic;
180 * produces a key for the reverse index.
183 * @param eventClass coded class
184 * @return reverse index key
186 protected String reverseCodersKey(String topic, String eventClass) {
187 return topic + ":" + eventClass;
193 * @param groupId group id
194 * @param artifactId artifact id
196 * @throws IllegalArgumentException if invalid input
198 public void remove(String groupId, String artifactId, String topic) {
200 if (groupId == null || groupId.isEmpty()) {
201 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
204 if (artifactId == null || artifactId.isEmpty()) {
205 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
208 if (topic == null || topic.isEmpty()) {
209 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
212 String key = this.codersKey(groupId, artifactId, topic);
214 synchronized (this) {
215 if (coders.containsKey(key)) {
216 ProtocolCoderToolset coderToolset = coders.remove(key);
218 logger.info("{}: removed toolset for {}: {}", this, key, coderToolset);
220 for (CoderFilters codeFilter : coderToolset.getCoders()) {
221 String className = codeFilter.getCodedClass();
222 String reverseKey = this.reverseCodersKey(topic, className);
223 if (this.reverseCoders.containsKey(reverseKey)) {
224 List<ProtocolCoderToolset> toolsets =
225 this.reverseCoders.get(reverseKey);
226 Iterator<ProtocolCoderToolset> toolsetsIter =
228 while (toolsetsIter.hasNext()) {
229 ProtocolCoderToolset toolset = toolsetsIter.next();
230 if (toolset.getControllerId().equals(key)) {
232 "{}: removed coder from toolset for {} from reverse mapping", this, reverseKey);
233 toolsetsIter.remove();
237 if (this.reverseCoders.get(reverseKey).isEmpty()) {
238 logger.info("{}: removing reverse mapping for {}: ", this, reverseKey);
239 this.reverseCoders.remove(reverseKey);
248 * does it support coding.
250 * @param groupId group id
251 * @param artifactId artifact id
253 * @return true if its is codable
255 public boolean isCodingSupported(String groupId, String artifactId, String topic) {
257 if (groupId == null || groupId.isEmpty()) {
258 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
261 if (artifactId == null || artifactId.isEmpty()) {
262 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
265 if (topic == null || topic.isEmpty()) {
266 throw new IllegalArgumentException(INVALID_TOPIC_MSG);
269 String key = this.codersKey(groupId, artifactId, topic);
270 synchronized (this) {
271 return coders.containsKey(key);
276 * decode a json string into an Object.
278 * @param groupId group id
279 * @param artifactId artifact id
281 * @param json json string to convert to object
282 * @return the decoded object
283 * @throws IllegalArgumentException if invalid argument is provided
284 * @throws UnsupportedOperationException if the operation cannot be performed
286 public Object decode(String groupId, String artifactId, String topic, String json) {
288 if (!isCodingSupported(groupId, artifactId, topic)) {
289 throw new IllegalArgumentException(
290 "Unsupported:" + codersKey(groupId, artifactId, topic) + " for encoding");
293 String key = this.codersKey(groupId, artifactId, topic);
294 ProtocolCoderToolset coderTools = coders.get(key);
296 Object event = coderTools.decode(json);
300 } catch (Exception e) {
301 logger.debug("{}, cannot decode {}", this, json, e);
304 throw new UnsupportedOperationException("Cannot decode with gson");
308 * encode an object into a json string.
310 * @param groupId group id
311 * @param artifactId artifact id
313 * @param event object to convert to string
314 * @return the json string
315 * @throws IllegalArgumentException if invalid argument is provided
316 * @throws UnsupportedOperationException if the operation cannot be performed
318 public String encode(String groupId, String artifactId, String topic, Object event) {
320 if (!isCodingSupported(groupId, artifactId, topic)) {
321 throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic));
325 throw new IllegalArgumentException("Unsupported topic:" + topic);
328 // reuse the decoder set, since there must be affinity in the model
329 String key = this.codersKey(groupId, artifactId, topic);
330 return this.encodeInternal(key, event);
334 * encode an object into a json string.
337 * @param event object to convert to string
338 * @return the json string
339 * @throws IllegalArgumentException if invalid argument is provided
340 * @throws UnsupportedOperationException if the operation cannot be performed
342 public String encode(String topic, Object event) {
345 throw new IllegalArgumentException("Invalid encoded class");
348 if (topic == null || topic.isEmpty()) {
349 throw new IllegalArgumentException("Invalid topic");
352 String reverseKey = this.reverseCodersKey(topic, event.getClass().getName());
353 if (!this.reverseCoders.containsKey(reverseKey)) {
354 throw new IllegalArgumentException("no reverse coder has been found");
357 List<ProtocolCoderToolset> toolsets =
358 this.reverseCoders.get(reverseKey);
362 toolsets.get(0).getGroupId(), toolsets.get(0).getArtifactId(), topic);
363 return this.encodeInternal(key, event);
367 * encode an object into a json string.
370 * @param encodedClass object to convert to string
371 * @return the json string
372 * @throws IllegalArgumentException if invalid argument is provided
373 * @throws UnsupportedOperationException if the operation cannot be performed
375 public String encode(String topic, Object encodedClass, DroolsController droolsController) {
377 if (encodedClass == null) {
378 throw new IllegalArgumentException("Invalid encoded class");
381 if (topic == null || topic.isEmpty()) {
382 throw new IllegalArgumentException("Invalid topic");
385 String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic);
386 return this.encodeInternal(key, encodedClass);
390 * encode an object into a json string.
392 * @param key identifier
393 * @param event object to convert to string
394 * @return the json string
395 * @throws IllegalArgumentException if invalid argument is provided
396 * @throws UnsupportedOperationException if the operation cannot be performed
398 protected String encodeInternal(String key, Object event) {
400 logger.debug("{}: encode for {}: {}", this, key, event);
402 ProtocolCoderToolset coderTools = coders.get(key);
404 String json = coderTools.encode(event);
405 if (json != null && !json.isEmpty()) {
408 } catch (Exception e) {
409 logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e);
412 throw new UnsupportedOperationException("Cannot decode with gson");
419 * @param encodedClass encoded class
420 * @return list of controllers
421 * @throws IllegalStateException illegal state
422 * @throws IllegalArgumentException argument
424 protected List<DroolsController> droolsCreators(String topic, Object encodedClass) {
426 List<DroolsController> droolsControllers = new ArrayList<>();
428 String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getName());
429 if (!this.reverseCoders.containsKey(reverseKey)) {
430 logger.warn("{}: no reverse mapping for {}", this, reverseKey);
431 return droolsControllers;
434 List<ProtocolCoderToolset> toolsets =
435 this.reverseCoders.get(reverseKey);
437 // There must be multiple toolsets associated with <topic,classname> reverseKey
438 // case 2 different controllers use the same models and register the same encoder for
439 // the same topic. This is assumed not to occur often but for the purpose of encoding
440 // but there should be no side-effects. Ownership is crosscheck against classname and
441 // classloader reference.
443 if (toolsets == null || toolsets.isEmpty()) {
444 throw new IllegalStateException(
445 "No Encoders toolsets available for topic "
448 + encodedClass.getClass().getName());
451 for (ProtocolCoderToolset encoderSet : toolsets) {
452 // figure out the right toolset
453 String groupId = encoderSet.getGroupId();
454 String artifactId = encoderSet.getArtifactId();
455 List<CoderFilters> coderFilters = encoderSet.getCoders();
456 for (CoderFilters coder : coderFilters) {
457 if (coder.getCodedClass().equals(encodedClass.getClass().getName())) {
458 DroolsController droolsController = DroolsController.factory.get(groupId, artifactId, "");
459 if (droolsController.ownsCoder(
460 encodedClass.getClass(), coder.getModelClassLoaderHash())) {
461 droolsControllers.add(droolsController);
467 if (droolsControllers.isEmpty()) {
468 throw new IllegalStateException(
469 "No Encoders toolsets available for "
472 + encodedClass.getClass().getName());
475 return droolsControllers;
479 * get all filters by maven coordinates and topic.
481 * @param groupId group id
482 * @param artifactId artifact id
484 * @return list of coders
485 * @throws IllegalArgumentException if invalid input
487 public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) {
489 if (!isCodingSupported(groupId, artifactId, topic)) {
490 throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic));
493 String key = this.codersKey(groupId, artifactId, topic);
494 ProtocolCoderToolset coderTools = coders.get(key);
495 return coderTools.getCoders();
499 * get all coders by maven coordinates and topic.
501 * @param groupId group id
502 * @param artifactId artifact id
503 * @return list of coders
504 * @throws IllegalArgumentException if invalid input
506 public List<CoderFilters> getFilters(String groupId, String artifactId) {
508 if (groupId == null || groupId.isEmpty()) {
509 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
512 if (artifactId == null || artifactId.isEmpty()) {
513 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
516 String key = this.codersKey(groupId, artifactId, "");
518 List<CoderFilters> codersFilters = new ArrayList<>();
519 for (Map.Entry<String, ProtocolCoderToolset> entry :
521 if (entry.getKey().startsWith(key)) {
522 codersFilters.addAll(entry.getValue().getCoders());
526 return codersFilters;
530 * get all filters by maven coordinates, topic, and classname.
532 * @param groupId group id
533 * @param artifactId artifact id
535 * @param classname classname
536 * @return list of coders
537 * @throws IllegalArgumentException if invalid input
539 public CoderFilters getFilters(
540 String groupId, String artifactId, String topic, String classname) {
542 if (!isCodingSupported(groupId, artifactId, topic)) {
543 throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic));
546 if (classname == null || classname.isEmpty()) {
547 throw new IllegalArgumentException("classname must be provided");
550 String key = this.codersKey(groupId, artifactId, topic);
551 ProtocolCoderToolset coderTools = coders.get(key);
552 return coderTools.getCoder(classname);
556 * get all coders by maven coordinates and topic.
558 * @param groupId group id
559 * @param artifactId artifact id
561 * @return list of coders
562 * @throws IllegalArgumentException if invalid input
564 public ProtocolCoderToolset getCoders(
565 String groupId, String artifactId, String topic) {
567 if (!isCodingSupported(groupId, artifactId, topic)) {
568 throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic));
571 String key = this.codersKey(groupId, artifactId, topic);
572 return coders.get(key);
576 * get all coders by maven coordinates and topic.
578 * @param groupId group id
579 * @param artifactId artifact id
580 * @return list of coders
581 * @throws IllegalArgumentException if invalid input
583 public List<ProtocolCoderToolset> getCoders(
584 String groupId, String artifactId) {
586 if (groupId == null || groupId.isEmpty()) {
587 throw new IllegalArgumentException(INVALID_GROUP_ID_MSG);
590 if (artifactId == null || artifactId.isEmpty()) {
591 throw new IllegalArgumentException(INVALID_ARTIFACT_ID_MSG);
594 String key = this.codersKey(groupId, artifactId, "");
596 List<ProtocolCoderToolset> coderToolset = new ArrayList<>();
597 for (Map.Entry<String, ProtocolCoderToolset> entry :
599 if (entry.getKey().startsWith(key)) {
600 coderToolset.add(entry.getValue());
608 * get coded based on class and topic.
611 * @param codedClass class
612 * @return list of reverse filters
614 public List<CoderFilters> getReverseFilters(String topic, String codedClass) {
616 if (topic == null || topic.isEmpty()) {
617 throw new IllegalArgumentException(UNSUPPORTED_MSG);
620 if (codedClass == null) {
621 throw new IllegalArgumentException(MISSING_CLASS);
624 String key = this.reverseCodersKey(topic, codedClass);
625 List<ProtocolCoderToolset> toolsets = this.reverseCoders.get(key);
626 if (toolsets == null) {
627 throw new IllegalArgumentException("No Coder found for " + key);
630 List<CoderFilters> coderFilters = new ArrayList<>();
631 for (ProtocolCoderToolset toolset : toolsets) {
632 coderFilters.addAll(toolset.getCoders());
639 * returns group and artifact id of the creator of the encoder.
643 * @return the drools controller
645 DroolsController getDroolsController(String topic, Object fact) {
647 if (topic == null || topic.isEmpty()) {
648 throw new IllegalArgumentException(UNSUPPORTED_MSG);
652 throw new IllegalArgumentException(MISSING_CLASS);
655 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
657 if (droolsControllers.isEmpty()) {
658 throw new IllegalArgumentException("Invalid Topic: " + topic);
661 if (droolsControllers.size() > 1) {
663 "{}: multiple drools-controller {} for {}:{} ",
667 fact.getClass().getName());
670 return droolsControllers.get(0);
674 * returns group and artifact id of the creator of the encoder.
678 * @return list of drools controllers
680 List<DroolsController> getDroolsControllers(String topic, Object fact) {
682 if (topic == null || topic.isEmpty()) {
683 throw new IllegalArgumentException(UNSUPPORTED_MSG);
687 throw new IllegalArgumentException(MISSING_CLASS);
690 List<DroolsController> droolsControllers = droolsCreators(topic, fact);
691 if (droolsControllers.size() > 1) {
694 "{}: multiple drools-controller {} for {}:{} ",
698 fact.getClass().getName());
701 return droolsControllers;
705 public String toString() {
706 return "GenericEventProtocolCoder [coders="
709 + reverseCoders.keySet()