223843631d072d5258ade58b64962584f0d2702c
[policy/apex-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2016-2018 Ericsson. All rights reserved.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  * 
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.apex.service.engine.event.impl.jsonprotocolplugin;
22
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.onap.policy.apex.context.SchemaHelper;
27 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
28 import org.onap.policy.apex.model.basicmodel.service.ModelService;
29 import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
30 import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
31 import org.onap.policy.apex.model.eventmodel.concepts.AxField;
32 import org.onap.policy.apex.service.engine.event.ApexEvent;
33 import org.onap.policy.apex.service.engine.event.ApexEventException;
34 import org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter;
35 import org.onap.policy.apex.service.engine.event.ApexEventRuntimeException;
36 import org.onap.policy.apex.service.parameters.eventprotocol.EventProtocolParameters;
37 import org.slf4j.ext.XLogger;
38 import org.slf4j.ext.XLoggerFactory;
39
40 import com.google.gson.Gson;
41 import com.google.gson.GsonBuilder;
42 import com.google.gson.JsonElement;
43 import com.google.gson.JsonObject;
44
45 /**
46  * The Class Apex2JSONEventConverter converts {@link ApexEvent} instances to and from JSON string representations of
47  * Apex events.
48  *
49  * @author Liam Fallon (liam.fallon@ericsson.com)
50  */
51 public class Apex2JSONEventConverter implements ApexEventProtocolConverter {
52     private static final XLogger LOGGER = XLoggerFactory.getXLogger(Apex2JSONEventConverter.class);
53
54     // The parameters for the JSON event protocol
55     private JSONEventProtocolParameters jsonPars;
56
57     /*
58      * (non-Javadoc)
59      * 
60      * @see org.onap.policy.apex.service.engine.event.ApexEventProtocolConverter#init(org.onap.policy.
61      * apex.service.parameters.eventprotocol.EventProtocolParameters)
62      */
63     @Override
64     public void init(final EventProtocolParameters parameters) {
65         // Check and get the JSON parameters
66         if (!(parameters instanceof JSONEventProtocolParameters)) {
67             final String errorMessage = "specified consumer properties are not applicable to the JSON event protocol";
68             LOGGER.warn(errorMessage);
69             throw new ApexEventRuntimeException(errorMessage);
70         }
71
72         jsonPars = (JSONEventProtocolParameters) parameters;
73     }
74
75     /*
76      * (non-Javadoc)
77      *
78      * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#toApexEvent(java.lang.String, java.lang.Object)
79      */
80     @Override
81     public List<ApexEvent> toApexEvent(final String eventName, final Object eventObject) throws ApexEventException {
82         // Check the event eventObject
83         if (eventObject == null) {
84             LOGGER.warn("event processing failed, event is null");
85             throw new ApexEventException("event processing failed, event is null");
86         }
87
88         // Cast the event to a string, if our conversion is correctly configured, this cast should
89         // always work
90         String jsonEventString = null;
91         try {
92             jsonEventString = (String) eventObject;
93         } catch (final Exception e) {
94             final String errorMessage = "error converting event \"" + eventObject + "\" to a string";
95             LOGGER.debug(errorMessage, e);
96             throw new ApexEventRuntimeException(errorMessage, e);
97         }
98
99         // The list of events we will return
100         final List<ApexEvent> eventList = new ArrayList<>();
101
102         try {
103             // We may have a single JSON object with a single event or an array of JSON objects
104             final Object decodedJsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
105                             Object.class);
106
107             // Check if we have a list of objects
108             if (decodedJsonObject instanceof List) {
109                 // Check if it's a list of JSON objects or a list of strings
110                 @SuppressWarnings("unchecked")
111                 final List<Object> decodedJsonList = (List<Object>) decodedJsonObject;
112
113                 // Decode each of the list elements in sequence
114                 for (final Object jsonListObject : decodedJsonList) {
115                     if (jsonListObject instanceof String) {
116                         eventList.add(jsonStringApexEvent(eventName, (String) jsonListObject));
117                     } else if (jsonListObject instanceof JsonObject) {
118                         eventList.add(jsonObject2ApexEvent(eventName, (JsonObject) jsonListObject));
119                     } else {
120                         throw new ApexEventException("incoming event (" + jsonEventString
121                                         + ") is a JSON object array containing an invalid object " + jsonListObject);
122                     }
123                 }
124             } else {
125                 eventList.add(jsonStringApexEvent(eventName, jsonEventString));
126             }
127         } catch (final Exception e) {
128             final String errorString = "Failed to unmarshal JSON event: " + e.getMessage() + ", event="
129                             + jsonEventString;
130             LOGGER.warn(errorString, e);
131             throw new ApexEventException(errorString, e);
132         }
133
134         // Return the list of events we have unmarshalled
135         return eventList;
136     }
137
138     /*
139      * (non-Javadoc)
140      *
141      * @see org.onap.policy.apex.service.engine.event.ApexEventConverter#fromApexEvent(org.onap.policy.
142      * apex.service.engine.event.ApexEvent)
143      */
144     @Override
145     public Object fromApexEvent(final ApexEvent apexEvent) throws ApexEventException {
146         // Check the Apex event
147         if (apexEvent == null) {
148             LOGGER.warn("event processing failed, Apex event is null");
149             throw new ApexEventException("event processing failed, Apex event is null");
150         }
151
152         // Get the event definition for the event from the model service
153         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
154                         apexEvent.getVersion());
155
156         // Use a GSON Json object to marshal the Apex event to JSON
157         final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
158         final JsonObject jsonObject = new JsonObject();
159
160         jsonObject.addProperty(ApexEvent.NAME_HEADER_FIELD, apexEvent.getName());
161         jsonObject.addProperty(ApexEvent.VERSION_HEADER_FIELD, apexEvent.getVersion());
162         jsonObject.addProperty(ApexEvent.NAMESPACE_HEADER_FIELD, apexEvent.getNameSpace());
163         jsonObject.addProperty(ApexEvent.SOURCE_HEADER_FIELD, apexEvent.getSource());
164         jsonObject.addProperty(ApexEvent.TARGET_HEADER_FIELD, apexEvent.getTarget());
165
166         if (apexEvent.getExceptionMessage() != null) {
167             jsonObject.addProperty(ApexEvent.EXCEPTION_MESSAGE_HEADER_FIELD, apexEvent.getExceptionMessage());
168         }
169
170         for (final AxField eventField : eventDefinition.getFields()) {
171             final String fieldName = eventField.getKey().getLocalName();
172
173             if (!apexEvent.containsKey(fieldName)) {
174                 if (!eventField.getOptional()) {
175                     final String errorMessage = "error parsing " + eventDefinition.getID() + " event to Json. "
176                                     + "Field \"" + fieldName + "\" is missing, but is mandatory. Fields: " + apexEvent;
177                     LOGGER.debug(errorMessage);
178                     throw new ApexEventRuntimeException(errorMessage);
179                 }
180                 continue;
181             }
182
183             final Object fieldValue = apexEvent.get(fieldName);
184
185             // Get the schema helper
186             final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
187                             eventField.getSchema());
188             jsonObject.add(fieldName, (JsonElement)fieldSchemaHelper.marshal2Object(fieldValue));
189         }
190
191         // Output JSON string in a pretty format
192         return gson.toJson(jsonObject);
193     }
194
195     /**
196      * This method converts a JSON object into an Apex event.
197      *
198      * @param eventName
199      *        the name of the event
200      * @param jsonEventString
201      *        the JSON string that holds the event
202      * @return the apex event that we have converted the JSON object into
203      * @throws ApexEventException
204      *         thrown on unmarshaling exceptions
205      */
206     private ApexEvent jsonStringApexEvent(final String eventName, final String jsonEventString)
207                     throws ApexEventException {
208         // Use GSON to read the event string
209         final JsonObject jsonObject = new GsonBuilder().serializeNulls().create().fromJson(jsonEventString,
210                         JsonObject.class);
211
212         if (jsonObject == null || !jsonObject.isJsonObject()) {
213             throw new ApexEventException(
214                             "incoming event (" + jsonEventString + ") is not a JSON object or an JSON object array");
215         }
216
217         return jsonObject2ApexEvent(eventName, jsonObject);
218     }
219
220     /**
221      * This method converts a JSON object into an Apex event.
222      *
223      * @param eventName
224      *        the name of the event
225      * @param jsonObject
226      *        the JSON object that holds the event
227      * @return the apex event that we have converted the JSON object into
228      * @throws ApexEventException
229      *         thrown on unmarshaling exceptions
230      */
231     private ApexEvent jsonObject2ApexEvent(final String eventName, final JsonObject jsonObject)
232                     throws ApexEventException {
233         // Process the mandatory Apex header
234         final ApexEvent apexEvent = processApexEventHeader(eventName, jsonObject);
235
236         // Get the event definition for the event from the model service
237         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(apexEvent.getName(),
238                         apexEvent.getVersion());
239
240         // Iterate over the input fields in the event
241         for (final AxField eventField : eventDefinition.getFields()) {
242             final String fieldName = eventField.getKey().getLocalName();
243             if (!hasJSONField(jsonObject, fieldName)) {
244                 if (!eventField.getOptional()) {
245                     final String errorMessage = "error parsing " + eventDefinition.getID() + " event from Json. "
246                                     + "Field \"" + fieldName + "\" is missing, but is mandatory.";
247                     LOGGER.debug(errorMessage);
248                     throw new ApexEventException(errorMessage);
249                 }
250                 continue;
251             }
252
253             final JsonElement fieldValue = getJSONField(jsonObject, fieldName, null, !eventField.getOptional());
254
255             if (fieldValue != null && !fieldValue.isJsonNull()) {
256                 // Get the schema helper
257                 final SchemaHelper fieldSchemaHelper = new SchemaHelperFactory().createSchemaHelper(eventField.getKey(),
258                                 eventField.getSchema());
259                 apexEvent.put(fieldName, fieldSchemaHelper.createNewInstance(fieldValue));
260             } else {
261                 apexEvent.put(fieldName, null);
262             }
263         }
264         return apexEvent;
265
266     }
267
268     /**
269      * This method processes the event header of an Apex event.
270      *
271      * @param eventName
272      *        the name of the event
273      * @param jsonObject
274      *        the JSON object containing the JSON representation of the incoming event
275      * @return an apex event constructed using the header fields of the event
276      * @throws ApexEventRuntimeException
277      *         the apex event runtime exception
278      * @throws ApexEventException
279      *         on invalid events with missing header fields
280      */
281     private ApexEvent processApexEventHeader(final String eventName, final JsonObject jsonObject)
282                     throws ApexEventException {
283         // Get the event header fields
284         // @formatter:off
285         String name      = getJSONStringField(jsonObject, ApexEvent.NAME_HEADER_FIELD,      jsonPars.getNameAlias(),      ApexEvent.NAME_REGEXP,      false);
286         String version   = getJSONStringField(jsonObject, ApexEvent.VERSION_HEADER_FIELD,   jsonPars.getVersionAlias(),   ApexEvent.VERSION_REGEXP,   false);
287         String namespace = getJSONStringField(jsonObject, ApexEvent.NAMESPACE_HEADER_FIELD, jsonPars.getNameSpaceAlias(), ApexEvent.NAMESPACE_REGEXP, false);
288         String source    = getJSONStringField(jsonObject, ApexEvent.SOURCE_HEADER_FIELD,    jsonPars.getSourceAlias(),    ApexEvent.SOURCE_REGEXP,    false);
289         String target    = getJSONStringField(jsonObject, ApexEvent.TARGET_HEADER_FIELD,    jsonPars.getTargetAlias(),    ApexEvent.TARGET_REGEXP,    false);
290         // @formatter:on
291
292         // Check that an event name has been specified
293         if (name == null && eventName == null) {
294             throw new ApexEventRuntimeException(
295                             "event received without mandatory parameter \"name\" on configuration or on event");
296         }
297
298         // Check if an event name was specified on the event parameters
299         if (eventName != null) {
300             if (name != null && !eventName.equals(name)) {
301                 LOGGER.warn("The incoming event name \"{}\" does not match the configured event name \"{}\", using configured event name",
302                                 name, eventName);
303             }
304             name = eventName;
305         }
306
307         // Now, find the event definition in the model service. If version is null, the newest event
308         // definition in the model service is used
309         final AxEvent eventDefinition = ModelService.getModel(AxEvents.class).get(name, version);
310         if (eventDefinition == null) {
311             throw new ApexEventRuntimeException("an event definition for an event named \"" + name
312                             + "\" with version \"" + version + "\" not found in Apex model");
313         }
314
315         // Use the defined event version if no version is specified on the incoming fields
316         if (version == null) {
317             version = eventDefinition.getKey().getVersion();
318         }
319
320         // Check the name space is OK if it is defined, if not, use the name space from the model
321         if (namespace != null) {
322             if (!namespace.equals(eventDefinition.getNameSpace())) {
323                 throw new ApexEventRuntimeException("namespace \"" + namespace + "\" on event \"" + name
324                                 + "\" does not match namespace \"" + eventDefinition.getNameSpace()
325                                 + "\" for that event in the Apex model");
326             }
327         } else {
328             namespace = eventDefinition.getNameSpace();
329         }
330
331         // For source, use the defined source only if the source is not found on the incoming event
332         if (source == null) {
333             source = eventDefinition.getSource();
334         }
335
336         // For target, use the defined source only if the source is not found on the incoming event
337         if (target == null) {
338             target = eventDefinition.getTarget();
339         }
340
341         return new ApexEvent(name, version, namespace, source, target);
342     }
343
344     /**
345      * This method gets an event string field from a JSON object.
346      *
347      * @param jsonObject
348      *        the JSON object containing the JSON representation of the incoming event
349      * @param fieldName
350      *        the field name to find in the event
351      * @param fieldAlias
352      *        the alias for the field to find in the event, overrides the field name if it is not null
353      * @param fieldRE
354      *        the regular expression to check the field against for validity
355      * @param mandatory
356      *        true if the field is mandatory
357      * @return the value of the field in the JSON object or null if the field is optional
358      * @throws ApexEventRuntimeException
359      *         the apex event runtime exception
360      */
361     private String getJSONStringField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
362                     final String fieldRE, final boolean mandatory) {
363         // Get the JSON field for the string field
364         final JsonElement jsonField = getJSONField(jsonObject, fieldName, fieldAlias, mandatory);
365
366         // Null strings are allowed
367         if (jsonField == null || jsonField.isJsonNull()) {
368             return null;
369         }
370
371         // Check if this is a string field
372         String fieldValueString = null;
373         try {
374             fieldValueString = jsonField.getAsString();
375         } catch (final Exception e) {
376             // The element is not a string so throw an error
377             throw new ApexEventRuntimeException("field \"" + fieldName + "\" with type \""
378                             + jsonField.getClass().getCanonicalName() + "\" is not a string value");
379         }
380
381         // Is regular expression checking required
382         if (fieldRE == null) {
383             return fieldValueString;
384         }
385
386         // Check the event field against its regular expression
387         if (!fieldValueString.matches(fieldRE)) {
388             throw new ApexEventRuntimeException(
389                             "field \"" + fieldName + "\" with value \"" + fieldValueString + "\" is invalid");
390         }
391
392         return fieldValueString;
393     }
394
395     /**
396      * This method gets an event field from a JSON object.
397      *
398      * @param jsonObject
399      *        the JSON object containing the JSON representation of the incoming event
400      * @param fieldName
401      *        the field name to find in the event
402      * @param fieldAlias
403      *        the alias for the field to find in the event, overrides the field name if it is not null
404      * @param mandatory
405      *        true if the field is mandatory
406      * @return the value of the field in the JSON object or null if the field is optional
407      * @throws ApexEventRuntimeException
408      *         the apex event runtime exception
409      */
410     private JsonElement getJSONField(final JsonObject jsonObject, final String fieldName, final String fieldAlias,
411                     final boolean mandatory) {
412
413         // Check if we should use the alias for this field
414         String fieldToFind = fieldName;
415         if (fieldAlias != null) {
416             fieldToFind = fieldAlias;
417         }
418
419         // Get the event field
420         final JsonElement eventElement = jsonObject.get(fieldToFind);
421         if (eventElement == null) {
422             if (!mandatory) {
423                 return null;
424             } else {
425                 throw new ApexEventRuntimeException("mandatory field \"" + fieldToFind + "\" is missing");
426             }
427         }
428
429         return eventElement;
430     }
431
432     /**
433      * This method if a JSON object has a named field.
434      *
435      * @param jsonObject
436      *        the JSON object containing the JSON representation of the incoming event
437      * @param fieldName
438      *        the field name to find in the event
439      * @return true if the field is present
440      * @throws ApexEventRuntimeException
441      *         the apex event runtime exception
442      */
443     private boolean hasJSONField(final JsonObject jsonObject, final String fieldName) {
444         // check for the field
445         return jsonObject.has(fieldName);
446     }
447 }