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, null);
 
 646                 if (droolsController == null) {
 
 647                         logger.info("{}: no drools-controller to process {} (continue)", this, event);
 
 648                         throw new IllegalStateException("custom-coder but no drools-controller");
 
 651                 if (this.customCoder != null) {
 
 653                                 Class<?> gsonClassContainer = 
 
 654                                                 droolsController.fetchModelClass(this.customCoder.getClassContainer());
 
 655                                 Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
 
 656                                 Object gsonObject = gsonField.get(null);
 
 657                                 Method toJsonMethod = gsonObject.getClass().
 
 659                                                                         ("toJson", new Class[]{Object.class});
 
 660                                 String encodedJson = (String) toJsonMethod.invoke(gsonObject, event);
 
 662                         } catch (Exception e) {
 
 663                                 logger.warn("{} cannot custom-encode {} because of {}", this, event, e.getMessage(), e);
 
 664                                 throw new UnsupportedOperationException("event cannot be encoded", e);
 
 668                                 String encodedEvent = this.encoder.toJson(event);
 
 670                         } catch (Exception e) {
 
 671                                 logger.warn("{} cannot encode {} because of {}", this, event, e.getMessage(), e);
 
 672                                 throw new UnsupportedOperationException("event cannot be encoded", e);
 
 678         public String toString() {
 
 679                 StringBuilder builder = new StringBuilder();
 
 680                 builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
 
 681                 return builder.toString();