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