97598df4f507dfe696d2017fd5ecb062a5a0bc09
[policy/drools-pdp.git] / policy-management / src / main / java / org / onap / policy / drools / protocol / coders / ProtocolCoderToolset.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2018 Samsung Electronics Co., Ltd.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.drools.protocol.coders;
23
24 import com.fasterxml.jackson.annotation.JsonIgnore;
25 import com.google.gson.Gson;
26 import com.google.gson.GsonBuilder;
27 import com.google.gson.JsonDeserializationContext;
28 import com.google.gson.JsonDeserializer;
29 import com.google.gson.JsonElement;
30 import com.google.gson.JsonParser;
31 import com.google.gson.JsonPrimitive;
32 import com.google.gson.JsonSerializationContext;
33 import com.google.gson.JsonSerializer;
34
35 import java.lang.reflect.Field;
36 import java.lang.reflect.Method;
37 import java.lang.reflect.Type;
38 import java.time.Instant;
39 import java.time.ZonedDateTime;
40 import java.time.format.DateTimeFormatter;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.CopyOnWriteArrayList;
44
45 import org.onap.policy.drools.controller.DroolsController;
46 import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters;
47 import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomCoder;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * Protocol Coding/Decoding Toolset.
53  */
54 public abstract class ProtocolCoderToolset {
55
56     /**
57      * Logger.
58      */
59     private static Logger logger = LoggerFactory.getLogger(ProtocolCoderToolset.class);
60
61     /**
62      * topic.
63      */
64     protected final String topic;
65
66     /**
67      * controller id.
68      */
69     protected final String controllerId;
70
71     /**
72      * group id.
73      */
74     protected final String groupId;
75
76     /**
77      * artifact id.
78      */
79     protected final String artifactId;
80
81     /**
82      * Protocols and associated Filters.
83      */
84     protected final List<CoderFilters> coders = new CopyOnWriteArrayList<>();
85
86     /**
87      * Tree model (instead of class model) generic parsing to be able to inspect elements.
88      */
89     protected JsonParser filteringParser = new JsonParser();
90
91     /**
92      * custom coder.
93      */
94     protected CustomCoder customCoder;
95
96     /**
97      * Constructor.
98      *
99      * @param eventProtocolParams parameter object for event encoder
100      * @param controllerId the controller id
101      * @throws IllegalArgumentException if invalid data has been passed in
102      */
103     public ProtocolCoderToolset(EventProtocolParams eventProtocolParams, String controllerId) {
104
105         if (eventProtocolParams == null || controllerId == null) {
106             throw new IllegalArgumentException("Invalid input");
107         }
108
109         this.topic = eventProtocolParams.getTopic();
110         this.controllerId = controllerId;
111         this.groupId = eventProtocolParams.getGroupId();
112         this.artifactId = eventProtocolParams.getArtifactId();
113         this.coders.add(new CoderFilters(
114                 eventProtocolParams.getEventClass(),
115                 eventProtocolParams.getProtocolFilter(),
116                 eventProtocolParams.getModelClassLoaderHash()));
117         this.customCoder = eventProtocolParams.getCustomCoder();
118     }
119
120     /**
121      * gets the coder + filters associated with this class name.
122      *
123      * @param classname class name
124      * @return the decoder filters or null if not found
125      */
126     public CoderFilters getCoder(String classname) {
127         if (classname == null || classname.isEmpty()) {
128             throw new IllegalArgumentException("no classname provided");
129         }
130
131         for (final CoderFilters decoder : this.coders) {
132             if (decoder.factClass.equals(classname)) {
133                 return decoder;
134             }
135         }
136         return null;
137     }
138
139     /**
140      * get a copy of the coder filters in use.
141      *
142      * @return coder filters
143      */
144     public List<CoderFilters> getCoders() {
145         return new ArrayList<>(this.coders);
146     }
147
148     /**
149      * add coder or replace it exists.
150      *
151      * @param eventClass decoder
152      * @param filter filter
153      */
154     public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) {
155         if (eventClass == null || eventClass.isEmpty()) {
156             throw new IllegalArgumentException("no event class provided");
157         }
158
159         for (final CoderFilters coder : this.coders) {
160             if (coder.getCodedClass().equals(eventClass)) {
161                 coder.setFilter(filter);
162                 coder.setFromClassLoaderHash(modelClassLoaderHash);
163                 return;
164             }
165         }
166         this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash));
167     }
168
169     /**
170      * remove coder.
171      * 
172      * @param eventClass event class
173      */
174     public void removeCoders(String eventClass) {
175         if (eventClass == null || eventClass.isEmpty()) {
176             throw new IllegalArgumentException("no event class provided");
177         }
178
179         List<CoderFilters> temp = new ArrayList<>();
180         for (final CoderFilters coder : this.coders) {
181             if (coder.factClass.equals(eventClass)) {
182                 temp.add(coder);
183             }
184         }
185
186         this.coders.removeAll(temp);
187     }
188
189     /**
190      * gets the topic.
191      *
192      * @return the topic
193      */
194     public String getTopic() {
195         return this.topic;
196     }
197
198     /**
199      * gets the controller id.
200      *
201      * @return the controller id
202      */
203     public String getControllerId() {
204         return this.controllerId;
205     }
206
207     /**
208      * Get group id.
209      * 
210      * @return the groupId
211      */
212     public String getGroupId() {
213         return this.groupId;
214     }
215
216     /**
217      * Get artifact id.
218      * 
219      * @return the artifactId
220      */
221     public String getArtifactId() {
222         return this.artifactId;
223     }
224
225     /**
226      * Get custom coder.
227      * 
228      * @return the customCoder
229      */
230     public CustomCoder getCustomCoder() {
231         return this.customCoder;
232     }
233
234     /**
235      * Set custom coder.
236      * 
237      * @param customCoder the customCoder to set.
238      */
239     public void setCustomCoder(CustomCoder customCoder) {
240         this.customCoder = customCoder;
241     }
242
243     /**
244      * performs filtering on a json string.
245      *
246      * @param json json string
247      * @return the decoder that passes the filter, otherwise null
248      * @throws UnsupportedOperationException can't filter
249      * @throws IllegalArgumentException invalid input
250      */
251     protected CoderFilters filter(String json) {
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             throw new IllegalStateException("No coders available");
263         }
264
265         if (this.coders.size() == 1) {
266             final JsonProtocolFilter filter = this.coders.get(0).getFilter();
267             if (!filter.isRules()) {
268                 return this.coders.get(0);
269             }
270         }
271
272         JsonElement event;
273         try {
274             event = this.filteringParser.parse(json);
275         } catch (final Exception e) {
276             throw new UnsupportedOperationException(e);
277         }
278
279         for (final CoderFilters decoder : this.coders) {
280             try {
281                 final boolean accepted = decoder.getFilter().accept(event);
282                 if (accepted) {
283                     return decoder;
284                 }
285             } catch (final Exception e) {
286                 logger.info("{}: unexpected failure accepting {} because of {}", this, event,
287                         e.getMessage(), e);
288                 // continue
289             }
290         }
291
292         return null;
293     }
294
295     /**
296      * Decode json into a POJO object.
297      *
298      * @param json json string
299      *
300      * @return a POJO object for the json string
301      * @throws IllegalArgumentException if an invalid parameter has been received
302      * @throws UnsupportedOperationException if parsing into POJO is not possible
303      */
304     public abstract Object decode(String json);
305
306     /**
307      * Encodes a POJO object into a JSON String.
308      *
309      * @param event JSON POJO event to be converted to String
310      * @return JSON string version of POJO object
311      * @throws IllegalArgumentException if an invalid parameter has been received
312      * @throws UnsupportedOperationException if parsing into POJO is not possible
313      */
314     public abstract String encode(Object event);
315
316     @Override
317     public String toString() {
318         final StringBuilder builder = new StringBuilder();
319         builder.append("ProtocolCoderToolset [topic=").append(this.topic).append(", controllerId=")
320         .append(this.controllerId).append(", groupId=").append(this.groupId).append(", artifactId=")
321         .append(this.artifactId).append(", coders=").append(this.coders)
322         .append(", filteringParser=").append(this.filteringParser).append(", customCoder=")
323         .append(this.customCoder).append("]");
324         return builder.toString();
325     }
326 }
327
328
329
330 /**
331  * Tools used for encoding/decoding using GSON.
332  */
333 class GsonProtocolCoderToolset extends ProtocolCoderToolset {
334     /**
335      * Logger.
336      */
337     private static final Logger logger = LoggerFactory.getLogger(GsonProtocolCoderToolset.class);
338
339     /**
340      * Formatter for JSON encoding/decoding.
341      */
342     @JsonIgnore
343     public static final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx");
344
345     @JsonIgnore
346     public static final DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT;
347
348     /**
349      * Adapter for ZonedDateTime.
350      */
351     public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
352         @Override
353         public ZonedDateTime deserialize(JsonElement element, Type type,
354                 JsonDeserializationContext context) {
355             try {
356                 return ZonedDateTime.parse(element.getAsString(), format);
357             } catch (final Exception e) {
358                 logger.info("GsonUTCAdapter: cannot parse {} because of {}", element, e.getMessage(), e);
359             }
360             return null;
361         }
362
363         @Override
364         public JsonElement serialize(ZonedDateTime datetime, Type type,
365                 JsonSerializationContext context) {
366             return new JsonPrimitive(datetime.format(format));
367         }
368     }
369
370     public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
371
372         @Override
373         public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
374             return Instant.ofEpochMilli(json.getAsLong());
375         }
376
377         @Override
378         public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
379             return new JsonPrimitive(src.toEpochMilli());
380         }
381
382     }
383
384
385     /**
386      * decoder.
387      */
388     @JsonIgnore
389     protected final Gson decoder = new GsonBuilder().disableHtmlEscaping()
390         .registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter())
391         .registerTypeAdapter(Instant.class, new GsonInstantAdapter()).create();
392
393     /**
394      * encoder.
395      */
396     @JsonIgnore
397     protected final Gson encoder = new GsonBuilder().disableHtmlEscaping()
398         .registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter())
399         .registerTypeAdapter(Instant.class, new GsonInstantAdapter()).create();
400
401     /**
402      * Toolset to encode/decode tools associated with a topic.
403      *
404      * @param eventProtocolParams parameter object for event encoder
405      * @param controllerId controller id
406      */
407     public GsonProtocolCoderToolset(EventProtocolParams eventProtocolParams, String controllerId) {
408         super(eventProtocolParams, controllerId);
409     }
410
411     /**
412      * gets the Gson decoder.
413      *
414      * @return the Gson decoder
415      */
416     @JsonIgnore
417     protected Gson getDecoder() {
418         return this.decoder;
419     }
420
421     /**
422      * gets the Gson encoder.
423      *
424      * @return the Gson encoder
425      */
426     @JsonIgnore
427     protected Gson getEncoder() {
428         return this.encoder;
429     }
430
431     /**
432      * {@inheritDoc}.
433      */
434     @Override
435     public Object decode(String json) {
436
437         final DroolsController droolsController =
438                 DroolsController.factory.get(this.groupId, this.artifactId, "");
439         if (droolsController == null) {
440             logger.warn("{}: no drools-controller to process {}", this, json);
441             throw new IllegalStateException("no drools-controller to process event");
442         }
443
444         final CoderFilters decoderFilter = this.filter(json);
445         if (decoderFilter == null) {
446             logger.debug("{}: no decoder to process {}", this, json);
447             throw new UnsupportedOperationException("no decoder to process event");
448         }
449
450         Class<?> decoderClass;
451         try {
452             decoderClass = droolsController.fetchModelClass(decoderFilter.getCodedClass());
453             if (decoderClass == null) {
454                 logger.warn("{}: cannot fetch application class {}", this, decoderFilter.getCodedClass());
455                 throw new IllegalStateException(
456                         "cannot fetch application class " + decoderFilter.getCodedClass());
457             }
458         } catch (final Exception e) {
459             logger.warn("{}: cannot fetch application class {} because of {}", this,
460                     decoderFilter.getCodedClass(), e.getMessage());
461             throw new UnsupportedOperationException(
462                     "cannot fetch application class " + decoderFilter.getCodedClass(), e);
463         }
464
465         if (this.customCoder != null) {
466             try {
467                 final Class<?> gsonClassContainer =
468                         droolsController.fetchModelClass(this.customCoder.getClassContainer());
469                 final Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
470                 final Object gsonObject = gsonField.get(null);
471                 final Method fromJsonMethod = gsonObject.getClass().getDeclaredMethod("fromJson",
472                         new Class[] {String.class, Class.class});
473                 return fromJsonMethod.invoke(gsonObject, json, decoderClass);
474             } catch (final Exception e) {
475                 logger.warn("{}: cannot fetch application class {} because of {}", this,
476                         decoderFilter.getCodedClass(), e.getMessage());
477                 throw new UnsupportedOperationException(
478                         "cannot fetch application class " + decoderFilter.getCodedClass(), e);
479             }
480         } else {
481             try {
482                 return this.decoder.fromJson(json, decoderClass);
483             } catch (final Exception e) {
484                 logger.warn("{} cannot decode {} into {} because of {}", this, json, decoderClass.getName(),
485                         e.getMessage(), e);
486                 throw new UnsupportedOperationException(
487                         "cannont decode into " + decoderFilter.getCodedClass(), e);
488             }
489         }
490     }
491
492     /**
493      * {@inheritDoc}.
494      */
495     @Override
496     public String encode(Object event) {
497
498         if (this.customCoder != null) {
499             try {
500                 final DroolsController droolsController =
501                         DroolsController.factory.get(this.groupId, this.artifactId, null);
502                 final Class<?> gsonClassContainer =
503                         droolsController.fetchModelClass(this.customCoder.getClassContainer());
504                 final Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField);
505                 final Object gsonObject = gsonField.get(null);
506                 final Method toJsonMethod =
507                         gsonObject.getClass().getDeclaredMethod("toJson", new Class[] {Object.class});
508                 return (String) toJsonMethod.invoke(gsonObject, event);
509             } catch (final Exception e) {
510                 logger.warn("{} cannot custom-encode {} because of {}", this, event, e.getMessage(), e);
511                 throw new UnsupportedOperationException("event cannot be encoded", e);
512             }
513         } else {
514             try {
515                 return this.encoder.toJson(event);
516             } catch (final Exception e) {
517                 logger.warn("{} cannot encode {} because of {}", this, event, e.getMessage(), e);
518                 throw new UnsupportedOperationException("event cannot be encoded", e);
519             }
520         }
521     }
522
523     @Override
524     public String toString() {
525         final StringBuilder builder = new StringBuilder();
526         builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
527         return builder.toString();
528     }
529 }