2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 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.openecomp.policy.drools.protocol.coders;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Type;
26 import java.time.Instant;
27 import java.time.ZonedDateTime;
28 import java.time.format.DateTimeFormatter;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.List;
33 import org.openecomp.policy.drools.controller.DroolsController;
34 import org.openecomp.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
35 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomCoder;
36 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
37 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.fasterxml.jackson.annotation.JsonIgnore;
42 import com.fasterxml.jackson.core.JsonProcessingException;
43 import com.fasterxml.jackson.databind.DeserializationFeature;
44 import com.fasterxml.jackson.databind.ObjectMapper;
45 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
46 import com.google.gson.Gson;
47 import com.google.gson.GsonBuilder;
48 import com.google.gson.JsonDeserializationContext;
49 import com.google.gson.JsonDeserializer;
50 import com.google.gson.JsonElement;
51 import com.google.gson.JsonParseException;
52 import com.google.gson.JsonParser;
53 import com.google.gson.JsonPrimitive;
54 import com.google.gson.JsonSerializationContext;
55 import com.google.gson.JsonSerializer;
58 * Protocol Coding/Decoding Toolset
60 public abstract class ProtocolCoderToolset {
65 private static Logger logger = LoggerFactory.getLogger(ProtocolCoderToolset.class);
70 protected final String topic;
75 protected final String controllerId;
80 protected final String groupId;
85 protected final String artifactId;
88 * Protocols and associated Filters
90 protected final List<CoderFilters> coders = new ArrayList<CoderFilters>();
93 * Tree model (instead of class model) generic parsing to be able to inspect elements
95 protected JsonParser filteringParser = new JsonParser();
100 protected CustomCoder customCoder;
105 * @param topic the topic
106 * @param controllerId the controller id
107 * @param codedClass the decoded class
108 * @param filters list of filters that apply to the
109 * selection of this decodedClass in case of multiplicity
110 * @throws IllegalArgumentException if invalid data has been passed in
112 public ProtocolCoderToolset(String topic,
117 JsonProtocolFilter filters,
118 CustomCoder customCoder,
119 int modelClassLoaderHash)
120 throws IllegalArgumentException {
122 if (topic == null || controllerId == null ||
123 groupId == null || artifactId == null ||
124 codedClass == null || filters == null ||
125 topic.isEmpty() || controllerId.isEmpty()) {
127 throw new IllegalArgumentException("Invalid input");
131 this.controllerId = controllerId;
132 this.groupId = groupId;
133 this.artifactId = artifactId;
134 this.coders.add(new CoderFilters(codedClass, filters, modelClassLoaderHash));
135 this.customCoder = customCoder;
139 * gets the coder + filters associated with this class name
141 * @param classname class name
142 * @return the decoder filters or null if not found
144 public CoderFilters getCoder(String classname) {
145 for (CoderFilters decoder: this.coders) {
146 if (decoder.factClass.equals(classname)) {
154 * get all coder filters in use
156 * @return coder filters
158 public List<CoderFilters> getCoders() {
163 * add coder or replace it exists
165 * @param eventClass decoder
166 * @param filter filter
168 public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) {
170 for (CoderFilters coder: this.coders) {
171 if (coder.factClass.equals(eventClass)) {
172 // this is a better check than checking pointers, just
173 // in case classloader is different and this is just an update
174 coder.factClass = eventClass;
175 coder.filter = filter;
176 coder.modelClassLoaderHash = modelClassLoaderHash;
182 this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash));
188 * @param eventClass decoder
189 * @param filter filter
191 public void removeCoders(String eventClass) {
193 Iterator<CoderFilters> codersIt = this.coders.iterator();
194 while (codersIt.hasNext()) {
195 CoderFilters coder = codersIt.next();
196 if (coder.factClass.equals(eventClass)) {
208 public String getTopic() {return topic;}
211 * gets the controller id
213 * @return the controller id
215 public String getControllerId() {return controllerId;}
218 * @return the groupId
220 public String getGroupId() {
225 * @return the artifactId
227 public String getArtifactId() {
232 * @return the customCoder
234 public CustomCoder getCustomCoder() {
239 * @param customCoder the customCoder to set
241 public void setCustomCoder(CustomCoder customCoder) {
242 this.customCoder = customCoder;
246 * performs filtering on a json string
248 * @param json json string
249 * @return the decoder that passes the filter, otherwise null
250 * @throws UnsupportedOperationException can't filter
251 * @throws IllegalArgumentException invalid input
253 protected CoderFilters filter(String json)
254 throws UnsupportedOperationException, IllegalArgumentException, IllegalStateException {
257 // 1. Get list of decoding classes for this controller Id and topic
258 // 2. If there are no classes, return error
259 // 3. Otherwise, from the available classes for decoding, pick the first one that
260 // passes the filters
262 // Don't parse if it is not necessary
264 if (this.coders.isEmpty()) {
265 // TODO this is an error
266 throw new IllegalStateException("No coders available");
269 if (this.coders.size() == 1) {
270 JsonProtocolFilter filter = this.coders.get(0).getFilter();
271 if (!filter.isRules()) {
272 return this.coders.get(0);
278 event = this.filteringParser.parse(json);
279 } catch (Exception e) {
280 throw new UnsupportedOperationException(e);
283 for (CoderFilters decoder: this.coders) {
285 boolean accepted = decoder.getFilter().accept(event);
289 } catch (Exception e) {
290 logger.info("{}: unexpected failure accepting {} because of {}",
291 this, event, e.getMessage(), e);
300 * Decode json into a POJO object
301 * @param json json string
303 * @return a POJO object for the json string
304 * @throws IllegalArgumentException if an invalid parameter has been received
305 * @throws UnsupportedOperationException if parsing into POJO is not possible
307 public abstract Object decode(String json)
308 throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException;
311 * Encodes a POJO object into a JSON String
313 * @param event JSON POJO event to be converted to String
314 * @return JSON string version of POJO object
315 * @throws IllegalArgumentException if an invalid parameter has been received
316 * @throws UnsupportedOperationException if parsing into POJO is not possible
318 public abstract String encode(Object event)
319 throws IllegalArgumentException, UnsupportedOperationException;
322 public String toString() {
323 StringBuilder builder = new StringBuilder();
324 builder.append("ProtocolCoderToolset [topic=").append(topic).append(", controllerId=").append(controllerId)
325 .append(", groupId=").append(groupId).append(", artifactId=").append(artifactId).append(", coders=")
326 .append(coders).append(", filteringParser=").append(filteringParser).append(", customCoder=")
327 .append(customCoder).append("]");
328 return builder.toString();
333 * Tools used for encoding/decoding using Jackson
335 class JacksonProtocolCoderToolset extends ProtocolCoderToolset {
336 private static Logger logger = LoggerFactory.getLogger(JacksonProtocolCoderToolset.class);
341 protected final ObjectMapper decoder = new ObjectMapper();
347 protected final ObjectMapper encoder = new ObjectMapper();
350 * Toolset to encode/decode tools associated with a topic
353 * @param decodedClass decoded class of an event
356 public JacksonProtocolCoderToolset(String topic, String controllerId,
357 String groupId, String artifactId,
359 JsonProtocolFilter filter,
360 CustomJacksonCoder customJacksonCoder,
361 int modelClassLoaderHash) {
362 super(topic, controllerId, groupId, artifactId, decodedClass, filter, customJacksonCoder, modelClassLoaderHash);
363 decoder.registerModule(new JavaTimeModule());
364 decoder.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
369 * gets the Jackson decoder
371 * @return the Jackson decoder
374 protected ObjectMapper getDecoder() {return decoder;}
377 * gets the Jackson encoder
379 * @return the Jackson encoder
382 protected ObjectMapper getEncoder() {return encoder;}
388 public Object decode(String json)
389 throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
391 // 0. Use custom coder if available
393 if (this.customCoder != null) {
394 throw new UnsupportedOperationException
395 ("Jackon Custom Decoder is not supported at this time");
398 DroolsController droolsController =
399 DroolsController.factory.get(groupId, artifactId, "");
400 if (droolsController == null) {
401 logger.warn("{}: no drools-controller to process {}", this, json);
402 throw new IllegalStateException("no drools-controller to process event");
405 CoderFilters decoderFilter = filter(json);
406 if (decoderFilter == null) {
407 logger.debug("{}: no decoder to process {}", this, json);
408 throw new UnsupportedOperationException("no decoder to process event");
411 Class<?> decoderClass;
414 droolsController.fetchModelClass(decoderFilter.getCodedClass());
415 if (decoderClass == null) {
416 logger.warn("{}: cannot fetch application class {}", this, decoderFilter.getCodedClass());
417 throw new IllegalStateException("cannot fetch application class " + decoderFilter.getCodedClass());
419 } catch (Exception e) {
420 logger.warn("{}: cannot fetch application class {} because of {}", this,
421 decoderFilter.getCodedClass(), e.getMessage());
422 throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e);
427 Object fact = this.decoder.readValue(json, decoderClass);
429 } catch (Exception e) {
430 logger.warn("{} cannot decode {} into {} because of {}",
431 this, json, decoderClass.getName(), e.getMessage(), e);
432 throw new UnsupportedOperationException("cannont decode into " + decoderFilter.getCodedClass(), e);
440 public String encode(Object event)
441 throws IllegalArgumentException, UnsupportedOperationException {
443 // 0. Use custom coder if available
445 if (this.customCoder != null) {
446 throw new UnsupportedOperationException
447 ("Jackon Custom Encoder is not supported at this time");
451 String encodedEvent = this.encoder.writeValueAsString(event);
453 } catch (JsonProcessingException e) {
454 logger.error("{} cannot encode {} because of {}", this, event, e.getMessage(), e);
455 throw new UnsupportedOperationException("event cannot be encoded");
460 public String toString() {
461 StringBuilder builder = new StringBuilder();
462 builder.append("JacksonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
463 return builder.toString();
469 * Tools used for encoding/decoding using Jackson
471 class GsonProtocolCoderToolset extends ProtocolCoderToolset {
476 private static Logger logger = LoggerFactory.getLogger(GsonProtocolCoderToolset.class);
479 * Formatter for JSON encoding/decoding
482 public static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx");
485 public static DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT;
488 * Adapter for ZonedDateTime
491 public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
493 public ZonedDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context)
494 throws JsonParseException {
496 return ZonedDateTime.parse(element.getAsString(), format);
497 } catch (Exception e) {
498 logger.info("GsonUTCAdapter: cannot parse {} because of {}",
499 element, e.getMessage(), e);
504 public JsonElement serialize(ZonedDateTime datetime, Type type, JsonSerializationContext context) {
505 return new JsonPrimitive(datetime.format(format));
509 public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
512 public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
513 throws JsonParseException {
514 return Instant.ofEpochMilli(json.getAsLong());
518 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
519 return new JsonPrimitive(src.toEpochMilli());
529 protected final Gson decoder = new GsonBuilder().disableHtmlEscaping().
530 registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
537 protected final Gson encoder = new GsonBuilder().disableHtmlEscaping().
538 registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
542 * Toolset to encode/decode tools associated with a topic
545 * @param decodedClass decoded class of an event
548 public GsonProtocolCoderToolset(String topic, String controllerId,
549 String groupId, String artifactId,
551 JsonProtocolFilter filter,
552 CustomGsonCoder customGsonCoder,
553 int modelClassLoaderHash) {
554 super(topic, controllerId, groupId, artifactId, decodedClass, filter, customGsonCoder, modelClassLoaderHash);
558 * gets the Gson decoder
560 * @return the Gson decoder
563 protected Gson getDecoder() {return decoder;}
566 * gets the Gson encoder
568 * @return the Gson encoder
571 protected Gson getEncoder() {return encoder;}
577 public Object decode(String json)
578 throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
580 DroolsController droolsController =
581 DroolsController.factory.get(groupId, artifactId, "");
582 if (droolsController == null) {
583 logger.warn("{}: no drools-controller to process {}", this, json);
584 throw new IllegalStateException("no drools-controller to process event");
587 CoderFilters decoderFilter = filter(json);
588 if (decoderFilter == null) {
589 logger.debug("{}: no decoder to process {}", this, json);
590 throw new UnsupportedOperationException("no decoder to process event");
593 Class<?> decoderClass;
596 droolsController.fetchModelClass(decoderFilter.getCodedClass());
597 if (decoderClass == null) {
598 logger.warn("{}: cannot fetch application class {}", this, decoderFilter.getCodedClass());
599 throw new IllegalStateException("cannot fetch application class " + decoderFilter.getCodedClass());
601 } catch (Exception e) {
602 logger.warn("{}: cannot fetch application class {} because of {}", this,
603 decoderFilter.getCodedClass(), e.getMessage());
604 throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e);
607 if (this.customCoder != null) {
609 Class<?> gsonClassContainer =
610 droolsController.fetchModelClass(this.customCoder.getClassContainer());
611 Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
612 Object gsonObject = gsonField.get(null);
613 Method fromJsonMethod = gsonObject.getClass().
615 ("fromJson", new Class[]{String.class, Class.class});
616 Object fact = fromJsonMethod.invoke(gsonObject, json, decoderClass);
618 } catch (Exception e) {
619 logger.warn("{}: cannot fetch application class {} because of {}", this,
620 decoderFilter.getCodedClass(), e.getMessage());
621 throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e);
625 Object fact = this.decoder.fromJson(json, decoderClass);
627 } catch (Exception e) {
628 logger.warn("{} cannot decode {} into {} because of {}",
629 this, json, decoderClass.getName(), e.getMessage(), e);
630 throw new UnsupportedOperationException("cannont decode into " + decoderFilter.getCodedClass(), e);
641 public String encode(Object event)
642 throws IllegalArgumentException, UnsupportedOperationException {
644 DroolsController droolsController =
645 DroolsController.factory.get(groupId, artifactId, "");
646 if (droolsController == null) {
647 logger.info("{}: no drools-controller to process {} (continue)", this, event);
648 if (this.customCoder != null) {
649 logger.warn("{}: no drools-controller to process {}", this, event);
650 throw new IllegalStateException("custom-coder but no drools-controller");
654 if (this.customCoder != null) {
656 Class<?> gsonClassContainer =
657 droolsController.fetchModelClass(this.customCoder.getClassContainer());
658 Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
659 Object gsonObject = gsonField.get(null);
660 Method toJsonMethod = gsonObject.getClass().
662 ("toJson", new Class[]{Object.class});
663 String encodedJson = (String) toJsonMethod.invoke(gsonObject, event);
665 } catch (Exception e) {
666 logger.warn("{} cannot custom-encode {} because of {}", this, event, e.getMessage(), e);
667 throw new UnsupportedOperationException("event cannot be encoded", e);
671 String encodedEvent = this.encoder.toJson(event);
673 } catch (Exception e) {
674 logger.warn("{} cannot encode {} because of {}", this, event, e.getMessage(), e);
675 throw new UnsupportedOperationException("event cannot be encoded", e);
681 public String toString() {
682 StringBuilder builder = new StringBuilder();
683 builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
684 return builder.toString();