2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.policy.drools.protocol.coders;
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;
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;
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;
52 * Protocol Coding/Decoding Toolset.
54 public abstract class ProtocolCoderToolset {
59 private static Logger logger = LoggerFactory.getLogger(ProtocolCoderToolset.class);
64 protected final String topic;
69 protected final String controllerId;
74 protected final String groupId;
79 protected final String artifactId;
82 * Protocols and associated Filters.
84 protected final List<CoderFilters> coders = new CopyOnWriteArrayList<>();
87 * Tree model (instead of class model) generic parsing to be able to inspect elements.
89 protected JsonParser filteringParser = new JsonParser();
94 protected CustomCoder customCoder;
99 * @param eventProtocolParams parameter object for event encoder
100 * @param controllerId the controller id
101 * @throws IllegalArgumentException if invalid data has been passed in
103 public ProtocolCoderToolset(EventProtocolParams eventProtocolParams, String controllerId) {
105 if (eventProtocolParams == null || controllerId == null) {
106 throw new IllegalArgumentException("Invalid input");
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();
121 * gets the coder + filters associated with this class name.
123 * @param classname class name
124 * @return the decoder filters or null if not found
126 public CoderFilters getCoder(String classname) {
127 if (classname == null || classname.isEmpty()) {
128 throw new IllegalArgumentException("no classname provided");
131 for (final CoderFilters decoder : this.coders) {
132 if (decoder.factClass.equals(classname)) {
140 * get a copy of the coder filters in use.
142 * @return coder filters
144 public List<CoderFilters> getCoders() {
145 return new ArrayList<>(this.coders);
149 * add coder or replace it exists.
151 * @param eventClass decoder
152 * @param filter filter
154 public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) {
155 if (eventClass == null || eventClass.isEmpty()) {
156 throw new IllegalArgumentException("no event class provided");
159 for (final CoderFilters coder : this.coders) {
160 if (coder.getCodedClass().equals(eventClass)) {
161 coder.setFilter(filter);
162 coder.setFromClassLoaderHash(modelClassLoaderHash);
166 this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash));
172 * @param eventClass event class
174 public void removeCoders(String eventClass) {
175 if (eventClass == null || eventClass.isEmpty()) {
176 throw new IllegalArgumentException("no event class provided");
179 List<CoderFilters> temp = new ArrayList<>();
180 for (final CoderFilters coder : this.coders) {
181 if (coder.factClass.equals(eventClass)) {
186 this.coders.removeAll(temp);
194 public String getTopic() {
199 * gets the controller id.
201 * @return the controller id
203 public String getControllerId() {
204 return this.controllerId;
210 * @return the groupId
212 public String getGroupId() {
219 * @return the artifactId
221 public String getArtifactId() {
222 return this.artifactId;
228 * @return the customCoder
230 public CustomCoder getCustomCoder() {
231 return this.customCoder;
237 * @param customCoder the customCoder to set.
239 public void setCustomCoder(CustomCoder customCoder) {
240 this.customCoder = customCoder;
244 * performs filtering on a json string.
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
251 protected CoderFilters filter(String json) {
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
259 // Don't parse if it is not necessary
261 if (this.coders.isEmpty()) {
262 throw new IllegalStateException("No coders available");
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);
274 event = this.filteringParser.parse(json);
275 } catch (final Exception e) {
276 throw new UnsupportedOperationException(e);
279 for (final CoderFilters decoder : this.coders) {
281 final boolean accepted = decoder.getFilter().accept(event);
285 } catch (final Exception e) {
286 logger.info("{}: unexpected failure accepting {} because of {}", this, event,
296 * Decode json into a POJO object.
298 * @param json json string
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
304 public abstract Object decode(String json);
307 * Encodes a POJO object into a JSON String.
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
314 public abstract String encode(Object event);
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();
331 * Tools used for encoding/decoding using GSON.
333 class GsonProtocolCoderToolset extends ProtocolCoderToolset {
337 private static final Logger logger = LoggerFactory.getLogger(GsonProtocolCoderToolset.class);
340 * Formatter for JSON encoding/decoding.
343 public static final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx");
346 public static final DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT;
349 * Adapter for ZonedDateTime.
351 public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
353 public ZonedDateTime deserialize(JsonElement element, Type type,
354 JsonDeserializationContext context) {
356 return ZonedDateTime.parse(element.getAsString(), format);
357 } catch (final Exception e) {
358 logger.info("GsonUTCAdapter: cannot parse {} because of {}", element, e.getMessage(), e);
364 public JsonElement serialize(ZonedDateTime datetime, Type type,
365 JsonSerializationContext context) {
366 return new JsonPrimitive(datetime.format(format));
370 public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
373 public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
374 return Instant.ofEpochMilli(json.getAsLong());
378 public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
379 return new JsonPrimitive(src.toEpochMilli());
389 protected final Gson decoder = new GsonBuilder().disableHtmlEscaping()
390 .registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter())
391 .registerTypeAdapter(Instant.class, new GsonInstantAdapter()).create();
397 protected final Gson encoder = new GsonBuilder().disableHtmlEscaping()
398 .registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter())
399 .registerTypeAdapter(Instant.class, new GsonInstantAdapter()).create();
402 * Toolset to encode/decode tools associated with a topic.
404 * @param eventProtocolParams parameter object for event encoder
405 * @param controllerId controller id
407 public GsonProtocolCoderToolset(EventProtocolParams eventProtocolParams, String controllerId) {
408 super(eventProtocolParams, controllerId);
412 * gets the Gson decoder.
414 * @return the Gson decoder
417 protected Gson getDecoder() {
422 * gets the Gson encoder.
424 * @return the Gson encoder
427 protected Gson getEncoder() {
435 public Object decode(String json) {
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");
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");
450 Class<?> decoderClass;
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());
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);
465 if (this.customCoder != null) {
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);
482 return this.decoder.fromJson(json, decoderClass);
483 } catch (final Exception e) {
484 logger.warn("{} cannot decode {} into {} because of {}", this, json, decoderClass.getName(),
486 throw new UnsupportedOperationException(
487 "cannont decode into " + decoderFilter.getCodedClass(), e);
496 public String encode(Object event) {
498 if (this.customCoder != null) {
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);
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);
524 public String toString() {
525 final StringBuilder builder = new StringBuilder();
526 builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]");
527 return builder.toString();