9e079ff53059f279e9f14d9375b4289c49773384
[policy/drools-pdp.git] /
1 package org.openecomp.policy.drools.protocol.coders;
2
3 import java.lang.reflect.Field;
4 import java.lang.reflect.InvocationTargetException;
5 import java.lang.reflect.Method;
6 import java.lang.reflect.Type;
7 import java.time.Instant;
8 import java.time.ZonedDateTime;
9 import java.time.format.DateTimeFormatter;
10 import java.util.ArrayList;
11 import java.util.Iterator;
12 import java.util.List;
13
14 import org.openecomp.policy.common.logging.eelf.MessageCodes;
15 import org.openecomp.policy.common.logging.flexlogger.FlexLogger;
16 import org.openecomp.policy.common.logging.flexlogger.Logger;
17 import org.openecomp.policy.drools.controller.DroolsController;
18 import org.openecomp.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
19 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomCoder;
20 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
21 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder;
22
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import com.fasterxml.jackson.databind.DeserializationFeature;
26 import com.fasterxml.jackson.databind.ObjectMapper;
27 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
28 import com.google.gson.Gson;
29 import com.google.gson.GsonBuilder;
30 import com.google.gson.JsonDeserializationContext;
31 import com.google.gson.JsonDeserializer;
32 import com.google.gson.JsonElement;
33 import com.google.gson.JsonParseException;
34 import com.google.gson.JsonParser;
35 import com.google.gson.JsonPrimitive;
36 import com.google.gson.JsonSerializationContext;
37 import com.google.gson.JsonSerializer;
38
39 /**
40  * Protocol Coding/Decoding Toolset
41  */
42 public abstract class ProtocolCoderToolset {
43         
44         /**
45          * topic
46          */
47         protected final String topic;
48         
49         /**
50          * controller id
51          */
52         protected final String controllerId;
53         
54         /**
55          * group id
56          */
57         protected final String groupId;
58         
59         /**
60          * artifact id
61          */
62         protected final String artifactId;
63         
64         /**
65          * Protocols and associated Filters
66          */
67         protected final List<CoderFilters> coders = new ArrayList<CoderFilters>();
68         
69         /**
70          * Tree model (instead of class model) generic parsing to be able to inspect elements
71          */
72         protected JsonParser filteringParser = new JsonParser();
73
74         /**
75          * custom coder
76          */
77         protected CustomCoder customCoder;
78         
79         /**
80          * Constructor
81          * 
82          * @param topic the topic
83          * @param controllerId the controller id
84          * @param codedClass the decoded class
85          * @param filters list of filters that apply to the
86          * selection of this decodedClass in case of multiplicity
87          * @throws IllegalArgumentException if invalid data has been passed in
88          */
89         public ProtocolCoderToolset(String topic, 
90                                                                 String controllerId, 
91                                             String groupId,
92                                             String artifactId,
93                                             String codedClass, 
94                                             JsonProtocolFilter filters, 
95                                                         CustomCoder customCoder,
96                                                         int modelClassLoaderHash)
97                 throws IllegalArgumentException {
98                 
99                 if (topic == null || controllerId == null || 
100                         groupId == null || artifactId == null ||
101                         codedClass == null || filters == null ||
102                         topic.isEmpty() || controllerId.isEmpty()) {
103                         // TODO
104                         throw new IllegalArgumentException("Invalid input");
105                 }
106                 
107                 this.topic = topic;
108                 this.controllerId = controllerId;
109                 this.groupId = groupId;
110                 this.artifactId = artifactId;
111                 this.coders.add(new CoderFilters(codedClass, filters, modelClassLoaderHash));
112                 this.customCoder = customCoder;
113         }
114         
115         /**
116          * gets the coder + filters associated with this class name
117          * 
118          * @param classname class name
119          * @return the decoder filters or null if not found
120          */
121         public CoderFilters getCoder(String classname) {
122                 for (CoderFilters decoder: this.coders) {
123                         if (decoder.factClass.equals(classname)) {
124                                 return decoder;
125                         }
126                 }
127                 return null;
128         }
129
130         /**
131          * get all coder filters in use
132          * 
133          * @return coder filters
134          */
135         public List<CoderFilters> getCoders() {
136                 return this.coders;
137         }
138
139         /**
140          * add coder or replace it exists
141          * 
142          * @param eventClass decoder
143          * @param filter filter
144          */
145         public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) {
146                 synchronized(this) {
147                         for (CoderFilters coder: this.coders) {
148                                 if (coder.factClass.equals(eventClass)) {
149                                         // this is a better check than checking pointers, just
150                                         // in case classloader is different and this is just an update
151                                         coder.factClass = eventClass;
152                                         coder.filter = filter;
153                                         coder.modelClassLoaderHash = modelClassLoaderHash;
154                                         return;
155                                 }
156                         }
157                 }
158                 
159                 this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash));
160         }
161         
162         /**
163          * remove coder
164          * 
165          * @param eventClass decoder
166          * @param filter filter
167          */
168         public void removeCoders(String eventClass) {
169                 synchronized(this) {
170                         Iterator<CoderFilters> codersIt = this.coders.iterator();
171                         while (codersIt.hasNext()) {
172                                 CoderFilters coder = codersIt.next();
173                                 if (coder.factClass.equals(eventClass)) {
174                                         codersIt.remove();
175                                 }
176                         }
177                 }
178         }
179
180         /**
181          * gets the topic
182          * 
183          * @return the topic
184          */
185         public String getTopic() {return topic;}
186         
187         /**
188          * gets the controller id
189          * 
190          * @return the controller id
191          */
192         public String getControllerId() {return controllerId;}
193
194         /**
195          * @return the groupId
196          */
197         public String getGroupId() {
198                 return groupId;
199         }
200
201         /**
202          * @return the artifactId
203          */
204         public String getArtifactId() {
205                 return artifactId;
206         }
207
208         /**
209          * @return the customCoder
210          */
211         public CustomCoder getCustomCoder() {
212                 return customCoder;
213         }
214
215         /**
216          * @param customCoder the customCoder to set
217          */
218         public void setCustomCoder(CustomCoder customCoder) {
219                 this.customCoder = customCoder;
220         }
221
222         /**
223          * performs filtering on a json string
224          * 
225          * @param json json string
226          * @return the decoder that passes the filter, otherwise null
227          * @throws UnsupportedOperationException can't filter
228          * @throws IllegalArgumentException invalid input
229          */
230         protected CoderFilters filter(String json) 
231                         throws UnsupportedOperationException, IllegalArgumentException, IllegalStateException {
232                 
233
234                 // 1. Get list of decoding classes for this controller Id and topic
235                 // 2. If there are no classes, return error
236                 // 3. Otherwise, from the available classes for decoding, pick the first one that
237                 //    passes the filters        
238                 
239                 // Don't parse if it is not necessary
240                 
241                 if (this.coders.isEmpty()) {
242                         // TODO this is an error
243                         throw new IllegalStateException("No coders available");
244                 }
245                 
246                 if (this.coders.size() == 1) {
247                         JsonProtocolFilter filter = this.coders.get(0).getFilter();
248                         if (!filter.isRules()) {
249                                 return this.coders.get(0);
250                         }
251                 }
252                 
253                 JsonElement event;
254                 try {
255                         event = this.filteringParser.parse(json);
256                 } catch (Exception e) {
257                         // TODO Auto-generated catch block
258                         e.printStackTrace();
259                         throw new UnsupportedOperationException(e);
260                 }
261                 
262                 for (CoderFilters decoder: this.coders) {
263                         try {
264                                 boolean accepted = decoder.getFilter().accept(event);
265                                 if (accepted) {
266                                         return decoder;
267                                 } 
268                         } catch (Exception e) {
269                                 // TODO: handle exception
270                                 e.printStackTrace();
271                         }
272                 }
273                 
274                 return null;
275         }
276
277         /**
278          * Decode json into a POJO object
279          * @param json json string
280          * 
281          * @return a POJO object for the json string
282          * @throws IllegalArgumentException if an invalid parameter has been received
283          * @throws UnsupportedOperationException if parsing into POJO is not possible
284          */
285         public abstract Object decode(String json) 
286                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException;
287         
288         /**
289          * Encodes a POJO object into a JSON String
290          * 
291          * @param event JSON POJO event to be converted to String
292          * @return JSON string version of POJO object
293          * @throws IllegalArgumentException if an invalid parameter has been received
294          * @throws UnsupportedOperationException if parsing into POJO is not possible
295          */
296         public abstract String encode(Object event) 
297                         throws IllegalArgumentException, UnsupportedOperationException;
298
299         @Override
300         public String toString() {
301                 StringBuilder builder = new StringBuilder();
302                 builder.append("ProtocolCoderToolset [topic=").append(topic).append(", controllerId=").append(controllerId)
303                                 .append(", groupId=").append(groupId).append(", artifactId=").append(artifactId).append(", coders=")
304                                 .append(coders).append(", filteringParser=").append(filteringParser).append(", customCoder=")
305                                 .append(customCoder).append("]");
306                 return builder.toString();
307         }
308 }
309
310 /**
311  * Tools used for encoding/decoding using Jackson
312  */
313 class JacksonProtocolCoderToolset extends ProtocolCoderToolset {
314         private static Logger  logger = FlexLogger.getLogger(JacksonProtocolCoderToolset.class);        
315         /**
316          * decoder
317          */
318         @JsonIgnore
319         protected final ObjectMapper decoder = new ObjectMapper();
320         
321         /**
322          * encoder
323          */
324         @JsonIgnore
325         protected final ObjectMapper encoder = new ObjectMapper();
326         
327         /**
328          * Toolset to encode/decode tools associated with a topic
329          * 
330          * @param topic topic
331          * @param decodedClass decoded class of an event
332          * @param filter 
333          */
334         public JacksonProtocolCoderToolset(String topic, String controllerId, 
335                                                                            String groupId, String artifactId,
336                                                    String decodedClass, 
337                                                    JsonProtocolFilter filter,
338                                                    CustomJacksonCoder customJacksonCoder,
339                                                    int modelClassLoaderHash) {
340                 super(topic, controllerId, groupId, artifactId, decodedClass, filter, customJacksonCoder, modelClassLoaderHash);
341                 decoder.registerModule(new JavaTimeModule());
342                 decoder.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
343                                           false);
344         }
345         
346         /**
347          * gets the Jackson decoder
348          * 
349          * @return the Jackson decoder
350          */
351         @JsonIgnore
352         protected ObjectMapper getDecoder() {return decoder;}
353         
354         /**
355          * gets the Jackson encoder
356          * 
357          * @return the Jackson encoder
358          */
359         @JsonIgnore
360         protected ObjectMapper getEncoder() {return encoder;}
361         
362         /**
363          * {@inheritDoc}
364          */
365         @Override
366         public Object decode(String json) 
367                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
368
369                 // 0. Use custom coder if available
370                 
371                 if (this.customCoder != null) {
372                         throw new UnsupportedOperationException
373                                         ("Jackon Custom Decoder is not supported at this time");
374                 }
375                 
376                 DroolsController droolsController = 
377                                 DroolsController.factory.get(groupId, artifactId, "");
378                 if (droolsController == null) {
379                         String error = "NO-DROOLS-CONTROLLER for: " + json + " IN " + this;
380                         logger.warn(error);
381                         throw new IllegalStateException(error);
382                 }
383                 
384                 CoderFilters decoderFilter = filter(json);
385                 if (decoderFilter == null) {
386                         String error = "NO-DECODER for: " + json + " IN " + this;
387                         logger.warn(error);
388                         throw new UnsupportedOperationException(error);
389                 }
390                 
391                 Class<?> decoderClass;
392                 try {
393                         decoderClass = 
394                                         droolsController.fetchModelClass(decoderFilter.getCodedClass());
395                         if (decoderClass ==  null) {
396                                 String error = "DECODE-ERROR FETCHING MODEL CLASS: " + ":" + json + ":" + this;
397                                 logger.error(error);
398                                 throw new IllegalStateException(error);                         
399                         }
400                 } catch (Exception e) {
401                         String error = "DECODE-ERROR FETCHING MODEL CLASS: "+ e.getMessage() + ":" + json + ":" + this;
402                         logger.warn(MessageCodes.EXCEPTION_ERROR, e, error);
403                         throw new UnsupportedOperationException(error, e);
404                 }
405                 
406
407                 try {
408                         Object fact = this.decoder.readValue(json, decoderClass);
409                         return fact;
410                 } catch (Exception e) {
411                         String error = "DECODE-ERROR FROM PDP-D FRAMEWORK: "+ json + ":" + e.getMessage() + ":" + this;
412                         logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
413                         throw new UnsupportedOperationException(error, e);
414                 }
415         }
416         
417         /**
418          * {@inheritDoc}
419          */
420         @Override
421         public String encode(Object event) 
422                         throws IllegalArgumentException, UnsupportedOperationException {
423                 
424                 // 0. Use custom coder if available
425                 
426                 if (this.customCoder != null) {
427                         throw new UnsupportedOperationException
428                                         ("Jackon Custom Encoder is not supported at this time");
429                 }
430                 
431                 try {
432                         String encodedEvent = this.encoder.writeValueAsString(event);
433                         return encodedEvent;                    
434                 } catch (JsonProcessingException e) {
435                         String error = "ENCODE-ERROR: "+ event + " IN " + this;
436                         logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
437                         throw new UnsupportedOperationException(error, e);
438                 }
439         }
440
441         @Override
442         public String toString() {
443                 StringBuilder builder = new StringBuilder();
444                 builder.append("JacksonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
445                 return builder.toString();
446         }
447
448 }
449
450 /**
451  * Tools used for encoding/decoding using Jackson
452  */
453 class GsonProtocolCoderToolset extends ProtocolCoderToolset {
454         
455         private static Logger  logger = FlexLogger.getLogger(GsonProtocolCoderToolset.class);
456         /**
457          * Formatter for JSON encoding/decoding
458          */
459         @JsonIgnore
460         public static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx");
461         
462         @JsonIgnore
463         public static DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT;
464         
465         /**
466          * Adapter for ZonedDateTime
467          */
468
469         public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
470
471                 public ZonedDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context)
472                                 throws JsonParseException {
473                         try {
474                                 return ZonedDateTime.parse(element.getAsString(), format);
475                         } catch (Exception e) {
476                                 System.err.println(e);
477                         }
478                         return null;
479                 }
480
481                 public JsonElement serialize(ZonedDateTime datetime, Type type, JsonSerializationContext context) {
482                         return new JsonPrimitive(datetime.format(format));
483                 }       
484         }
485         
486         public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
487
488                 @Override
489                 public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
490                                 throws JsonParseException {
491                         return Instant.ofEpochMilli(json.getAsLong());
492                 }
493
494                 @Override
495                 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
496                         return new JsonPrimitive(src.toEpochMilli());
497                 }
498                 
499         }
500
501         
502         /**
503          * decoder
504          */
505         @JsonIgnore
506         protected final Gson decoder = new GsonBuilder().disableHtmlEscaping().
507                                                                                                          registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
508                                                                                                          create();
509         
510         /**
511          * encoder
512          */
513         @JsonIgnore
514         protected final Gson encoder = new GsonBuilder().disableHtmlEscaping().
515                                                                                                          registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
516                                                                                                          create();
517         
518         /**
519          * Toolset to encode/decode tools associated with a topic
520          * 
521          * @param topic topic
522          * @param decodedClass decoded class of an event
523          * @param filter 
524          */
525         public GsonProtocolCoderToolset(String topic, String controllerId, 
526                                                                         String groupId, String artifactId,
527                                                                         String decodedClass, 
528                                                                         JsonProtocolFilter filter,
529                                                                 CustomGsonCoder customGsonCoder,
530                                                                 int modelClassLoaderHash) {
531                 super(topic, controllerId, groupId, artifactId, decodedClass, filter, customGsonCoder, modelClassLoaderHash);
532         }
533         
534         /**
535          * gets the Gson decoder
536          * 
537          * @return the Gson decoder
538          */
539         @JsonIgnore
540         protected Gson getDecoder() {return decoder;}
541         
542         /**
543          * gets the Gson encoder
544          * 
545          * @return the Gson encoder
546          */
547         @JsonIgnore
548         protected Gson getEncoder() {return encoder;}
549         
550         /**
551          * {@inheritDoc}
552          */
553         @Override
554         public Object decode(String json) 
555                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
556         
557                 DroolsController droolsController = 
558                                 DroolsController.factory.get(groupId, artifactId, "");
559                 if (droolsController == null) {
560                         String error = "NO-DROOLS-CONTROLLER for: " + json + " IN " + this;
561                         logger.warn(error);
562                         throw new IllegalStateException(error);
563                 }
564                 
565                 CoderFilters decoderFilter = filter(json);
566                 if (decoderFilter == null) {
567                         String error = "NO-DECODER for: " + json + " IN " + this;
568                         logger.warn(error);
569                         throw new UnsupportedOperationException(error);
570                 }
571                 
572                 Class<?> decoderClass;
573                 try {
574                         decoderClass = 
575                                         droolsController.fetchModelClass(decoderFilter.getCodedClass());
576                         if (decoderClass ==  null) {
577                                 String error = "DECODE-ERROR FETCHING MODEL CLASS: " + ":" + json + ":" + this;
578                                 logger.error(error);
579                                 throw new IllegalStateException(error);                         
580                         }
581                 } catch (Exception e) {
582                         String error = "DECODE-ERROR FETCHING MODEL CLASS: "+ e.getMessage() + ":" + json + ":" + this;
583                         logger.warn(MessageCodes.EXCEPTION_ERROR, e, error);
584                         throw new UnsupportedOperationException(error, e);
585                 }
586                 
587                 if (this.customCoder != null) {
588                         try {
589                                 Class<?> gsonClassContainer = 
590                                                 droolsController.fetchModelClass(this.customCoder.getClassContainer());
591                                 Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
592                                 Object gsonObject = gsonField.get(null);
593                                 Method fromJsonMethod = gsonObject.getClass().
594                                                                      getDeclaredMethod
595                                                                         ("fromJson", new Class[]{String.class, Class.class});
596                                 Object fact = fromJsonMethod.invoke(gsonObject, json, decoderClass);
597                                 return fact;
598                         } catch (NoSuchFieldException | SecurityException | IllegalAccessException | 
599                                          NoSuchMethodException | InvocationTargetException e) {
600                                 String error = "DECODE-ERROR-FROM-CUSTOM-CODER: " + e.getMessage() + ":" + json + ":" + this;
601                                 logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
602                                 throw new UnsupportedOperationException(error, e);
603                         }                       
604                 } else {
605                         try {
606                                 Object fact = this.decoder.fromJson(json, decoderClass);
607                                 return fact;
608                         } catch (Exception e) {
609                                 String error = "DECODE-ERROR FROM PDP-D FRAMEWORK: "+ json + ":" + e.getMessage() + ":" + this;
610                                 logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
611                                 throw new UnsupportedOperationException(error, e);
612                         }                       
613                 }
614         }
615
616
617         
618         /**
619          * {@inheritDoc}
620          */
621         @Override
622         public String encode(Object event) 
623                         throws IllegalArgumentException, UnsupportedOperationException {        
624                 
625                 DroolsController droolsController = 
626                                 DroolsController.factory.get(groupId, artifactId, "");
627                 if (droolsController == null) {
628                         String error = "NO-DROOLS-CONTROLLER for: " + event + " IN " + this;
629                         logger.warn(error);
630                         throw new IllegalStateException(error);
631                 }
632                 
633                 if (this.customCoder != null) {
634                         try {
635                                 Class<?> gsonClassContainer = 
636                                                 droolsController.fetchModelClass(this.customCoder.getClassContainer());
637                                 Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
638                                 Object gsonObject = gsonField.get(null);
639                                 Method toJsonMethod = gsonObject.getClass().
640                                                                      getDeclaredMethod
641                                                                         ("toJson", new Class[]{Object.class});
642                                 String encodedJson = (String) toJsonMethod.invoke(gsonObject, event);
643                                 return encodedJson;
644                         } catch (NoSuchFieldException | SecurityException | IllegalAccessException | 
645                                          NoSuchMethodException | InvocationTargetException e) {
646                                 String error = "DECODE-ERROR-FROM-CUSTOM-CODER: " + e.getMessage() + ":" + event + ":" + this;
647                                 logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
648                                 throw new UnsupportedOperationException(error, e);
649                         }                       
650                 } else {
651                         try {
652                                 String encodedEvent = this.encoder.toJson(event);
653                                 return encodedEvent;                    
654                         } catch (Exception e) {
655                                 String error = "ENCODE-ERROR: "+ event + " IN " + this;
656                                 logger.error(MessageCodes.EXCEPTION_ERROR, e, error);
657                                 throw new UnsupportedOperationException(error, e);
658                         }               
659                 }
660         }
661
662         @Override
663         public String toString() {
664                 StringBuilder builder = new StringBuilder();
665                 builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
666                 return builder.toString();
667         }
668 }