bf7a43f381c033e41edd61006e065674c8050cc8
[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.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;
32
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;
40
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;
56
57 /**
58  * Protocol Coding/Decoding Toolset
59  */
60 public abstract class ProtocolCoderToolset {
61         
62         /**
63          * Logger
64          */
65         private static Logger logger = LoggerFactory.getLogger(ProtocolCoderToolset.class);
66         
67         /**
68          * topic
69          */
70         protected final String topic;
71         
72         /**
73          * controller id
74          */
75         protected final String controllerId;
76         
77         /**
78          * group id
79          */
80         protected final String groupId;
81         
82         /**
83          * artifact id
84          */
85         protected final String artifactId;
86         
87         /**
88          * Protocols and associated Filters
89          */
90         protected final List<CoderFilters> coders = new ArrayList<CoderFilters>();
91         
92         /**
93          * Tree model (instead of class model) generic parsing to be able to inspect elements
94          */
95         protected JsonParser filteringParser = new JsonParser();
96
97         /**
98          * custom coder
99          */
100         protected CustomCoder customCoder;
101         
102         /**
103          * Constructor
104          * 
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
111          */
112         public ProtocolCoderToolset(String topic, 
113                                                                 String controllerId, 
114                                             String groupId,
115                                             String artifactId,
116                                             String codedClass, 
117                                             JsonProtocolFilter filters, 
118                                                         CustomCoder customCoder,
119                                                         int modelClassLoaderHash)
120                 throws IllegalArgumentException {
121                 
122                 if (topic == null || controllerId == null || 
123                         groupId == null || artifactId == null ||
124                         codedClass == null || filters == null ||
125                         topic.isEmpty() || controllerId.isEmpty()) {
126                         // TODO
127                         throw new IllegalArgumentException("Invalid input");
128                 }
129                 
130                 this.topic = topic;
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;
136         }
137         
138         /**
139          * gets the coder + filters associated with this class name
140          * 
141          * @param classname class name
142          * @return the decoder filters or null if not found
143          */
144         public CoderFilters getCoder(String classname) {
145                 for (CoderFilters decoder: this.coders) {
146                         if (decoder.factClass.equals(classname)) {
147                                 return decoder;
148                         }
149                 }
150                 return null;
151         }
152
153         /**
154          * get all coder filters in use
155          * 
156          * @return coder filters
157          */
158         public List<CoderFilters> getCoders() {
159                 return this.coders;
160         }
161
162         /**
163          * add coder or replace it exists
164          * 
165          * @param eventClass decoder
166          * @param filter filter
167          */
168         public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) {
169                 synchronized(this) {
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;
177                                         return;
178                                 }
179                         }
180                 }
181                 
182                 this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash));
183         }
184         
185         /**
186          * remove coder
187          * 
188          * @param eventClass decoder
189          * @param filter filter
190          */
191         public void removeCoders(String eventClass) {
192                 synchronized(this) {
193                         Iterator<CoderFilters> codersIt = this.coders.iterator();
194                         while (codersIt.hasNext()) {
195                                 CoderFilters coder = codersIt.next();
196                                 if (coder.factClass.equals(eventClass)) {
197                                         codersIt.remove();
198                                 }
199                         }
200                 }
201         }
202
203         /**
204          * gets the topic
205          * 
206          * @return the topic
207          */
208         public String getTopic() {return topic;}
209         
210         /**
211          * gets the controller id
212          * 
213          * @return the controller id
214          */
215         public String getControllerId() {return controllerId;}
216
217         /**
218          * @return the groupId
219          */
220         public String getGroupId() {
221                 return groupId;
222         }
223
224         /**
225          * @return the artifactId
226          */
227         public String getArtifactId() {
228                 return artifactId;
229         }
230
231         /**
232          * @return the customCoder
233          */
234         public CustomCoder getCustomCoder() {
235                 return customCoder;
236         }
237
238         /**
239          * @param customCoder the customCoder to set
240          */
241         public void setCustomCoder(CustomCoder customCoder) {
242                 this.customCoder = customCoder;
243         }
244
245         /**
246          * performs filtering on a json string
247          * 
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
252          */
253         protected CoderFilters filter(String json) 
254                         throws UnsupportedOperationException, IllegalArgumentException, IllegalStateException {
255                 
256
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        
261                 
262                 // Don't parse if it is not necessary
263                 
264                 if (this.coders.isEmpty()) {
265                         // TODO this is an error
266                         throw new IllegalStateException("No coders available");
267                 }
268                 
269                 if (this.coders.size() == 1) {
270                         JsonProtocolFilter filter = this.coders.get(0).getFilter();
271                         if (!filter.isRules()) {
272                                 return this.coders.get(0);
273                         }
274                 }
275                 
276                 JsonElement event;
277                 try {
278                         event = this.filteringParser.parse(json);
279                 } catch (Exception e) {
280                         throw new UnsupportedOperationException(e);
281                 }
282                 
283                 for (CoderFilters decoder: this.coders) {
284                         try {
285                                 boolean accepted = decoder.getFilter().accept(event);
286                                 if (accepted) {
287                                         return decoder;
288                                 } 
289                         } catch (Exception e) {
290                                 logger.info("{}: unexpected failure accepting {} because of {}", 
291                                                     this, event, e.getMessage(), e);
292                                 // continue
293                         }
294                 }
295                 
296                 return null;
297         }
298
299         /**
300          * Decode json into a POJO object
301          * @param json json string
302          * 
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
306          */
307         public abstract Object decode(String json) 
308                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException;
309         
310         /**
311          * Encodes a POJO object into a JSON String
312          * 
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
317          */
318         public abstract String encode(Object event) 
319                         throws IllegalArgumentException, UnsupportedOperationException;
320
321         @Override
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();
329         }
330 }
331
332 /**
333  * Tools used for encoding/decoding using Jackson
334  */
335 class JacksonProtocolCoderToolset extends ProtocolCoderToolset {
336         private static Logger  logger = LoggerFactory.getLogger(JacksonProtocolCoderToolset.class);     
337         /**
338          * decoder
339          */
340         @JsonIgnore
341         protected final ObjectMapper decoder = new ObjectMapper();
342         
343         /**
344          * encoder
345          */
346         @JsonIgnore
347         protected final ObjectMapper encoder = new ObjectMapper();
348         
349         /**
350          * Toolset to encode/decode tools associated with a topic
351          * 
352          * @param topic topic
353          * @param decodedClass decoded class of an event
354          * @param filter 
355          */
356         public JacksonProtocolCoderToolset(String topic, String controllerId, 
357                                                                            String groupId, String artifactId,
358                                                    String decodedClass, 
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, 
365                                           false);
366         }
367         
368         /**
369          * gets the Jackson decoder
370          * 
371          * @return the Jackson decoder
372          */
373         @JsonIgnore
374         protected ObjectMapper getDecoder() {return decoder;}
375         
376         /**
377          * gets the Jackson encoder
378          * 
379          * @return the Jackson encoder
380          */
381         @JsonIgnore
382         protected ObjectMapper getEncoder() {return encoder;}
383         
384         /**
385          * {@inheritDoc}
386          */
387         @Override
388         public Object decode(String json) 
389                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
390
391                 // 0. Use custom coder if available
392                 
393                 if (this.customCoder != null) {
394                         throw new UnsupportedOperationException
395                                         ("Jackon Custom Decoder is not supported at this time");
396                 }
397                 
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");
403                 }
404                 
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");
409                 }
410                 
411                 Class<?> decoderClass;
412                 try {
413                         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());                            
418                         }
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);
423                 }
424                 
425
426                 try {
427                         Object fact = this.decoder.readValue(json, decoderClass);
428                         return fact;
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);
433                 }
434         }
435         
436         /**
437          * {@inheritDoc}
438          */
439         @Override
440         public String encode(Object event) 
441                         throws IllegalArgumentException, UnsupportedOperationException {
442                 
443                 // 0. Use custom coder if available
444                 
445                 if (this.customCoder != null) {
446                         throw new UnsupportedOperationException
447                                         ("Jackon Custom Encoder is not supported at this time");
448                 }
449                 
450                 try {
451                         String encodedEvent = this.encoder.writeValueAsString(event);
452                         return encodedEvent;                    
453                 } catch (JsonProcessingException e) {
454                         logger.error("{} cannot encode {} because of {}", this, event, e.getMessage(), e);
455                         throw new UnsupportedOperationException("event cannot be encoded");
456                 }
457         }
458
459         @Override
460         public String toString() {
461                 StringBuilder builder = new StringBuilder();
462                 builder.append("JacksonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
463                 return builder.toString();
464         }
465
466 }
467
468 /**
469  * Tools used for encoding/decoding using Jackson
470  */
471 class GsonProtocolCoderToolset extends ProtocolCoderToolset {
472         
473         /**
474          * Logger
475          */
476         private static Logger  logger = LoggerFactory.getLogger(GsonProtocolCoderToolset.class);
477         
478         /**
479          * Formatter for JSON encoding/decoding
480          */
481         @JsonIgnore
482         public static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx");
483         
484         @JsonIgnore
485         public static DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT;
486         
487         /**
488          * Adapter for ZonedDateTime
489          */
490
491         public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
492
493                 public ZonedDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context)
494                                 throws JsonParseException {
495                         try {
496                                 return ZonedDateTime.parse(element.getAsString(), format);
497                         } catch (Exception e) {
498                                 logger.info("GsonUTCAdapter: cannot parse {} because of {}", 
499                                                     element, e.getMessage(), e);
500                         }
501                         return null;
502                 }
503
504                 public JsonElement serialize(ZonedDateTime datetime, Type type, JsonSerializationContext context) {
505                         return new JsonPrimitive(datetime.format(format));
506                 }       
507         }
508         
509         public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
510
511                 @Override
512                 public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
513                                 throws JsonParseException {
514                         return Instant.ofEpochMilli(json.getAsLong());
515                 }
516
517                 @Override
518                 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
519                         return new JsonPrimitive(src.toEpochMilli());
520                 }
521                 
522         }
523
524         
525         /**
526          * decoder
527          */
528         @JsonIgnore
529         protected final Gson decoder = new GsonBuilder().disableHtmlEscaping().
530                                                                                                          registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
531                                                                                                          create();
532         
533         /**
534          * encoder
535          */
536         @JsonIgnore
537         protected final Gson encoder = new GsonBuilder().disableHtmlEscaping().
538                                                                                                          registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()).
539                                                                                                          create();
540         
541         /**
542          * Toolset to encode/decode tools associated with a topic
543          * 
544          * @param topic topic
545          * @param decodedClass decoded class of an event
546          * @param filter 
547          */
548         public GsonProtocolCoderToolset(String topic, String controllerId, 
549                                                                         String groupId, String artifactId,
550                                                                         String decodedClass, 
551                                                                         JsonProtocolFilter filter,
552                                                                 CustomGsonCoder customGsonCoder,
553                                                                 int modelClassLoaderHash) {
554                 super(topic, controllerId, groupId, artifactId, decodedClass, filter, customGsonCoder, modelClassLoaderHash);
555         }
556         
557         /**
558          * gets the Gson decoder
559          * 
560          * @return the Gson decoder
561          */
562         @JsonIgnore
563         protected Gson getDecoder() {return decoder;}
564         
565         /**
566          * gets the Gson encoder
567          * 
568          * @return the Gson encoder
569          */
570         @JsonIgnore
571         protected Gson getEncoder() {return encoder;}
572         
573         /**
574          * {@inheritDoc}
575          */
576         @Override
577         public Object decode(String json) 
578                         throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException {
579         
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");
585                 }
586                 
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");
591                 }
592                 
593                 Class<?> decoderClass;
594                 try {
595                         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());                            
600                         }
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);
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 (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);
622                         }                       
623                 } else {
624                         try {
625                                 Object fact = this.decoder.fromJson(json, decoderClass);
626                                 return fact;
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);
631                         }                       
632                 }
633         }
634
635
636         
637         /**
638          * {@inheritDoc}
639          */
640         @Override
641         public String encode(Object event) 
642                         throws IllegalArgumentException, UnsupportedOperationException {        
643                 
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");
649                 }
650                 
651                 if (this.customCoder != null) {
652                         try {
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().
658                                                                      getDeclaredMethod
659                                                                         ("toJson", new Class[]{Object.class});
660                                 String encodedJson = (String) toJsonMethod.invoke(gsonObject, event);
661                                 return encodedJson;
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);
665                         }                       
666                 } else {
667                         try {
668                                 String encodedEvent = this.encoder.toJson(event);
669                                 return encodedEvent;                    
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);
673                         }               
674                 }
675         }
676
677         @Override
678         public String toString() {
679                 StringBuilder builder = new StringBuilder();
680                 builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
681                 return builder.toString();
682         }
683 }